oracle-nosqldb
Version:
Node.js driver for Oracle NoSQL Database
571 lines (562 loc) • 29.3 kB
JavaScript
/*-
* Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
'use strict';
/**
* Describes integration with 3rd party number libraries to support arbitrary
* precision decimal numbers.
*/
/**
* NoSQL database allows you to store arbitrary precision decimal numbers
* in tables by using database type <em>Number</em>, which supports all
* numbers represented by Java BigDecimal class. By default, the driver
* represents them as Javascript numbers. However this has some limitations:
* <ul>
* <li>The number of significant digits is limited to what can be represented
* by Javascript number type, which is approximately 15 decimal
* digits, so the values with higher precision cannot be reresented exactly
* and will be rounded.</li>
* <li>The range is limited to what can be represented by Javascript numbers,
* which is upproximately up to +-1e308.</li>
* <li>Javascript numbers are stored in binary and not decimal format, which
* means that some decimal values cannot be represented exactly and are
* subject to rounding errors when performing decimal calculations.</li>
* </ul>
* <p>
* Because Javascript and Node.js do not currently have a standard way to
* represent arbitrary precision decimal numbers, the driver allows you to use
* a 3rd party number library of your choice. Typically the number libraries
* represent numbers as objects of certain prototype or class and have methods
* to perform arithmetic and other operations. If you enable this feature,
* the driver will represent the column values of datatype <em>Number</em> as
* objects of this type from the number library. You can pass these objects
* as record fields for put operations, as key fields for get operations and
* they will also be returned as part of get and query results wherever the
* column value or expression result is of datatype <em>Number</em>. Then
* your application can use methods from the number library to perform further
* numeric operations on number objects or convert them to suitable display
* representation.
* <p>
* In most cases you only need the following:
* <ol>
* <li>Install the 3rd party number library of your choice (typically from
* NPM) globally or as a dependency of your application (note that the driver
* does not automatically install number libraries)</li>
* <li>In the initial {@link Config} used to create {@link NoSQLClient},
* specify <em>dbNumber</em> property as <em>dbNumber: 'module_name'</em>
* where module_name is the module name of the number library</li>
* </ol>
* See examples below.
* <p>
* The above is sufficient for most cases when using default settings and
* for number libraries that provide standard method names for common
* operations. The driver has been tested with the following number
* libraries:
* <ul>
* <li>[decimal.js]{@link https://github.com/MikeMcl/decimal.js}</li>
* <li>[decimal.js-light]{@link https://github.com/MikeMcl/decimal.js-light}</li>
* <li>[bignumber.js]{@link https://github.com/MikeMcl/bignumber.js}</li>
* <li>[big.js]{@link https://github.com/MikeMcl/big.js}</li>
* </ul>
* <p>
* These libraries should work out of the box with simple steps as described
* above. However you may use any number library of your choice as long as
* it uses objects to represent arbitrary precision decimal numbers, provides
* constructor function to create these objects and supports some common
* operations. Below we describe what the driver needs to know from the
* number library and whether it will work out of the box as mentioned
* above or if it may require additional configuration.
* <p>
* The driver needs to know the following:
* <ol>
* <li> Constructor function. The constructor should be able to create
* the number object from its string representation. Typically the
* number libraries export the constructor as a sole module export, so that
* the result of <em>require('module_name')</em> is the
* constructor. In this case no additional configuration is required.
* </li>
* <li> Conversion to string. Typically this is the instance method
* <em>toString</em> (overriden from <em>Object</em> class), in which case no
* additional configuration is required.</li>
* <li> Operations for comparison, addition, subtraction, multiplication and
* division, as may be needed by the driver for client-side query processing.
* These are typically static (properties of the constructor itself)
* and/or instance methods (properties of constructor's prototype), with the
* static methods taking 2 arguments and instance
* methods taking one argument. The driver will look for common method names
* (instance or static) such as "add", "plus" for addition, "subtract", "sub",
* "minus" for subtraction, etc. See {@link NumberLibMethods} for complete
* list of names. If the number library of your choice uses any of the common
* names for each operation listed above, no additional configuration is
* required.</li>
* <li> Precision and rounding mode. These are optional and if not specified,
* default values will be used. On the server side, NoSQL database uses Java
* BigDecimal to do calculations for Number datatype during query processing.
* The result of each operation is rounded accoring to Java MathContext
* settings, which consist of precision (number of significant digits) and
* rounding mode (how to round the result to precision), so these settings
* need to be provided to the server for query processing. You may choose to
* provide them explicitly in configuration (see {@link NumberLibConfig}) or
* let the driver figure them out from the number library settings using some
* common property names (see {@link NumberLibPrecision} and
* {@link NumberLibRoundingMode}). If not found, default precision of 20 and
* rounding mode <em>ROUND_HALF_UP</em> will be used.</li>
* </ol>
* <p>
* Note that configuration for this feature is always specified as
* <em>dbNumber</em> property of {@link Config} and thus can be provided
* either as part of {@link Config} Javascript object or in JSON file
* containing {@link Config}.
* <p>
* <em>dbNumber</em> property can be one of the following:
* <ul>
* <li>String specifying number library module name as discussed above.</li>
* <li>Constructor function (not available if using JSON file). You may
* specify constructor function explicitly. This is useful when you want to
* set custom settings for the constructor (to customize created number
* instances) before passing it to the driver instead of using default number
* library settings. Also see {@link NumberLibConfig}#Constructor.</li>
* <li>{@link NumberLibConfig} object if more complex configuration is desired.
* </li>
* </ul>
*
* @see {@link NumberLibConfig}
* @see {@link NumberLibMethods}
*
* @example // Using number library bignumber.js with Cloud Simulator
*
* const NoSQLClient = require('oracle-nosqldb').NoSQLClient;
* const BigNumber = require('bignumber.js');
*
* async function test() {
* let client;
* try {
* client = new NoSQLClient({
* endpoint: 'http://localhost:8080',
* dbNumber: 'bignumber.js'
* });
* let res = await client.tableDDL(
* 'CREATE TABLE squareRoots(num INTEGER, sqrtValue NUMBER, PRIMARY KEY(num))'
* {
* tableLimits: {
* readUnits: 100,
* writeUnits: 100,
* storageGB: 50
* },
* completion: true
* }
* );
* // insert data and verify
* for(let n = 1; n < 1000; n++) {
* res = await client.put('squareRoots', {
* num: n,
* sqrtValue: new BigNumber(n).squareRoot()
* });
* res = await client.get('foo', { num: n });
* //check that we got the row back
* assert(res.row != null);
* console.log(`Square root of ${n} is ${res.row.sqrtVal}`);
* }
* // query to get sum of square roots
* const opt = {};
* let rows = [];
* do {
* // Note that result may not always be returned on the first
* // query call
* res = await client.query(
* 'SELECT sum(sqrtValue) AS sqrtSum FROM squareRoots');
* rows = rows.concat(res.rows);
* opt.continuationKey = res.continuationKey;
* } while(res.continuationKey);
* // only one result for this query
* assert(rows.length === 1);
* const sqrtSum = rows[0].sqrtSum;
* // the sum should be instance of BigNumber type
* assert(sqrtSum instanceof BigNumber);
* console.log(`Square root sum is ${sqrtSum}`);
* } catch(err) {
* //handle errors
* } finally {
* if (client) {
* client.close();
* }
* }
* }
*
* @global
* @typedef {string|function|NumberLibConfig} DBNumberConfig
*/
/**
* This object can be set as <em>dbNumber</em> property of the {@link Config}
* to specify configuration for using 3rd party number libary when additional
* information is required, as described in {@link DBNumberConfig}.
* <p>
* The only required property is <em>Constructor</em>. As menitoned,
* <em>static</em> and <em>instance</em> properties are only needed when
* the number library is not using common method names, or if you need to
* customize implementation, see {@link NumberLibMethods}.
* <p>
* An important note about precision and rounding:
* <p>
* Different number libraries have different options to round the results
* of arithmetic and other operations. For example, some libraries like
* <em>decimal.js</em> and <em>decimal.js-light</em> round to precision, which
* is total number of significant digits in the number. These libraries
* typically have a precision value specified as configuration setting in the
* constructor. Other libraries, like <em>bignumber.js</em> and
* <em>big.js</em> round to scale, or decimal places, which is number of
* digits after decimal point. These libraries typically have decimal
* places value as configuration setting in the constructor. In addition,
* some libraries, like <em>decimal.js</em> and <em>bignumber.js</em> do
* automatic rounding of some arithmetic operations and other libraries like
* <em>big.js</em> and <em>decimal.js-light</em> allow only manual rounding
* via methods such as <em>round</em>.
* <p>
* The above may have implication on some query results. On the server side,
* NoSQL database uses Java BigDecimal and rounds each arithmetic operation
* according to precision and rounding mode settings of MathContext, as
* mentioned in {@link DBNumberConfig}. The driver, on the other hand, does
* not perform rounding during query processing, other than the rounding
* performed automatically by some number libraries as mentioned above. This
* means that for queries that use arithmetic expressions and some aggregate
* functions, the query results may slightly differ when using different
* number libraries. From the libraries tested, <em>decimal.js</em> has the
* closest matching behavior to query processing on the server side because
* it automatically rounds arithmetic operations to precision using rounding
* mode setting.
* <p>
* You may also use the rounding methods from the number library to perform
* rounding after receiving query results. This may be useful when using
* number libraries that do not perform automatic rounding. In addition,
* number libraries may have other settings related to rounding. See
* documentation for the number library of your choice. If required, you can
* clone and customize the constructor and pass it as
* {@link NumberLibConfig}#Constructor property. You may also customize
* arithmetic operations further using <em>static</em> and <em>instance</em>
* properties, see {@link NumberLibMethods}, in case non-standard behavior is
* needed.
* <p>
* The driver need to know precision and rounding mode in order to create
* MathContext for server-side query processing. It will try to infer these
* settings from the constructor as described in {@link NumberLibPrecision}
* and {@link NumberLibRoundingMode}. For libraries that round to scale,
* such as <em>bignumber.js</em> and <em>big.js</em>, it is not possible
* to infer precision from constructor, so you may need to set
* <em>precision</em> property, otherwise the driver will use the default
* value. For <em>precision</em> and <em>roundingMode</em> properties, if
* set, the driver will use their values instead of inferring them from
* the constructor.
* <p>
* Note that setting properties <em>precision</em> and <em>roundingMode</em>
* only affects how rounding is done on the server side. The driver will not
* set any properties of the constructor. If the number library does
* automatic rounding, it will do so based on the constructor as passed. If
* the number library supports precision and rounding mode settings and does
* automatic rounding/trucation, you can ensure that the same settings are
* used by the server and the client by letting the driver infer them from
* the constructor (by using <em>getPrecision</em> and
* <em>getRoundingMode</em> properties if necessary). E.g. for libraries
* <em>decimal.js</em> and <em>decimal.js-light</em> the driver will
* automatically infer and use precision and rounding mode settings.
*
* @example //A possible JSON config when using decimal.js-light, explicitly
* //specifying precision and rounding mode
* {
* "endpoint": "......",
* ......................
* "dbNumber": {
* "Constructor": "decimal.js-light",
* "precision": 10,
* "roundingMode": "ROUND_HALF_EVEN"
* }
* }
*
* @see {@link DBNumberConfig}
* @see {@link NumberLibMethods}
* @see {@link NumberLibPrecision}
* @see {@link NumberLibRoundingMode}
* @see {@link RoundingModesMap}
* @global
* @typedef {object} NumberLibConfig
* @property {string|function} Constructor String representing number library
* module name, if the sole export is the constructor, or constructor
* function. Constructor function must be able to create instances from
* number's string representation or from Javascript number (although other
* options may also be provided by the number library). Note the upper case to
* disambiguate from Object's <em>constructor</em> property
* @property {string} [module] In rare cases when the number library export
* is not the constructor, you may specify the module name. If this property
* is set and <em>Constructor</em> property is specified as a string, then the
* <em>Constructor</em> property specifies property name of the
* <em>module.exports</em> object instead of the module name
* @property {NumberLibMethods} [static] Static method mappings, that is
* methods that are properties of the constructor itself. If not set, or
* for any required method not present in the mapping, the driver will try to
* infer it from constructor. See {@link NumberLibMethods}
* @property {NumberLibMethods} [instance] Instance method mappings, that is
* methods that are properties of the constructor's prototype. If not set, or
* for any required method not present in the mapping, the driver will try
* to infer it from constructor's prototype. See {@link NumberLibMethods}
* @property {number} [precision=20] Precision to use for rounding of
* server-side query calculations on datatype <em>Number</em>. If not
* set, the driver will try to infer the value from constructor (see
* {@link NumberLibPrecision}). If cannot be inferred, default precision of
* 20 is used
* @property {*} [roundingMode='HALF_UP'] Rounding mode to use for rounding of
* server-side query calculations on datatype <em>Number</em>. See
* {@link RoundingModesMap} for details on supported roundings modes.
* This property can be specified either as rounding mode name string, such as
* 'DOWN', 'UP', 'HALF_DOWN', etc. (with or without <em>ROUND_</em> prefix) or
* as number library-specific constant value, as long as the driver can find
* a mapping between rounding mode names and their values in the number
* library (see {@link RoundingModesMap}). If not set, the driver will try
* to infer the value from constructor (see {@link NumberLibRoundingMode}).
* If cannot be inferred, default value of <em>ROUND_HALF_UP</em> is used
* @property {NumberLibPrecision} [getPrecision] Specifies how to get
* precision value from constructor. See {@link NumberLibPrecision}. If not
* set, the driver will use {@link NumberLibConfig}#precision
* @property {NumberLibRoundingMode} [getRoundingMode] Specifies how to get
* rounding mode from constructor. See {@link NumberLibRoundingMode}. If not
* set, the driver will use {@link NumberLibConfig}#roundingMode
* @property {RoundingModesMap} [RoundingModes] Specifies mapping between
* rounding mode names and their constant values in the number library. If
* not set, the driver will try to infer the mapping from constuctor or module
* (see {@link RoundingModesMap})
*/
/**
* Object that specifies method mappings for methods from the number library
* that are required by the driver. Set this as
* {@link NumberLibConfig}#static property for mappings to static methods
* (properties of the constructor) and to {@link NumberLibConfig}#instance
* property for mappings to instance methods (properties of the constructor's
* prototype). You may also set both of the above to provide static mappings
* for some methods and instance mappings for others. For any required
* method, one mapping is sufficient (otherwise the driver will prefer
* instance over static).
* <p>
* Unless otherwise specified, the required operations are binary (i.e.
* they operate on two number objects). This means that static methods take 2
* parameters and instance methods have <em>this</em> context as the 1st
* number object and pass 2nd number as one parameter.
* <p>
* You can specify each method mapping as either string or a function. If
* string, the driver will use constructor's or prototype's property by that
* name for static or instance method respectively. You may also provide a
* function for either static or instance method (as long as it follows the
* rules for arguments and <em>this</em> context described in previous
* paragraph). This allows you to customize implementation if needed.
* <p>
* <p>
* The following assumptions can be made about the argument types:
* <ul>
* <li>For static methods, you may assume that the first argument is the
* number object but second argument may be either number object, string or
* Javascript number. This should fit the signatures of static methods
* from number libraries, which typically assume that both arguments may be
* either number object, string or Javascript number.</li>
* <li>For instance methods, <em>this</em> context is the number object but
* the argument may be number object, string or Javascript number.
* Typically number libraries make the same assumption.</li>
* </ul>
* All properties are optional. For any method mapping property, if
* not set, the driver will look for methods with list of names as specified.
* Unless otherwise specified, the driver will look first for each
* instance method on that list and then for each static. If the mapping is
* not specified and none of the methods on the list could be found,
* {@link NoSQLArgumentError} will result.
* <p>
* The above means that you only need to set mappings for methods with
* non-typical names that would not be found on corresponding name list or if
* you with to customize thier implementation. In particular, for 4 tested
* number libraries mentioned in {@link DBNumberConfig}, no mappings are
* required.
*
* @example // Example of some complex mappings
* // We assume that divide and multiply methods take precision, to which
* // the result should be rounded, as an argument. We also assume unusual
* // instance method names for compare and add. These are for illustration
* // purposes only.
*
* const MyBigNumber = require('my_number_library');
* const precision = 50;
* const config = {
* endpoint: '..........',
* ........................
* dbNumber: {
* Constructor: MyBigNumber,
* precision,
* static: {
* multiply: (n1, n2) => MyBigNumber.multiply(n1, n2, precision),
* divide: (n1, n2) => MyBigNumber.divide(n1, n2, precision)
* }
* instance: {
* compare: 'compareWith',
* add: 'sumWith'
* }
* }
* }
*
* @global
* @typedef {object} NumberLibMethods
* @property {string|function} [compare] Compare two numbers n1 and n2.
* Return value of this function should be > 0 if n1 > n2, = 0 if n1 = n2 and
* < 0 if n1 < n2. If not set, the driver will look for methods named
* <em>comparedTo</em>, <em>compareTo</em>, <em>cmp</em> and
* <em>compare</em> in that order
* @property {string|function} [valuesEqual] Determine if two numbers are
* equal. Should return true/false. This property may be useful for equality
* checks during query processing which is usually faster than numeric
* comparison via {@link NumberLibMethods}#compare. If not set, the driver
* will look for instance methods <em>equals</em>, <em>isEqualTo</em> and
* <em>eq</em> in that order. If not found, the driver will use
* {@link NumberLibMethods}#compare for equality checks
* @property {string|function} [add] Add two numbers n1 and n2, return number
* object with value of n1 + n2. If not set, the driver will look for methods
* named <em>plus</em> and <em>add</em> in that order
* @property {string|function} [subtract] Subtract number n2 from n1, return
* number object with value of n1 - n2. If not set, the driver will look for
* methods named <em>minus</em>, <em>sub</em> and <em>subtract</em> in that
* order
* @property {string|function} [multiply] Multiply numbers n1 and n2, return
* number object with value n1 * n2. If not set, the driver will look for
* methods named <em>times</em>, <em>multipliedBy</em>, <em>multiply</em>
* and <em>mul</em> in that order
* @property {string|function} [divide] Divide number n1 by n2, return number
* object with value of n1 / n2. If not set, the driver will look for methods
* named <em>dividedBy</em>, <em>divide</em> and <em>div</em> in that order
*/
/**
* Value of <em>getPrecision</em> property of {@link NumberLibConfig}.
* This property may be:
* <ul>
* <li>String, specifying the property name of the constructor which contains
* precision. Nested paths (with '.'s) are allowed.</li>
* <li>Function, specifying how to get precision value from the constructor.
* The function takes constructor as sole parameter and returns precision
* value as Javascript number.</li>
* </ul>
* If {@link NumberLibConfig}#getPrecision is not set, the driver will use
* value {@link NumberLibConfig}#precision property. If that is also not set,
* the driver will check constructor properties <em>precision</em> and
* <em>PRECISION</em>. If not found, default precision of 20 is assumed.
*
* @see {@link NumberLibConfig}
* @global
* @typedef {string|function} NumberLibPrecision
*/
/**
* Value of <em>getRoundingMode</em> property of {@link NumberLibConfig}.
* <p>
* This property may be:
* <ul>
* <li>String, specifying the property name of the constructor which contains
* rounding mode. Nested paths (with '.'s) are allowed.</li>
* <li>Function, specifying how to get rounding mode value from the
* constructor. The function takes constructor as sole parameter and returns
* rounding mode value as one of the constant values used by the number
* library, which is typically numeric, but does not have to be so.</li>
* </ul>
* If {@link NumberLibConfig}#getRoundingMode is not set, the driver will use
* value {@link NumberLibConfig}#roundingMode property. If that is also not
* set, the driver will check constructor properties <em>rounding</em>,
* <em>roundingMode</em>, <em>ROUNDING_MODE</em> and <em>RM</em> in that
* order. If not found, default rounding mode ROUND_HALF_UP is assumed.
* <p>
* Also see {@link RoundingModesMap} for explanation on rounding modes.
*
* @see {@link NumberLibConfig}
* @see {@link RoundingModesMap}
* @global
* @typedef {string|function} NumberLibRoundingMode
*/
/**
* Specifies constant values used for rounding modes in the number library.
* <p>
* Rounding mode specifies rounding behavior of numerical and/or rounding
* operations in the number library, specifically indicating how the least
* significant digit of the result should be calculated. There are several
* well known rounding modes. NoSQL database supports rounding modes
* used by Java BigDecimal and specified as
* [RoundingMode enumeration]{@link https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/math/RoundingMode.html}.
* Also see documentation for the number library of your choice on supported
* rounding modes.
* <p>
* This object may be specified as {@link NumberLibConfig}#RoundingModes
* property and it helps the driver determine the rounding mode constant
* values (which are usually Javascript numbers) for different rounding modes
* used by the number library. The driver uses it to infer which rounding
* mode is used by the library or set as {@link NumberLibConfig}#roundingMode
* property, see {@link NumberLibRoundingMode}.
* <p>
* In most cases, it is not necessary to set
* {@link NumberLibConfig}#RoundingModes and thus use this object.
* Specifically, this property is not necessary if:
* <ul>
* <li>You wish to keep default rounding mode <em>ROUND_HALF_UP</em></li>
* <li>You set {@link NumberLibConfig}#roundingMode property as a name string
* of the rounding mode (such as 'ROUND_UP', 'ROUND_DOWN', etc.), see
* {@link NumberLibRoundingMode}.</li>
* <li>The driver can infer rounding mode constants from the constructor or
* its properties, see below.</li>
* </ul>
* If not set, the driver will try to infer rounding mode constants by:
* <ol>
* <li>Looking for an object containing these constants by checking properties
* <em>RoundingModes</em> and <em>RoundingMode</em> of the constructor and of
* the module (if specified). If not found assume the constructor itself
* contains the constants.</li>
* <li>Looking for properties with rounding mode names such as
* <em>ROUND_UP</em>, <em>ROUND_DOWN</em>, etc. in the object determined
* above, with and without <em>ROUND_</em> prefix.</li>
* </ol>
* <p>
* Note that you may also set {@link NumberLibConfig}#RoundingModes to string
* in which case it will be used as a property name or path ('.'s allowed) of
* constructor or module to the object containing rounding mode constants
* instead of checking candidate names as described above.
* <p>
* The properties listed correspond to each rounding mode supported
* by the driver. Note that some number libraries, such as <em>big.js</em>,
* support only a subset of these. In this case the driver will infer all it
* can. If neither {@link NumberLibConfig}#RoundingModes nor
* {@link NumberLibConfig}#roundingMode properties are set and rounding mode
* constants cannot be inferred, the driver will assume default rounding mode
* <em>ROUND_HALF_UP</em>.
* <p>
* Note that some number libraries may support rounding modes not listed here
* and not supported by Java BigDecimal, such as <em>ROUND_HALF_CEIL</em> and
* <em>ROUND_HALF_FLOOR</em>. Using of these rounding modes will result in
* {@link NoSQLArgumentError}.
* <p>
* Although numeric rounding mode values are usually used, if the library
* of your choice using other type for rounding mode constants you may specify
* them as well. Any value is allowed except <em>undefined</em> and
* <em>null</em>.
*
* @example //Using RoundingModeMap for big.js in JSON config with Cloud Simulator
* {
* "endpoint": "http://localhost:8080",
* "dbNumber": {
* "Constructor": "big.js",
* "RoundingModes": {
* "ROUND_DOWN": 0,
* "ROUND_HALF_UP": 1,
* "ROUND_HALF_EVEN": 2,
* "ROUND_UP": 3
* }
* }
* }
*
* @global
* @typedef {object|string} RoundingModesMap
* @property {*} [UP] Constant value for ROUND_UP rounding mode
* @property {*} [DOWN] Constant value for ROUND_DOWN rounding mode
* @property {*} [CEILING] Constant value for ROUND_CEILING rounding mode
* @property {*} [FLOOR] Constant value for ROUND_FLOOR rounding mode
* @property {*} [HALF_UP] Constant value for ROUND_HALF_UP rounding mode
* @property {*} [HALF_DOWN] Constant value for ROUND_HALF_DOWN rounding mode
* @property {*} [HALF_EVEN] Constant value for ROUND_HALF_EVEN rounding mode
*/