oracle-nosqldb
Version:
Node.js driver for Oracle NoSQL Database
533 lines (505 loc) • 25.9 kB
TypeScript
/*-
* Copyright (c) 2018, 2025 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/
*/
import type { Config } from "./config";
/**
* 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>{@link https://github.com/MikeMcl/decimal.js | decimal.js}</li>
* <li>{@link https://github.com/MikeMcl/decimal.js-light | decimal.js-light}</li>
* <li>{@link https://github.com/MikeMcl/bignumber.js | bignumber.js}</li>
* <li>{@link https://github.com/MikeMcl/big.js | 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>
*/
export type DBNumberConfig = string|DBNumberConstructor|NumberLibConfig;
/**
* Constructor signature for 3rd party library number type.
*/
export interface DBNumberConstructor {
new (val: string): unknown;
}
/**
* This object can be set as {@link Config#dbNumber} property
* 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 {@link Constructor}. 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 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
* {@link precision} property, otherwise the driver will use the default
* value. For {@link precision} and {@link roundingMode} properties, if
* set, the driver will use their values instead of inferring them from
* the constructor.
* <p>
* Note that setting properties {@link precision} and {@link roundingMode}
* 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 {@link getPrecision} and
* {@link getRoundingMode} 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.
*/
export interface NumberLibConfig {
/**
* 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.
*/
Constructor: string|DBNumberConstructor;
/**
* In rare cases when the number library export
* is not the constructor, you may specify the module name. If this
* property is set and {@link Constructor} property is specified as a
* string, then the {@link Constructor} property specifies property name
* of the <em>module.exports</em> object instead of the module name.
*/
module?: string;
/**
* 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}.
*/
static?: NumberLibMethods;
/**
* 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}.
*/
instance?: NumberLibMethods;
/**
* 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.
*/
precision?: number;
/**
* 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.
*/
roundingMode?: unknown;
/**
* Specifies how to get
* precision value from constructor. See {@link NumberLibPrecision}. If
* not set, the driver will use {@link NumberLibConfig#precision}.
*/
getPrecision?: NumberLibPrecision;
/**
* Specifies how to get
* rounding mode from constructor. See {@link NumberLibRoundingMode}. If
* not set, the driver will use {@link NumberLibConfig#roundingMode}.
*/
getRoundingMode?: NumberLibRoundingMode;
/**
* 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}).
*/
RoundingModes?: RoundingModesMap | string;
}
/**
* 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.
*/
export interface NumberLibMethods {
/**
* 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
*/
compare?: string|((n1: any, n2: any) => number);
/**
* 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 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 compare} for equality checks.
*/
valuesEqual?: string|((n1: any, n2: any) => boolean);
/**
* 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.
*/
add?: string|((n1: any, n2: any) => any);
/**
* 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.
*/
subtract?: string|((n1: any, n2: any) => any);
/**
* 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.
*/
multiply?: string|((n1: any, n2: any) => any);
/**
* 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.
*/
divide?: string|((n1: any, n2: any) => any);
}
/**
* Value of {@link NumberLibConfig#getPrecision} property.
* 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.
*/
export type NumberLibPrecision =
string | ((cons: DBNumberConstructor) => number);
/**
* Value of of {@link NumberLibConfig#getRoundingMode} property.
* <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.
*/
export type NumberLibRoundingMode =
string | ((cons: DBNumberConstructor) => unknown);
/**
* 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
* {@link https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/math/RoundingMode.html | RoundingMode}
* enumeration. 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>.
*/
export interface RoundingModesMap {
/**
* Constant value for ROUND_UP rounding mode.
*/
UP?: unknown;
/**
* Constant value for ROUND_DOWN rounding mode.
*/
DOWN?: unknown;
/**
* Constant value for ROUND_CEILING rounding mode.
*/
CEILING?: unknown;
/**
* Constant value for ROUND_FLOOR rounding mode.
*/
FLOOR?: unknown;
/**
* Constant value for ROUND_HALF_UP rounding mode.
*/
HALF_UP?: unknown;
/**
* Constant value for ROUND_HALF_DOWN rounding mode.
*/
HALF_DOWN?: unknown;
/**
* Constant value for ROUND_HALF_EVEN rounding mode.
*/
HALF_EVEN?: unknown;
}