diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 92026c2d..6dfc7812 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -29,12 +29,12 @@ jobs: - 20.x continue-on-error: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - run: ./scripts/build-c-client.sh - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'npm' diff --git a/.github/workflows/macOS-build.yml b/.github/workflows/macOS-build.yml index e4365da8..2df7672d 100644 --- a/.github/workflows/macOS-build.yml +++ b/.github/workflows/macOS-build.yml @@ -27,7 +27,7 @@ jobs: needs: setup runs-on: macos-latest # https://github.com/actions/virtual-environments steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - run: ./scripts/build-package.sh 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index baac19b1..c48f8ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,31 @@ # Changelog All notable changes to this project will be documented in this file. +## [5.6.0] + +* **New Features** + * [CLIENT-1803] - Added support for creation of a secondary index on elements within a Collection Data Type. + * [CLIENT-1990] - Added support for Collection Data Type (CDT) Context Base64 serialization. + * [CLIENT-2085] - Added support for rack aware queries and scans. + * [CLIENT-2347] - Added the 'replica' property to the QueryPolicy and ScanPolicy Classes. + * [CLIENT-2348] - Added filter support for secondary indices on elements within a Collection Data Type. + +* **Improvements** + * [CLIENT-1823] - Changed the example and parameters in the API Documentation for Client.batchApply. + * [CLIENT-2345] - Improved Client.indexRemove unit test by verifying deletion with a query. + * [CLIENT-2373] - Modified Query.where() to replace the current filter rather than add a filter to Query.filters. + * [CLIENT-2376] - Removed dynamic linking to OpenSSL. + +* **Updates** + * The typescript description file 'index.d.ts' has not been updated. The next release will update 'index.d.ts' and restore typescript support. + ## [5.5.0] * **Breaking Changes** * [CLIENT-2343] - Dropped support for Node.js 14 * **New Features** - * [CLIENT-2108] - Added pagination support for queries and scans. + * [CLIENT-2108] - Added pagination support for queries and scans. Requires Aerospike Server version 6.0 or above. * [CLIENT-2224] - Added support for rack aware reads when the replication factor is three. * [CLIENT-2303] - Added support for Amazon Linux 2023. * [CLIENT-2342] - Added support for Node.js 20 diff --git a/README.md b/README.md index 66c9b64a..a1effea2 100644 --- a/README.md +++ b/README.md @@ -197,43 +197,72 @@ Before starting with the Aerospike Nodejs Client, verify that you have the follo - Xcode 5 or greater. -**Openssl Library** +**OpenSSL Library** +OpenSSL is needed to build the Aerospike C Client. If downloading from NPM at version 5.6.0 or later, you will not need to +have OpenSSL installed to use the Aerospike Nodejs Client. If you are using version 5.5.0 and below, you will need to do +some additional linking to use the client, which is specified below. -The below example shows how to install the Openssl library. +We recommend using brew to install OpenSSL: ```bash brew install openssl -unlink /usr/local/opt/openssl -# Change the below linking based on openssl version and installation path -ln -s /usr/local/Cellar/openssl@x/y.z/ /usr/local/opt/openssl ``` -If you are looking upgrade switch from openssl@1 or an older version of openssl@3, add these variables to your .bashrc, .profile, or .zshrc file. +For a Mac using ARM architecture, OpenSSL should be linked as shown below: + +```bash +# When building from source before version 5.6 with openssl@3 +sudo ln -s /opt/homebrew/opt/openssl@3/ /usr/local/opt/openssl; +# link here if before version 5.6.0 +sudo ln -s /opt/homebrew/opt/openssl@3/ /usr/local/opt/openssl@3; + +# When building from source with openssl@1.1 +sudo ln -s /opt/homebrew/opt/openssl@1.1/ /usr/local/opt/openssl; +# link here if before version 5.6.0 with openssl@1.1 +sudo ln -s /opt/homebrew/opt/openssl@1.1/ /usr/local/opt/openssl@1.1; +``` + +For a Mac using X86 architecture, OpenSSL should be linked as shown below: + +```bash +# When building from source before version 5.6 with openssl@1.1 +sudo ln -s /opt/homebrew/opt/openssl@1.1/ /usr/local/opt/openssl; +# link here if before version 5.6.0 with openssl@1.1 +sudo ln -s /opt/homebrew/opt/openssl@1.1/ /usr/local/opt/openssl@1.1; +``` + +Before version 5.6.0, you may need to some add some variables to your profile for OpenSSL to be found. + ```bash export PATH="/usr/local/bin/:/usr/local/opt/openssl/bin:$PATH" export LDFLAGS="-L/usr/local/opt/openssl/lib" export CPPFLAGS="-I/usr/local/opt/openssl/include" export EXT_CFLAGS="-I/usr/local/opt/openssl/include" -``` - -Afterwards, source the file that was changed and confirm the desired openssl version is installed -```bash +# Make sure to source the changes to your shell profile source ~/.bashrc source ~/.profile source ~/.zshrc -openssl version ``` -For 4x client support, install openssl@1.1 version. + +For 4x client support, install OpenSSL@1.1 version. **LIBUV Library** -The example below shows how to install the LIBUV library. +Libuv is needed to build the Aerospike C Client. If downloading from NPM at version 5.6.0 or later, you will not need to +have Libuv installed to use the Aerospike Nodejs Client. If you are using version 5.5.0 and below, you will need to do +some additional linking to use the client, which is specified below. + +We recommend using brew to install Libuv: ```bash brew install libuv -unlink /usr/local/opt/libuv -# Change the below linking based on libuv version and installation path -ln -s /usr/local/Cellar/libuv/1.44.1_1/ /usr/local/opt/libuv +``` + +For a Mac using ARM architecture, Libuv should be linked as shown below: + +```bash +# When building from source before version 5.6 +sudo ln -s /opt/homebrew/opt/libuv/ /usr/local/opt/libuv; ``` ### Git Repository Installations diff --git a/aerospike-client-c b/aerospike-client-c index 294084c7..ad90542b 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit 294084c753f98d44317b85611915bb43362ed1ca +Subproject commit ad90542b50c36a31614f31d46207cff0d89ef963 diff --git a/binding.gyp b/binding.gyp index e94a6884..f1a6456d 100644 --- a/binding.gyp +++ b/binding.gyp @@ -151,8 +151,7 @@ ['OS=="linux"',{ 'libraries': [ '../aerospike-client-c/target/Linux-<(build_arch)/lib/libaerospike.a', - '-lz', - '-lssl' + '-lz' ], 'defines': [ 'AS_USE_LIBUV' @@ -168,8 +167,7 @@ ['OS=="mac"',{ 'libraries': [ '../aerospike-client-c/target/Darwin-<(build_arch)/lib/libaerospike.a', - '-lz', - '-lssl' + '-lz' ], 'defines': [ 'AS_USE_LIBUV' diff --git a/lib/cdt_context.js b/lib/cdt_context.js index 6a9a6ef8..eb504dde 100644 --- a/lib/cdt_context.js +++ b/lib/cdt_context.js @@ -33,6 +33,10 @@ const MAP_RANK = 0x21 const MAP_KEY = 0x22 /** @private **/ const MAP_VALUE = 0x23 +/** @private **/ +const int32Max = 2147483647 +/** @private **/ +const int32Min = -2147483648 /** * @summary Nested CDT context type. @@ -63,6 +67,11 @@ class CdtContext { * @return {CdtContext} Updated CDT context, so calls can be chained. */ addListIndex (index) { + if (int32Max < index) { + throw new Error('index overflow, largest supported integer is ' + int32Max) + } else if (int32Min > index) { + throw new Error('index underflow, smallest supported integer is ' + int32Min) + } return this.add(LIST_INDEX, index) } @@ -82,6 +91,11 @@ class CdtContext { * @return {CdtContext} Updated CDT context, so calls can be chained. */ addListIndexCreate (index, order, pad) { + if (int32Max < index) { + throw new Error('index overflow, largest supported integer is' + int32Max) + } else if (int32Min > index) { + throw new Error('index underflow, smallest supported integer is' + int32Min) + } if (order) { return this.add(LIST_INDEX | 0xc0, index) } else { @@ -106,6 +120,11 @@ class CdtContext { * @return {CdtContext} Updated CDT context, so calls can be chained. */ addListRank (rank) { + if (int32Max < rank) { + throw new Error('rank overflow, largest supported integer is' + int32Max) + } else if (int32Min > rank) { + throw new Error('rank underflow, smallest supported integer is' + int32Min) + } return this.add(LIST_RANK, rank) } @@ -135,6 +154,11 @@ class CdtContext { * @return {CdtContext} Updated CDT context, so calls can be chained. */ addMapIndex (index) { + if (int32Max < index) { + throw new Error('index overflow, largest supported integer is' + int32Max) + } else if (int32Min > index) { + throw new Error('index underflow, smallest supported integer is' + int32Min) + } return this.add(MAP_INDEX, index) } @@ -151,6 +175,11 @@ class CdtContext { * @return {CdtContext} Updated CDT context, so calls can be chained. */ addMapRank (rank) { + if (int32Max < rank) { + throw new Error('rank overflow, largest supported integer is' + int32Max) + } else if (int32Min > rank) { + throw new Error('rank underflow, smallest supported integer is' + int32Min) + } return this.add(MAP_RANK, rank) } diff --git a/lib/client.js b/lib/client.js index 8917cc47..f4962898 100644 --- a/lib/client.js +++ b/lib/client.js @@ -22,6 +22,7 @@ const EventEmitter = require('events') const as = require('bindings')('aerospike.node') const AerospikeError = require('./error') +const Context = require('./cdt_context') const Commands = require('./commands') const Config = require('./config') const EventLoop = require('./event_loop') @@ -210,6 +211,95 @@ Client.prototype.getNodes = function () { return this.as_client.getNodes() } +/** + * @function Client#contextToBase64 + * + * @summary Returns a serialized CDT Context + * + * @param {Object} context - {@link CdtContext} + * + * @return {String} serialized context - base64 representation of the CDT Context + * + * @since v5.6.0 + * + * @example How to use CDT context serialization + * + * const Aerospike = require('aerospike'); + * const Context = Aerospike.cdt.Context + * // Define host configuration + * let config = { + * hosts: '192.168.33.10:3000', + * policies: { + * operate : new Aerospike.OperatePolicy({socketTimeout : 0, totalTimeout : 0}), + * write : new Aerospike.WritePolicy({socketTimeout : 0, totalTimeout : 0}), + * read : new Aerospike.ReadPolicy({socketTimeout : 0, totalTimeout : 0}) + * } + * } + * + * + * Aerospike.connect(config, async (error, client) => { + * // Create a context + * let context = new Context().addMapKey('nested') + * + * // Create keys for records to be written + * let recordKey = new Aerospike.Key('test', 'demo', 'record') + * let contextKey = new Aerospike.Key('test', 'demo', 'context') + * + * // Put record with a CDT + * await client.put(recordKey, {exampleBin: {nested: {food: 'blueberry', drink: 'koolaid'}}}) + * + * // Test the context with client.operate() + * var ops = [ + * Aerospike.maps.getByKey('exampleBin', 'food', Aerospike.maps.returnType.KEY_VALUE).withContext(context) + * ] + * let results = await client.operate(recordKey, ops) + * console.log(results.bins.exampleBin) // [ 'food', 'blueberry' ] + * + * // Serialize CDT Context + * let serializedContext = client.contextToBase64(context) + * + * // Put record with bin containing the serialized record + * await client.put(contextKey, {context: serializedContext}) + * + * // Get context when needed for operation + * let contextRecord = await client.get(contextKey) + * + * // Deserialize CDT Context + * context = client.contextFromBase64(contextRecord.bins.context) + * + * // Test the context with client.operate() + * ops = [ + * Aerospike.maps.getByKey('exampleBin', 'food', Aerospike.maps.returnType.KEY_VALUE).withContext(context) + * ] + * results = await client.operate(recordKey, ops) + * console.log(results.bins.exampleBin) // [ 'food', 'blueberry' ] + * + * // Close the client + * client.close() + * }) + */ +Client.prototype.contextToBase64 = function (context) { + return this.as_client.contextToBase64({ context }) +} + +/** + * @function Client#contextFromBase64 + * + * @summary Returns a deserialized CDT Context + * + * @param {String} serializedContext - base64 serialized {@link CdtContext} + * + * @return {CdtContext} Deserialized CDT Context + * + * @since v5.6.0 + * + */ +Client.prototype.contextFromBase64 = function (serializedContext) { + const context = new Context() + context.items = this.as_client.contextFromBase64({ context: serializedContext }) + return context +} + /** * @function Client#addSeedHost * @@ -579,9 +669,7 @@ Client.prototype.batchWrite = function (records, policy, callback) { * This method allows multiple sub-commands for each key in the batch. * This method requires server >= 6.0.0. * - * @param {object[]} records - {@link Record} List of batch sub-commands to perform. - * @param {number} records[].type - {@link Record#type} Batch type. - * @param {Key} records[].key - Record Key. + * @param {Key[]} keys - An array of keys, used to locate the records in the cluster. * @param {object[]} udf - Server UDF module/function and argList to apply. * @param {BatchPolicy} [batchPolicy] - The Batch Policy to use for this operation. * @param {BatchApplyPolicy} [batchApplyPolicy] UDF policy configuration parameters. @@ -595,6 +683,8 @@ Client.prototype.batchWrite = function (records, policy, callback) { * * @example * const Aerospike = require('aerospike') + * const batchType = Aerospike.batchType; + * * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE! * const config = { * hosts: '192.168.33.10:3000', @@ -604,52 +694,34 @@ Client.prototype.batchWrite = function (records, policy, callback) { * } * } * - * const batchType = Aerospike.batchType - * var batchRecords = [ - * { type: batchType.BATCH_READ, - * key: new Aerospike.Key('test', 'demo', 'key1'), - * bins: ['i', 's'] }, - * { type: batchType.BATCH_READ, - * key: new Aerospike.Key('test', 'demo', 'key2'), - * readAllBins: true }, - * { type: batchType.BATCH_APPLY, - * key: new Aerospike.Key('test', 'demo', 'key4'), - * policy: new Aerospike.BatchApplyPolicy({ - * filterExpression: exp.eq(exp.binInt('i'), exp.int(37)), - * key: Aerospike.policy.key.SEND, - * commitLevel: Aerospike.policy.commitLevel.ALL, - * durableDelete: true - * }), - * udf: { - * module: 'udf', - * funcname: 'function1', - * args: [[1, 2, 3]] - * } - * }, - * { type: batchType.BATCH_APPLY, - * key: new Aerospike.Key('test', 'demo', 'key5'), - * policy: new Aerospike.BatchApplyPolicy({ - * filterExpression: exp.eq(exp.binInt('i'), exp.int(37)), - * key: Aerospike.policy.key.SEND, - * commitLevel: Aerospike.policy.commitLevel.ALL, - * durableDelete: true - * }), - * udf: { - * module: 'udf', - * funcname: 'function2', - * args: [[1, 2, 3]] - * } - * } - * ] - * Aerospike.connect(config, (error, client) => { - * if (error) throw error - * client.batchApply(batchRecords, (error, results) => { - * if (error) throw error - * results.forEach(function (result) { - * console.log(result) - * }) - * }) - * }) + * // Create batch of keys + * let keys = []; + * for(i = 0; i < 10; i++){ + * keys.push(new Aerospike.Key('sandbox', 'ufodata', i + 1)); + * } + * + * ;(async () => { + * // Establishes a connection to the server + * let client = await Aerospike.connect(config); + * + * // Execute the UDF + * let batchResult = await client.batchApply(batchRecords, + * { + * module: 'example', + * funcname: 'getDaysBetween', + * args: ['occurred', 'posted'] + * } + * ); + * + * // Access the records + * batchResult.forEach(result => { + * // Do something + * console.info("%o days between occurrence and post", result.record.bins.SUCCESS); + * }); + * + * // Close the connection to the server + * client.close(); + * })(); */ Client.prototype.batchApply = function (records, udf, batchPolicy, batchApplyPolicy, callback) { if (typeof batchPolicy === 'function') { @@ -993,6 +1065,7 @@ Client.prototype.connect = function (callback) { * individual entries of the list or keys/values of the map should be indexed. * @param {module:aerospike.indexDataType} options.datatype - The data type of * the index to be created, e.g. Numeric, String or Geo. + * @param {Object} options.context - The {@link CdtContext} on which the index is to be created. * @param {InfoPolicy} [policy] - The Info Policy to use for this operation. * @param {jobCallback} [callback] - The function to call when the operation completes. * @@ -1008,6 +1081,7 @@ Client.prototype.connect = function (callback) { * @example * * const Aerospike = require('aerospike') + * const Context = Aerospike.cdt.Context * * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE! * var config = { @@ -1024,12 +1098,15 @@ Client.prototype.connect = function (callback) { * let indexName = 'recentLocationsIdx' * let indexType = Aerospike.indexType.LIST * let dataType = Aerospike.indexDataType.GEO2DSPHERE + * let context = new Context().addListIndex(0) * let options = { ns: namespace, * set: set, * bin: binName, * index: indexName, * type: indexType, - * datatype: dataType } + * datatype: dataType, + * context: context } + * * let policy = new Aerospike.InfoPolicy({ timeout: 100 }) * * client.createIndex(options, policy, (error, job) => { @@ -1058,6 +1135,7 @@ Client.prototype.createIndex = function (options, policy, callback) { options.index, options.type || as.indexType.DEFAULT, options.datatype, + { context: options.context }, policy ] diff --git a/lib/filter.js b/lib/filter.js index 9152ca94..0c820786 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -30,10 +30,11 @@ const GeoJSON = require('./geojson') * @example * * const Aerospike = require('aerospike') + * const Context = Aerospike.cdt.Context * * Aerospike.connect().then(async (client) => { * // find any records that have a recent location within 1000m radius of the specified coordinates - * let geoFilter = Aerospike.filter.geoWithinRadius('recent', 103.8, 1.305, 1000, Aerospike.indexType.LIST) + * let geoFilter = Aerospike.filter.geoWithinRadius('recent', 103.8, 1.305, 1000, Aerospike.indexType.LIST, new Context().addListIndex(0)) * let query = client.query('test', 'demo') * query.where(geoFilter) * @@ -53,11 +54,12 @@ const GeoJSON = require('./geojson') * module:aerospike/filter} module. */ class SindexFilterPredicate { - constructor (predicate, bin, dataType, indexType, props) { + constructor (predicate, bin, dataType, indexType, context, props) { this.predicate = predicate this.bin = bin this.datatype = dataType this.type = indexType || as.indexType.DEFAULT + this.context = context if (props) { Object.assign(this, props) } @@ -66,16 +68,16 @@ class SindexFilterPredicate { exports.SindexFilterPredicate = SindexFilterPredicate class EqualPredicate extends SindexFilterPredicate { - constructor (bin, value, dataType, indexType) { - super(as.predicates.EQUAL, bin, dataType, indexType, { + constructor (bin, value, dataType, indexType, context) { + super(as.predicates.EQUAL, bin, dataType, indexType, context, { val: value }) } } class RangePredicate extends SindexFilterPredicate { - constructor (bin, min, max, dataType, indexType) { - super(as.predicates.RANGE, bin, dataType, indexType, { + constructor (bin, min, max, dataType, indexType, context) { + super(as.predicates.RANGE, bin, dataType, indexType, context, { min, max }) @@ -83,8 +85,8 @@ class RangePredicate extends SindexFilterPredicate { } class GeoPredicate extends SindexFilterPredicate { - constructor (bin, value, indexType) { - super(as.predicates.RANGE, bin, as.indexDataType.GEO2DSPHERE, indexType, { + constructor (bin, value, indexType, context) { + super(as.predicates.RANGE, bin, as.indexDataType.GEO2DSPHERE, indexType, context, { val: value }) } @@ -124,12 +126,13 @@ function dataTypeOf (value) { * @param {number} max - Upper end of the range (inclusive). * @param {number} [indexType=Aerospike.indexType.DEFAULT] - One of {@link * module:aerospike.indexType}, i.e. LIST or MAPVALUES. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. */ -exports.range = function (bin, min, max, indexType) { +exports.range = function (bin, min, max, indexType, context) { const dataType = as.indexDataType.NUMERIC - return new RangePredicate(bin, min, max, dataType, indexType) + return new RangePredicate(bin, min, max, dataType, indexType, context) } /** @@ -157,14 +160,15 @@ exports.equal = function (bin, value) { * list or map in the bin. * @param {number} indexType - One of {@link module:aerospike.indexType}, * i.e. LIST, MAPVALUES or MAPKEYS. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. * * @since v2.0 */ -exports.contains = function (bin, value, indexType) { +exports.contains = function (bin, value, indexType, context) { const dataType = dataTypeOf(value) - return new EqualPredicate(bin, value, dataType, indexType) + return new EqualPredicate(bin, value, dataType, indexType, context) } /** @@ -178,18 +182,19 @@ exports.contains = function (bin, value, indexType) { * @param {GeoJSON} value - GeoJSON region value. * @param {number} [indexType=Aerospike.indexType.DEFAULT] - One of {@link * module:aerospike.indexType}, i.e. LIST or MAPVALUES. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. * * @since v2.0 */ -exports.geoWithinGeoJSONRegion = function (bin, value, indexType) { +exports.geoWithinGeoJSONRegion = function (bin, value, indexType, context) { if (value instanceof GeoJSON) { value = value.toString() } else if (typeof value === 'object') { value = JSON.stringify(value) } - return new GeoPredicate(bin, value, indexType) + return new GeoPredicate(bin, value, indexType, context) } /** @@ -203,18 +208,19 @@ exports.geoWithinGeoJSONRegion = function (bin, value, indexType) { * @param {GeoJSON} value - GeoJSON point value. * @param {number} [indexType=Aerospike.indexType.DEFAULT] - One of {@link * module:aerospike.indexType}, i.e. LIST or MAPVALUES. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. * * @since v2.0 */ -exports.geoContainsGeoJSONPoint = function (bin, value, indexType) { +exports.geoContainsGeoJSONPoint = function (bin, value, indexType, context) { if (value instanceof GeoJSON) { value = value.toString() } else if (typeof value === 'object') { value = JSON.stringify(value) } - return new GeoPredicate(bin, value, indexType) + return new GeoPredicate(bin, value, indexType, context) } /** @@ -230,14 +236,15 @@ exports.geoContainsGeoJSONPoint = function (bin, value, indexType) { * @param {number} radius - Radius in meters. * @param {number} [indexType=Aerospike.indexType.DEFAULT] - One of {@link * module:aerospike.indexType}, i.e. LIST or MAPVALUES. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. * * @since v2.0 */ -exports.geoWithinRadius = function (bin, lon, lat, radius, indexType) { +exports.geoWithinRadius = function (bin, lon, lat, radius, indexType, context) { const circle = GeoJSON.Circle(lon, lat, radius) - return new GeoPredicate(bin, circle.toString(), indexType) + return new GeoPredicate(bin, circle.toString(), indexType, context) } /** @@ -252,12 +259,13 @@ exports.geoWithinRadius = function (bin, lon, lat, radius, indexType) { * @param {number} lat - Latitude of the point. * @param {number} [indexType=Aerospike.indexType.DEFAULT] - One of {@link * module:aerospike.indexType}, i.e. LIST or MAPVALUES. + * @param {Object} context - The {@link CdtContext} of the index. * @returns {module:aerospike/filter~SindexFilterPredicate} SI * filter predicate, that can be applied to queries using {@link Query#where}. * * @since v2.0 */ -exports.geoContainsPoint = function (bin, lon, lat, indexType) { +exports.geoContainsPoint = function (bin, lon, lat, indexType, context) { const point = GeoJSON.Point(lon, lat) - return new GeoPredicate(bin, point.toString(), indexType) + return new GeoPredicate(bin, point.toString(), indexType, context) } diff --git a/lib/policies/query_policy.js b/lib/policies/query_policy.js index 1e6f0d86..bc9da5a8 100644 --- a/lib/policies/query_policy.js +++ b/lib/policies/query_policy.js @@ -34,6 +34,14 @@ class QueryPolicy extends BasePolicy { props = props || {} super(props) + /** + * Specifies the replica to be consulted for the query operation. + * + * @type number + * @see {@link module:aerospike/policy.replica} for supported policy values. + */ + this.replica = props.replica + /** * Should CDT data types (Lists / Maps) be deserialized to JS data types * (Arrays / Objects) or returned as raw bytes (Buffer). diff --git a/lib/policies/scan_policy.js b/lib/policies/scan_policy.js index ee8f03bf..7488f3c4 100644 --- a/lib/policies/scan_policy.js +++ b/lib/policies/scan_policy.js @@ -34,6 +34,14 @@ class ScanPolicy extends BasePolicy { props = props || {} super(props) + /** + * Specifies the replica to be consulted for the scan operation. + * + * @type number + * @see {@link module:aerospike/policy.replica} for supported policy values. + */ + this.replica = props.replica + /** * Specifies whether a {@link * http://www.aerospike.com/docs/guide/durable_deletes.html|tombstone} diff --git a/lib/query.js b/lib/query.js index e8b047a2..2344cf19 100644 --- a/lib/query.js +++ b/lib/query.js @@ -473,20 +473,6 @@ Query.prototype.select = function (bins) { * }) * * @see {@link module:aerospike/filter} to create SI filters. - * - * const Aerospike = require('aerospike') - * - * Aerospike.connect().then(client => { - * let query = client.query('test', 'demo') - * - * let tagsFilter = Aerospike.filter.contains('tags', 'blue', Aerospike.indexType.LIST) - * query.where(tagsFilter) - * - * let stream = query.foreach() - * stream.on('data', record => { console.info(record.bins.tags) }) - * stream.on('error', error => { throw error }) - * stream.on('end', () => client.close()) - * }) */ Query.prototype.where = function (indexFilter) { if (indexFilter instanceof filter.SindexFilterPredicate) { @@ -497,7 +483,7 @@ Query.prototype.where = function (indexFilter) { } Query.prototype.setSindexFilter = function (sindexFilter) { - this.filters = this.filters || [] + this.filters = [] this.filters.push(sindexFilter) } @@ -586,6 +572,7 @@ Query.prototype.foreach = function (policy, dataCb, errorCb, endCb) { if (this.paginate) { args.push(this.queryState) args.push(this.maxRecords) + args.push(this.filters[0].context ? { context: this.filters[0].context } : null) cmd = new Commands.QueryPages(stream, args) } else { cmd = new Commands.Query(stream, args) diff --git a/package-lock.json b/package-lock.json index 7a47961b..84789e21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aerospike", - "version": "5.5.0", + "version": "5.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aerospike", - "version": "5.5.0", + "version": "5.6.0", "cpu": [ "x64", "arm64" @@ -60,42 +60,42 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.9.tgz", - "integrity": "sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", - "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-compilation-targets": "^7.21.5", - "@babel/helper-module-transforms": "^7.21.5", - "@babel/helpers": "^7.21.5", - "@babel/parser": "^7.21.8", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -120,12 +120,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz", - "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -135,13 +135,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", - "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.21.5", - "@babel/helper-validator-option": "^7.21.0", + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -178,142 +178,142 @@ "dev": true }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", - "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-module-imports": "^7.21.4", - "@babel/helper-simple-access": "^7.21.5", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", - "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", - "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -322,9 +322,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz", - "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -334,33 +334,33 @@ } }, "node_modules/@babel/template": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", - "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/parser": "^7.21.9", - "@babel/types": "^7.21.5" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -369,13 +369,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -943,9 +943,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "funding": [ { @@ -955,13 +955,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -1045,9 +1049,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001489", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", - "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", + "version": "1.0.30001506", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001506.tgz", + "integrity": "sha512-6XNEcpygZMCKaufIcgpQNZNf00GEqc7VQON+9Rd0K1bMYo8xhMZRAo5zpbnbMNizi4YNgIDAFrdykWsvY3H4Hw==", "dev": true, "funding": [ { @@ -1377,9 +1381,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.405", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.405.tgz", - "integrity": "sha512-JdDgnwU69FMZURoesf9gNOej2Cms1XJFfLk24y1IBtnAdhTcJY/mXnokmpmxHN59PcykBP4bgUU98vLY44Lhuw==", + "version": "1.4.436", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.436.tgz", + "integrity": "sha512-aktOxo8fnrMC8vOIBMVS3PXbT1nrPQ+SouUuN7Y0a+Rw3pOMrvIV92Ybnax7x4tugA+ZpYA5fOHTby7ama8OQQ==", "dev": true }, "node_modules/emoji-regex": { @@ -5191,9 +5195,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 7efe612c..2f3a2cb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aerospike", - "version": "5.5.0", + "version": "5.6.0", "description": "Aerospike Client Library", "keywords": [ "aerospike", @@ -40,7 +40,6 @@ "scripts": { "preinstall": "npm install @mapbox/node-pre-gyp", "install": "node-pre-gyp install --fallback-to-build", - "postinstall": "node scripts/postinstall.js", "test": "mocha", "test-noserver": "GLOBAL_CLIENT=false mocha -g '#noserver'", "lint": "standard", @@ -81,7 +80,8 @@ "ignore": [ "apidocs", "tmp-*.js", - "/*.js" + "/*.js", + "libuv-v*" ] }, "files": [ diff --git a/scripts/build-c-client.sh b/scripts/build-c-client.sh index e85a04f5..48dbbd8c 100755 --- a/scripts/build-c-client.sh +++ b/scripts/build-c-client.sh @@ -25,15 +25,11 @@ CWD=$(pwd) SCRIPT_DIR=$(dirname $0) BASE_DIR=$(cd "${SCRIPT_DIR}/.."; pwd) -SETUP=$1 . ${SCRIPT_DIR}/build-commands.sh -if [ -n "$1" ]; then setup -fi download_libuv rebuild_libuv -check_libuv rebuild_c_client diff --git a/scripts/build-commands.sh b/scripts/build-commands.sh index 5ca06a8b..adf39dd2 100755 --- a/scripts/build-commands.sh +++ b/scripts/build-commands.sh @@ -28,10 +28,10 @@ BASE_DIR=$(cd "${SCRIPT_DIR}/.."; pwd) AEROSPIKE_C_HOME=${CWD}/aerospike-client-c OS_FLAVOR=linux AEROSPIKE_NODEJS_RELEASE_HOME=${CWD}/lib/binding -LIBUV_VERSION=1.8.0 +LIBUV_VERSION=1.45.0 LIBUV_DIR=libuv-v${LIBUV_VERSION} LIBUV_TAR=${LIBUV_DIR}.tar.gz -LIBUV_URL=http://dist.libuv.org/dist/v1.8.0/${LIBUV_TAR} +LIBUV_URL=http://dist.libuv.org/dist/v1.45.0/${LIBUV_TAR} LIBUV_ABS_DIR=${CWD}/${LIBUV_DIR} LIBUV_BUILD=0 build_arch=$(uname -m) @@ -113,35 +113,6 @@ rebuild_libuv() { fi } -check_libuv() { - - cd ${CWD} - - printf "\n" >&1 - - if [ $LIBUV_BUILD -eq 1 ]; then - if [ -f ${LIBUV_LIBRARY} ]; then - printf " [✓] %s\n" "${LIBUV_LIBRARY}" >&1 - else - printf " [✗] %s\n" "${LIBUV_LIBRARY}" >&1 - FAILED=1 - fi - fi - - if [ -f ${LIBUV_INCLUDE_DIR}/uv.h ]; then - printf " [✓] %s\n" "${LIBUV_INCLUDE_DIR}/uv.h" >&1 - else - printf " [✗] %s\n" "${LIBUV_INCLUDE_DIR}/uv.h" >&1 - FAILED=1 - fi - - printf "\n" >&1 - - if [ $FAILED ]; then - exit 1 - fi -} - rebuild_c_client() { # if [ ! -f ${AEROSPIKE_LIBRARY} ]; then cd ${AEROSPIKE_C_HOME} @@ -151,29 +122,6 @@ rebuild_c_client() { # fi } -setup() { - if [[ "$OSTYPE" == "darwin"* ]]; then - # # install xcode CLI - # xcode-select —-install - # Check for Homebrew to be present, install if it's missing - if test ! $(which brew); then - echo "Installing homebrew..." - ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - fi - brew update - PACKAGES=( - openssl - ) - echo "Installing packages..." - brew install ${PACKAGES[@]} - # link openssl - unlink /usr/local/opt/openssl - ln -s /usr/local/Cellar/openssl@3/*/ /usr/local/opt/openssl - export LDFLAGS="-L/usr/local/opt/openssl@3/lib" - export CPPFLAGS="-I/usr/local/opt/openssl@3/include" - fi -} - check_aerospike() { cd ${CWD} @@ -208,6 +156,5 @@ perform_check() { printf "\n" >&1 printf "CHECK\n" >&1 - check_libuv check_aerospike } diff --git a/scripts/build-package.sh b/scripts/build-package.sh index b4e02ee6..1795ea88 100755 --- a/scripts/build-package.sh +++ b/scripts/build-package.sh @@ -44,11 +44,8 @@ build_nodejs_client() { configure_nvm -if [ -n "$1" ]; then setup -fi download_libuv rebuild_libuv -check_libuv rebuild_c_client diff --git a/scripts/postinstall.js b/scripts/postinstall.js deleted file mode 100644 index 1489e3d5..00000000 --- a/scripts/postinstall.js +++ /dev/null @@ -1,12 +0,0 @@ -const fs = require('fs') - -function createDir (folder) { - if (!fs.existsSync(folder)) { - fs.mkdirSync(folder, { recursive: true }) - - console.log('Folder Created Successfully: ' + folder) - } -} - -createDir('./lib/binding/openssl@1') -createDir('./lib/binding/openssl@3') diff --git a/scripts/prebuiltBinding.js b/scripts/prebuiltBinding.js old mode 100755 new mode 100644 index 3a1e179f..5bd14e3b --- a/scripts/prebuiltBinding.js +++ b/scripts/prebuiltBinding.js @@ -1,73 +1,73 @@ -const { exec } = require('child_process') const fs = require('fs') -exec('openssl version', (error, stdout, stderr) => { - if (error) throw error - exec('uname -s', (error, OS, stderr) => { - if (error) throw error - exec('uname -m', (error, arch, stderr) => { - if (error) throw error - const openssl = stdout.split(' ') - OS = OS.trim() - arch = arch.trim() - const dict = { - v19: 'v111', - v18: 'v108', - v16: 'v93', - v14: 'v83', - Linux: 'linux', - Darwin: 'darwin', - arm64: 'arm64', - x86_64: 'x64', - aarch64: 'arm64' - } - if ((openssl[1][0] === '3') && (openssl[0] !== 'LibreSSL')) { - console.log('lib/binding/openssl@3/node-v111-' + dict[OS] + '-' + dict[arch]) - fs.rename('lib/binding/openssl@3/node-v111-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v111-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@3/node-v108-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v108-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@3/node-v93-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v93-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@3/node-v115-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v115-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) return - fs.rm('lib/binding/openssl@3', { recursive: true, force: true }, (e) => { - if (e) throw e - }) - fs.rm('lib/binding/openssl@1', { recursive: true, force: true }, (e) => { - if (e) throw e - }) - }) - }) - }) - }) - } else { - fs.rename('lib/binding/openssl@1/node-v111-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v111-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@1/node-v108-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v108-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@1/node-v93-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v93-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) throw err - fs.rename('lib/binding/openssl@1/node-v115-' + dict[OS] + '-' + dict[arch], - 'lib/binding/node-v115-' + dict[OS] + '-' + dict[arch], (err) => { - if (err) return - fs.rm('lib/binding/openssl@3', { recursive: true, force: true }, (e) => { - if (e) throw e - }) - fs.rm('lib/binding/openssl@1', { recursive: true, force: true }, (e) => { - if (e) throw e - }) - }) - }) - }) - }) - } - }) - }) -}) +const util = require('util') +const exec = util.promisify(require('child_process').exec) +const rename = util.promisify(fs.rename) +const rm = util.promisify(fs.rm) +const dict = { + Linux: 'linux', + Darwin: 'darwin', + arm64: 'arm64', + x86_64: 'x64', + aarch64: 'arm64' +} + +;(async function () { + let output = await exec('uname -s') + const os = output.stdout.trim() + output = await exec('uname -m') + const arch = output.stdout.trim() + output = await exec('openssl version') + const version = output.stdout + const openssl = version.split(' ')[1].slice(0, 1) + const deleteList = [] + if (dict[os] === 'linux') { + deleteList.push('lib/binding/node-v115-darwin-arm64') + deleteList.push('lib/binding/node-v111-darwin-arm64') + deleteList.push('lib/binding/node-v108-darwin-arm64') + deleteList.push('lib/binding/node-v93-darwin-arm64') + deleteList.push('lib/binding/node-v115-darwin-x64') + deleteList.push('lib/binding/node-v111-darwin-x64') + deleteList.push('lib/binding/node-v108-darwin-x64') + deleteList.push('lib/binding/node-v93-darwin-x64') + + if (dict[arch] === 'arm64') { + await rename('lib/binding/openssl@' + openssl + '/node-v115-linux-arm64', 'lib/binding/node-v115-linux-arm64') + await rename('lib/binding/openssl@' + openssl + '/node-v111-linux-arm64', 'lib/binding/node-v111-linux-arm64') + await rename('lib/binding/openssl@' + openssl + '/node-v108-linux-arm64', 'lib/binding/node-v108-linux-arm64') + await rename('lib/binding/openssl@' + openssl + '/node-v93-linux-arm64', 'lib/binding/node-v93-linux-arm64') + } else { + await rename('lib/binding/openssl@' + openssl + '/node-v115-linux-x64', 'lib/binding/node-v115-linux-x64') + await rename('lib/binding/openssl@' + openssl + '/node-v111-linux-x64', 'lib/binding/node-v111-linux-x64') + await rename('lib/binding/openssl@' + openssl + '/node-v108-linux-x64', 'lib/binding/node-v108-linux-x64') + await rename('lib/binding/openssl@' + openssl + '/node-v93-linux-x64', 'lib/binding/node-v93-linux-x64') + } + await rm('lib/binding/openssl@3', { recursive: true, force: true }) + await rm('lib/binding/openssl@1', { recursive: true, force: true }) + await rm(deleteList[0], { recursive: true, force: true }) + await rm(deleteList[1], { recursive: true, force: true }) + await rm(deleteList[2], { recursive: true, force: true }) + await rm(deleteList[3], { recursive: true, force: true }) + await rm(deleteList[4], { recursive: true, force: true }) + await rm(deleteList[5], { recursive: true, force: true }) + await rm(deleteList[6], { recursive: true, force: true }) + await rm(deleteList[7], { recursive: true, force: true }) + } else { + if (dict[arch] === 'arm64') { + deleteList.push('lib/binding/node-v115-darwin-x64') + deleteList.push('lib/binding/node-v111-darwin-x64') + deleteList.push('lib/binding/node-v108-darwin-x64') + deleteList.push('lib/binding/node-v93-darwin-x64') + } else { + deleteList.push('lib/binding/node-v115-darwin-arm64') + deleteList.push('lib/binding/node-v111-darwin-arm64') + deleteList.push('lib/binding/node-v108-darwin-arm64') + deleteList.push('lib/binding/node-v93-darwin-arm64') + } + await rm('lib/binding/openssl@3', { recursive: true, force: true }) + await rm('lib/binding/openssl@1', { recursive: true, force: true }) + await rm(deleteList[0], { recursive: true, force: true }) + await rm(deleteList[1], { recursive: true, force: true }) + await rm(deleteList[2], { recursive: true, force: true }) + await rm(deleteList[3], { recursive: true, force: true }) + } +})() diff --git a/src/include/client.h b/src/include/client.h index de89af4a..6fdb82e9 100644 --- a/src/include/client.h +++ b/src/include/client.h @@ -83,6 +83,8 @@ class AerospikeClient : public Nan::ObjectWrap { static NAN_METHOD(BatchApply); static NAN_METHOD(BatchRemove); static NAN_METHOD(BatchSelect); + static NAN_METHOD(ContextFromBase64); + static NAN_METHOD(ContextToBase64); static NAN_METHOD(Close); static NAN_METHOD(Connect); static NAN_METHOD(ExistsAsync); diff --git a/src/include/operations.h b/src/include/operations.h index 1d41eafa..4bccec84 100644 --- a/src/include/operations.h +++ b/src/include/operations.h @@ -44,6 +44,7 @@ int get_optional_cdt_context(as_cdt_ctx *context, bool *has_context, as_cdt_ctx* get_optional_cdt_context_heap(int * rc, v8::Local obj, const char *prop, const LogInfo *log); +int get_v8_cdt_context(as_cdt_ctx *context, v8::Local items); v8::Local scalar_opcode_values(); v8::Local list_opcode_values(); diff --git a/src/include/query.h b/src/include/query.h index d715bce8..f097a4fa 100644 --- a/src/include/query.h +++ b/src/include/query.h @@ -34,8 +34,9 @@ struct query_udata { void setup_query(as_query *query, v8::Local ns, v8::Local set, v8::Local maybe_options, - LogInfo *log); -void setup_options(as_query *query, v8::Local options, LogInfo *log); + as_cdt_ctx* context, bool* with_context, LogInfo *log); +void setup_options(as_query *query, v8::Local options, as_cdt_ctx* context, bool* with_context, LogInfo *log); void setup_query_pages(as_query ** query, v8::Local ns, v8::Local set, - v8::Localmaybe_options, uint8_t* bytes, uint32_t bytes_size, LogInfo *log); + v8::Localmaybe_options, uint8_t* bytes, uint32_t bytes_size, + as_cdt_ctx* context, bool* with_context, LogInfo *log); void free_query(as_query *query, as_policy_query *policy); diff --git a/src/main/async.cc b/src/main/async.cc index 41419e33..1c449256 100644 --- a/src/main/async.cc +++ b/src/main/async.cc @@ -195,7 +195,7 @@ bool async_scan_pages_listener(as_error *err, as_record *record, void *udata, query_bytes_to_jsobject(bytes, bytes_size, log), Nan::Null()}; - cmd->Callback(4, {argv}); + cmd->Callback(4, argv); as_scan_destroy(scan); free(bytes); delete cmd; @@ -253,7 +253,7 @@ bool async_query_pages_listener(as_error *err, as_record *record, void *udata, query_bytes_to_jsobject(bytes, bytes_size, log), Nan::Null()}; - cmd->Callback(4, {argv}); + cmd->Callback(4, argv); free_query(query, NULL); free(bytes); delete cmd; diff --git a/src/main/cdt_ctx.cc b/src/main/cdt_ctx.cc index 23811f61..3d4657af 100644 --- a/src/main/cdt_ctx.cc +++ b/src/main/cdt_ctx.cc @@ -26,6 +26,83 @@ extern "C" { using namespace v8; +NAN_METHOD(AerospikeClient::ContextToBase64) +{ + TYPE_CHECK_REQ(info[0], IsObject, "Context must be an object"); + + as_cdt_ctx context; + bool has_context = false; + + if (info[0]->IsObject()) { + get_optional_cdt_context(&context, &has_context, info[0].As(), "context", NULL); + } + + if(has_context){ + uint32_t capacity = as_cdt_ctx_base64_capacity(&context); + char* serializedContext = new char[capacity]; + as_cdt_ctx_to_base64(&context, serializedContext, capacity);; + as_cdt_ctx_destroy(&context); + info.GetReturnValue().Set(Nan::New(serializedContext).ToLocalChecked()); + delete [] serializedContext; + } + else{ + Nan::ThrowError("Context is invalid, cannot serialize"); + } + +} + +NAN_METHOD(AerospikeClient::ContextFromBase64) +{ + TYPE_CHECK_REQ(info[0], IsObject, "Serialized context must be an object"); + + char* serializedContext = NULL; + if (info[0]->IsObject()) { + if(get_string_property(&serializedContext, info[0].As(), "context", NULL) != AS_NODE_PARAM_OK){ + Nan::ThrowError("Serialized context is invalid"); + return; + } + } + + as_cdt_ctx context; + as_cdt_ctx_from_base64(&context, serializedContext); + Local v8_items = Nan::New(context.list.size); + get_v8_cdt_context(&context, v8_items); + cf_free(serializedContext); + as_cdt_ctx_destroy(&context); + info.GetReturnValue().Set(v8_items); + +} + +int get_v8_cdt_context(as_cdt_ctx *context, Local items) +{ + Nan::HandleScope scope; + for(uint32_t i = 0; i < context->list.size; i++){ + + as_cdt_ctx_item* item = (as_cdt_ctx_item*) as_vector_get(&context->list, i); + Local v8Item = Nan::New(2); + + if((item->type & 0xF) > 0x1){ + Nan::Set(v8Item, 0, Nan::New(item->type)); + Nan::Set(v8Item, 1, val_to_jsvalue(item->val.pval, NULL)); + + Nan::Set(items, i, v8Item); + } + else{ + Nan::Set(v8Item, 0, Nan::New(item->type)); + //First 31 bits mask + int32_t ival = item->val.ival & 0x7FFFFFFF; + //Signed bit mask + if(item->val.ival & 0x8000000000000000){ + ival = ival | 0x80000000; + } + Nan::Set(v8Item, 1, Nan::New(ival)); + + Nan::Set(items, i, v8Item); + } + } + return AS_NODE_PARAM_OK; +} + int get_optional_cdt_context(as_cdt_ctx *context, bool *has_context, Local obj, const char *prop, const LogInfo *log) diff --git a/src/main/client.cc b/src/main/client.cc index b382e805..3dee670b 100644 --- a/src/main/client.cc +++ b/src/main/client.cc @@ -289,6 +289,8 @@ void AerospikeClient::Init() Nan::SetPrototypeMethod(tpl, "batchApply", BatchApply); Nan::SetPrototypeMethod(tpl, "batchRemove", BatchRemove); Nan::SetPrototypeMethod(tpl, "batchSelect", BatchSelect); + Nan::SetPrototypeMethod(tpl, "contextFromBase64", ContextFromBase64); + Nan::SetPrototypeMethod(tpl, "contextToBase64", ContextToBase64); Nan::SetPrototypeMethod(tpl, "close", Close); Nan::SetPrototypeMethod(tpl, "connect", Connect); Nan::SetPrototypeMethod(tpl, "existsAsync", ExistsAsync); diff --git a/src/main/commands/index_create.cc b/src/main/commands/index_create.cc index cfa5dfd4..9f0d0987 100644 --- a/src/main/commands/index_create.cc +++ b/src/main/commands/index_create.cc @@ -20,6 +20,7 @@ #include "conversions.h" #include "policy.h" #include "log.h" +#include "operations.h" extern "C" { #include @@ -42,6 +43,8 @@ class IndexCreateCommand : public AerospikeCommand { cf_free(policy); if (index != NULL) free(index); + if (with_context) + as_cdt_ctx_destroy(&context); } as_index_task task; @@ -52,6 +55,8 @@ class IndexCreateCommand : public AerospikeCommand { char *index = NULL; as_index_type itype; as_index_datatype dtype; + as_cdt_ctx context; + bool with_context; }; static void *prepare(const Nan::FunctionCallbackInfo &info) @@ -60,7 +65,7 @@ static void *prepare(const Nan::FunctionCallbackInfo &info) AerospikeClient *client = Nan::ObjectWrap::Unwrap(info.This()); IndexCreateCommand *cmd = - new IndexCreateCommand(client, info[7].As()); + new IndexCreateCommand(client, info[8].As()); LogInfo *log = client->log; if (as_strlcpy(cmd->ns, *Nan::Utf8String(info[0].As()), @@ -90,8 +95,16 @@ static void *prepare(const Nan::FunctionCallbackInfo &info) cmd->dtype = (as_index_datatype)Nan::To(info[5]).FromJust(); if (info[6]->IsObject()) { + if (get_optional_cdt_context(&cmd->context, &cmd->with_context, info[6].As(), "context", log) != + AS_NODE_PARAM_OK) { + return CmdSetError(cmd, AEROSPIKE_ERR_PARAM, + "Context parameter is invalid"); + } + } + + if (info[7]->IsObject()) { cmd->policy = (as_policy_info *)cf_malloc(sizeof(as_policy_info)); - if (infopolicy_from_jsobject(cmd->policy, info[6].As(), log) != + if (infopolicy_from_jsobject(cmd->policy, info[7].As(), log) != AS_NODE_PARAM_OK) { return CmdSetError(cmd, AEROSPIKE_ERR_PARAM, "Policy parameter is invalid"); @@ -115,9 +128,9 @@ static void execute(uv_work_t *req) "index=%s, type=%d, datatype=%d", cmd->ns, cmd->set, cmd->bin, cmd->index, cmd->itype, cmd->dtype); - aerospike_index_create_complex(cmd->as, &cmd->err, &cmd->task, cmd->policy, + aerospike_index_create_ctx(cmd->as, &cmd->err, &cmd->task, cmd->policy, cmd->ns, cmd->set, cmd->bin, cmd->index, - cmd->itype, cmd->dtype); + cmd->itype, cmd->dtype, cmd->with_context ? &cmd->context : NULL); } static void respond(uv_work_t *req, int status) @@ -144,8 +157,9 @@ NAN_METHOD(AerospikeClient::IndexCreate) TYPE_CHECK_REQ(info[3], IsString, "Index name must be a string"); TYPE_CHECK_OPT(info[4], IsNumber, "Index type must be an integer"); TYPE_CHECK_REQ(info[5], IsNumber, "Index datatype must be an integer"); - TYPE_CHECK_OPT(info[6], IsObject, "Policy must be an object"); - TYPE_CHECK_REQ(info[7], IsFunction, "Callback must be a function"); + TYPE_CHECK_OPT(info[6], IsObject, "Context must be an object"); + TYPE_CHECK_OPT(info[7], IsObject, "Policy must be an object"); + TYPE_CHECK_REQ(info[8], IsFunction, "Callback must be a function"); async_invoke(info, prepare, execute, respond); } diff --git a/src/main/commands/query_apply.cc b/src/main/commands/query_apply.cc index f54b28d7..3268e257 100644 --- a/src/main/commands/query_apply.cc +++ b/src/main/commands/query_apply.cc @@ -46,11 +46,16 @@ class QueryApplyCommand : public AerospikeCommand { cf_free(policy); if (val != NULL) cf_free(val); + if(with_context){ + as_cdt_ctx_destroy(&context); + } } as_policy_query *policy = NULL; as_query query; as_val *val = NULL; + as_cdt_ctx context; + bool with_context = false; }; static bool query_foreach_callback(const as_val *val, void *udata) @@ -71,7 +76,7 @@ static void *prepare(const Nan::FunctionCallbackInfo &info) new QueryApplyCommand(client, info[4].As()); LogInfo *log = client->log; - setup_query(&cmd->query, info[0], info[1], info[2], log); + setup_query(&cmd->query, info[0], info[1], info[2], &cmd->context, &cmd->with_context, log); if (info[3]->IsObject()) { cmd->policy = (as_policy_query *)cf_malloc(sizeof(as_policy_query)); diff --git a/src/main/commands/query_async.cc b/src/main/commands/query_async.cc index 60b6d6f0..2599baec 100644 --- a/src/main/commands/query_async.cc +++ b/src/main/commands/query_async.cc @@ -53,8 +53,10 @@ NAN_METHOD(AerospikeClient::QueryAsync) as_status status; as_partition_filter pf; bool pf_defined = false; + as_cdt_ctx context; + bool with_context = false; - setup_query(&query, info[0], info[1], info[2], log); + setup_query(&query, info[0], info[1], info[2], &context, &with_context, log); if (info[3]->IsObject()) { if (querypolicy_from_jsobject(&policy, info[3].As(), log) != @@ -95,4 +97,8 @@ NAN_METHOD(AerospikeClient::QueryAsync) Cleanup: delete cmd; free_query(&query, p_policy); + if(with_context){ + as_cdt_ctx_destroy(&context); + } + } diff --git a/src/main/commands/query_background.cc b/src/main/commands/query_background.cc index f8e31f45..54f096b6 100644 --- a/src/main/commands/query_background.cc +++ b/src/main/commands/query_background.cc @@ -44,11 +44,16 @@ class QueryBackgroundCommand : public AerospikeCommand { free_query(&query, NULL); if (policy != NULL) cf_free(policy); + if(with_context){ + as_cdt_ctx_destroy(&context); + } } as_policy_write *policy = NULL; uint64_t query_id = 0; as_query query; + as_cdt_ctx context; + bool with_context = false; }; static void *prepare(const Nan::FunctionCallbackInfo &info) @@ -59,7 +64,8 @@ static void *prepare(const Nan::FunctionCallbackInfo &info) new QueryBackgroundCommand(client, info[5].As()); LogInfo *log = client->log; - setup_query(&cmd->query, info[0], info[1], info[2], log); + + setup_query(&cmd->query, info[0], info[1], info[2], &cmd->context, &cmd->with_context, log); if (info[3]->IsObject()) { cmd->policy = (as_policy_write *)cf_malloc(sizeof(as_policy_write)); diff --git a/src/main/commands/query_foreach.cc b/src/main/commands/query_foreach.cc index df68f53c..5e440f1d 100644 --- a/src/main/commands/query_foreach.cc +++ b/src/main/commands/query_foreach.cc @@ -54,6 +54,9 @@ class QueryForeachCommand : public AerospikeCommand { as_queue_mt_destroy(results); results = NULL; } + if(with_context){ + as_cdt_ctx_destroy(&context); + } } as_policy_query *policy = NULL; @@ -62,6 +65,8 @@ class QueryForeachCommand : public AerospikeCommand { uint32_t max_q_size; uint32_t signal_interval = 0; uv_async_t async_handle; + as_cdt_ctx context; + bool with_context = false; }; // Push the record from the server to a queue. @@ -151,7 +156,7 @@ static void *prepare(const Nan::FunctionCallbackInfo &info) new QueryForeachCommand(client, info[4].As()); LogInfo *log = client->log; - setup_query(&cmd->query, info[0], info[1], info[2], log); + setup_query(&cmd->query, info[0], info[1], info[2], &cmd->context, &cmd->with_context, log); if (info[3]->IsObject()) { cmd->policy = (as_policy_query *)cf_malloc(sizeof(as_policy_query)); diff --git a/src/main/commands/query_pages.cc b/src/main/commands/query_pages.cc index f0755513..3d4335b2 100644 --- a/src/main/commands/query_pages.cc +++ b/src/main/commands/query_pages.cc @@ -21,6 +21,7 @@ #include "policy.h" #include "log.h" #include "query.h" +#include "operations.h" extern "C" { #include @@ -40,13 +41,14 @@ NAN_METHOD(AerospikeClient::QueryPages) TYPE_CHECK_OPT(info[2], IsObject, "Options must be an object"); TYPE_CHECK_OPT(info[3], IsObject, "Policy must be an object"); TYPE_CHECK_OPT(info[4], IsObject, "saved_query must be an object"); - TYPE_CHECK_OPT(info[5], IsNumber, "max_records must be an object"); - TYPE_CHECK_REQ(info[6], IsFunction, "Callback must be a function"); + TYPE_CHECK_OPT(info[5], IsNumber, "max_records must be a number"); + TYPE_CHECK_OPT(info[6], IsObject, "context must be an object"); + TYPE_CHECK_REQ(info[7], IsFunction, "Callback must be a function"); AerospikeClient *client = Nan::ObjectWrap::Unwrap(info.This()); AsyncCommand *cmd = - new AsyncCommand("Query", client, info[6].As()); + new AsyncCommand("Query", client, info[7].As()); LogInfo *log = client->log; as_policy_query* p_policy = NULL; @@ -54,23 +56,36 @@ NAN_METHOD(AerospikeClient::QueryPages) as_partition_filter pf; bool pf_defined = false; as_status status; + as_cdt_ctx context; + bool with_context = false; struct query_udata* qu = (query_udata*) cf_malloc(sizeof(struct query_udata)); qu->cmd = cmd; qu->count = 0; + if (info[6]->IsObject()) { + if (get_optional_cdt_context(&context, &with_context, info[6].As(), "context", log) != + AS_NODE_PARAM_OK) { + as_v8_error(log, "Parsing context arguments for query index filter failed"); + Nan::ThrowTypeError("Error in filter context"); + } + } + if (info[4]->IsObject()) { uint32_t bytes_size = 0; load_bytes_size(info[4].As(), &bytes_size, log); uint8_t* bytes = new uint8_t[bytes_size]; load_bytes(info[4].As(), bytes, bytes_size, log); - setup_query_pages(&qu->query, info[0], info[1], Nan::Null(), bytes, bytes_size, log); - delete bytes; + setup_query_pages(&qu->query, info[0], info[1], Nan::Null(), bytes, bytes_size, &context, &with_context, log); + delete [] bytes; } else{ - setup_query_pages(&qu->query, info[0], info[1], info[2], NULL, 0, log); + setup_query_pages(&qu->query, info[0], info[1], info[2], NULL, 0, &context, &with_context, log); } + if(with_context) { + qu->query->where.entries[0].ctx = &context; + } if (info[3]->IsObject()) { if (querypolicy_from_jsobject(&policy, info[3].As(), log) != @@ -120,5 +135,8 @@ NAN_METHOD(AerospikeClient::QueryPages) if (p_policy && policy.base.filter_exp) { as_exp_destroy(policy.base.filter_exp); } + if(with_context) { + as_cdt_ctx_destroy(&context); + } } \ No newline at end of file diff --git a/src/main/commands/scan_pages.cc b/src/main/commands/scan_pages.cc index faf85161..503aa423 100644 --- a/src/main/commands/scan_pages.cc +++ b/src/main/commands/scan_pages.cc @@ -68,7 +68,7 @@ NAN_METHOD(AerospikeClient::ScanPages) uint8_t* bytes = new uint8_t[bytes_size]; load_bytes(info[5].As(), bytes, bytes_size, log); setup_scan_pages(&su->scan, info[0], info[1], Nan::Null(), bytes, bytes_size, log); - delete bytes; + delete [] bytes; } else{ setup_scan_pages(&su->scan, info[0], info[1], info[2], NULL, 0, log); diff --git a/src/main/policy.cc b/src/main/policy.cc index 4bb80082..93477256 100644 --- a/src/main/policy.cc +++ b/src/main/policy.cc @@ -546,6 +546,11 @@ int querypolicy_from_jsobject(as_policy_query *policy, Local obj, AS_NODE_PARAM_OK) { return rc; } + if ((rc = get_optional_uint32_property((uint32_t *)&policy->replica, NULL, + obj, "replica", log)) != + AS_NODE_PARAM_OK) { + return rc; + } if ((rc = get_optional_uint32_property((uint32_t *)&policy->info_timeout, NULL, obj, "infoTimeout", log)) != AS_NODE_PARAM_OK) { @@ -600,6 +605,11 @@ int scanpolicy_from_jsobject(as_policy_scan *policy, Local obj, AS_NODE_PARAM_OK) { return rc; } + if ((rc = get_optional_uint32_property((uint32_t *)&policy->replica, NULL, + obj, "replica", log)) != + AS_NODE_PARAM_OK) { + return rc; + } as_v8_detail(log, "Parsing scan policy: success"); return AS_NODE_PARAM_OK; } diff --git a/src/main/query.cc b/src/main/query.cc index 64af4442..779d14fd 100644 --- a/src/main/query.cc +++ b/src/main/query.cc @@ -31,7 +31,7 @@ extern "C" { using namespace v8; void setup_query(as_query *query, Local ns, Local set, - Local maybe_options, LogInfo *log) + Local maybe_options, as_cdt_ctx* context, bool* with_context, LogInfo *log) { as_namespace as_ns = {'\0'}; as_set as_set = {'\0'}; @@ -50,18 +50,18 @@ void setup_query(as_query *query, Local ns, Local set, // TODO: Return param error } } - as_query_init(query, as_ns, as_set); if (!maybe_options->IsObject()) { return; } - setup_options(query, maybe_options.As(), log); + + setup_options(query, maybe_options.As(), context, with_context, log); } -void setup_options(as_query *query, Local options, LogInfo *log) +void setup_options(as_query *query, Local options, as_cdt_ctx* context, bool* with_context, LogInfo *log) { Local filters_val = @@ -76,6 +76,14 @@ void setup_options(as_query *query, Local options, LogInfo *log) for (int i = 0; i < size; i++) { Local filter = Nan::Get(filters, i).ToLocalChecked().As(); + if(!(*with_context)){ + if (get_optional_cdt_context(context, with_context, filter, "context", log) != + AS_NODE_PARAM_OK) { + as_v8_error(log, "Parsing context arguments for query index filter failed"); + Nan::ThrowTypeError("Error in filter context"); + } + } + Local bin = Nan::Get(filter, Nan::New("bin").ToLocalChecked()) .ToLocalChecked(); @@ -115,7 +123,7 @@ void setup_options(as_query *query, Local options, LogInfo *log) if (v8min->IsNumber() && v8max->IsNumber()) { const int64_t min = Nan::To(v8min).FromJust(); const int64_t max = Nan::To(v8max).FromJust(); - as_query_where(query, bin_name, predicate, type, + as_query_where_with_ctx(query, bin_name, *with_context ? context : NULL, predicate, type, datatype, min, max); as_v8_debug(log, "Integer range predicate from %llu to %llu", @@ -140,7 +148,7 @@ void setup_options(as_query *query, Local options, LogInfo *log) "The region value passed is not a GeoJSON string"); } const char *bin_val = strdup(*Nan::Utf8String(value)); - as_query_where(query, bin_name, predicate, type, datatype, + as_query_where_with_ctx(query, bin_name, *with_context ? context : NULL, predicate, type, datatype, bin_val); as_v8_debug(log, "Geo range predicate %s", bin_val); } @@ -153,7 +161,7 @@ void setup_options(as_query *query, Local options, LogInfo *log) .ToLocalChecked(); if (value->IsNumber()) { const int64_t val = Nan::To(value).FromJust(); - as_query_where(query, bin_name, predicate, type, + as_query_where_with_ctx(query, bin_name, *with_context ? context : NULL, predicate, type, datatype, val); as_v8_debug(log, "Integer equality predicate %d", val); } @@ -175,7 +183,7 @@ void setup_options(as_query *query, Local options, LogInfo *log) "predicate - value is not a string"); } const char *bin_val = strdup(*Nan::Utf8String(value)); - as_query_where(query, bin_name, predicate, type, datatype, + as_query_where_with_ctx(query, bin_name, *with_context ? context : NULL, predicate, type, datatype, bin_val); as_v8_debug(log, "String equality predicate %s", bin_val); } @@ -253,7 +261,8 @@ void setup_options(as_query *query, Local options, LogInfo *log) } void setup_query_pages(as_query** query, Local ns, Local set, - Local maybe_options, uint8_t* bytes, uint32_t bytes_size, LogInfo *log) + Local maybe_options, uint8_t* bytes, uint32_t bytes_size, + as_cdt_ctx* context, bool* with_context, LogInfo *log) { as_namespace as_ns = {'\0'}; as_set as_set = {'\0'}; @@ -285,7 +294,7 @@ void setup_query_pages(as_query** query, Local ns, Local set, return; } - setup_options(*query, maybe_options.As(), log); + setup_options(*query, maybe_options.As(), context, with_context, log); } diff --git a/test/cdt_context.js b/test/cdt_context.js index 3bed967a..278e56dc 100644 --- a/test/cdt_context.js +++ b/test/cdt_context.js @@ -16,7 +16,7 @@ 'use strict' -/* eslint-env mocha */ +/* global expect, describe, it */ const Aerospike = require('../lib/aerospike') const helper = require('./test_helper') @@ -49,6 +49,12 @@ describe('Aerospike.cdt.Context', function () { .then(assertResultEql({ nested: 5 })) .then(cleanup) }) + it('Throws an error when index is too large', function () { + expect(() => new Context().addListIndex(2147483648)).to.throw(Error) + }) + it('Throws an error when index is too small', function () { + expect(() => new Context().addListIndex(-2147483649)).to.throw(Error) + }) }) describe('Context.addListIndexCreate', function () { @@ -127,6 +133,12 @@ describe('Aerospike.cdt.Context', function () { .then(assertError(status.ERR_OP_NOT_APPLICABLE)) .then(cleanup) }) + it('Throws an error when index is too large', function () { + expect(() => new Context().addListIndexCreate(2147483648)).to.throw(Error) + }) + it('Throws an error when index is too small', function () { + expect(() => new Context().addListIndexCreate(-2147483649)).to.throw(Error) + }) }) describe('Context.addListRank', function () { @@ -139,6 +151,12 @@ describe('Aerospike.cdt.Context', function () { .then(assertResultEql({ nested: 3 })) .then(cleanup) }) + it('Throws an error when rank is too large', function () { + expect(() => new Context().addListRank(2147483648)).to.throw(Error) + }) + it('Throws an error when rank is too small', function () { + expect(() => new Context().addListRank(-2147483649)).to.throw(Error) + }) }) describe('Context.addListValue', function () { @@ -163,6 +181,12 @@ describe('Aerospike.cdt.Context', function () { .then(assertResultEql({ nested: 3 })) .then(cleanup) }) + it('Throws an error when index is too large', function () { + expect(() => new Context().addMapIndex(2147483648)).to.throw(Error) + }) + it('Throws an error when index is too small', function () { + expect(() => new Context().addMapIndex(-2147483649)).to.throw(Error) + }) }) describe('Context.addMapRank', function () { @@ -175,6 +199,12 @@ describe('Aerospike.cdt.Context', function () { .then(assertResultEql({ nested: 3 })) .then(cleanup) }) + it('Throws an error when rank is too large', function () { + expect(() => new Context().addMapRank(2147483648)).to.throw(Error) + }) + it('Throws an error when rank is too small', function () { + expect(() => new Context().addMapRank(-2147483649)).to.throw(Error) + }) }) describe('Context.addMapKey', function () { diff --git a/test/client.js b/test/client.js index 3ecb9577..5cf7d11e 100644 --- a/test/client.js +++ b/test/client.js @@ -20,6 +20,7 @@ const Aerospike = require('../lib/aerospike') const Client = Aerospike.Client +const Context = Aerospike.cdt.Context const helper = require('./test_helper') const keygen = helper.keygen @@ -152,6 +153,74 @@ describe('Client', function () { }) }) + describe('Client#contextToBase64', function () { + const client = helper.client + const context = new Context().addMapKey('nested') + it('Serializes a CDT context', function () { + expect(typeof client.contextToBase64(context)).to.equal('string') + }) + it('Throws an error if no context is given', function () { + expect(() => { client.contextToBase64() }).to.throw(Error) + }) + it('Throws an error if a non-object is given', function () { + expect(() => { client.contextToBase64('test') }).to.throw(Error) + }) + }) + + describe('Client#contextFromBase64', function () { + const client = helper.client + const addListIndex = new Context().addListIndex(5) + const addListIndexCreate = new Context().addListIndexCreate(45, Aerospike.lists.order.KEY_ORDERED, true) + const addListRank = new Context().addListRank(15) + const addListValueString = new Context().addListValue('apple') + const addListValueInt = new Context().addListValue(4500) + const addMapIndex = new Context().addMapIndex(10) + const addMapRank = new Context().addMapRank(11) + const addMapKey = new Context().addMapKey('nested') + const addMapKeyCreate = new Context().addMapKeyCreate('nested', Aerospike.maps.order.ORDERED) + const addMapValueString = new Context().addMapValue('nested') + const addMapValueInt = new Context().addMapValue(1000) + it('Deserializes a cdt context with addListIndex', function () { + expect(client.contextFromBase64(client.contextToBase64(addListIndex))).to.eql(addListIndex) + }) + it('Deserializes a cdt context with addListIndexCreate', function () { + expect(client.contextFromBase64(client.contextToBase64(addListIndexCreate))).to.eql(addListIndexCreate) + }) + it('Deserializes a cdt context with addListRank', function () { + expect(client.contextFromBase64(client.contextToBase64(addListRank))).to.eql(addListRank) + }) + it('Deserializes a cdt context with addListValueString', function () { + expect(client.contextFromBase64(client.contextToBase64(addListValueString))).to.eql(addListValueString) + }) + it('Deserializes a cdt context with addListValueInt', function () { + expect(client.contextFromBase64(client.contextToBase64(addListValueInt))).to.eql(addListValueInt) + }) + it('Deserializes a cdt context with addMapIndex', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapIndex))).to.eql(addMapIndex) + }) + it('Deserializes a cdt context with addMapRank', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapRank))).to.eql(addMapRank) + }) + it('Deserializes a cdt context with addMapKey', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapKey))).to.eql(addMapKey) + }) + it('Deserializes a cdt context with addMapKeyCreate', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapKeyCreate))).to.eql(addMapKeyCreate) + }) + it('Deserializes a cdt context with addMapValueString', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapValueString))).to.eql(addMapValueString) + }) + it('Deserializes a cdt context with addMapValueInt', function () { + expect(client.contextFromBase64(client.contextToBase64(addMapValueInt))).to.eql(addMapValueInt) + }) + it('Throws an error if no value is given', function () { + expect(() => { client.contextFromBase64() }).to.throw(Error) + }) + it('Throws an error if an non-string value is given', function () { + expect(() => { client.contextFromBase64(45) }).to.throw(Error) + }) + }) + context.skip('cluster name', function () { it('should fail to connect to the cluster if the cluster name does not match', function (done) { const config = Object.assign({}, helper.config) diff --git a/test/index.js b/test/index.js index f98019e0..9fbf5b74 100644 --- a/test/index.js +++ b/test/index.js @@ -23,6 +23,7 @@ const Aerospike = require('../lib/aerospike') const Job = require('../lib/job') const IndexJob = require('../lib/index_job') const helper = require('./test_helper') +const Context = Aerospike.cdt.Context context('secondary indexes', function () { const client = helper.client @@ -80,6 +81,37 @@ context('secondary indexes', function () { .then(() => verifyIndexExists(helper.namespace, testIndex.name)) }) + it('should create an index with CDT Context', function () { + const options = { + ns: helper.namespace, + set: helper.set, + bin: testIndex.bin, + index: testIndex.name, + type: Aerospike.indexType.LIST, + datatype: Aerospike.indexDataType.NUMERIC, + context: new Context().addListIndex(0) + } + + return client.createIndex(options) + .then(() => verifyIndexExists(helper.namespace, testIndex.name)) + }) + + it('should not create an index with CDT Context \'addListIndexCreate\'', function () { + const options = { + ns: helper.namespace, + set: helper.set, + bin: testIndex.bin, + index: testIndex.name, + type: Aerospike.indexType.LIST, + datatype: Aerospike.indexDataType.NUMERIC, + context: new Context().addListIndexCreate(0, 0, false) + } + + return client.createIndex(options) + .then(() => expect(1).to.equal(2)) + .catch(() => { expect('pass').to.equal('pass') }) + }) + it('should create an integer index with info policy', function () { const options = { ns: helper.namespace, @@ -162,15 +194,35 @@ context('secondary indexes', function () { }) }) - describe('Client#indexRemove()', function () { - beforeEach(() => helper.index.create(testIndex.name, helper.set, testIndex.bin, - Aerospike.indexDataType.STRING, Aerospike.indexType.DEFAULT)) + describe('Client#indexRemove()', async function () { + beforeEach(() => { + helper.index.create(testIndex.name, helper.set, testIndex.bin, + Aerospike.indexDataType.STRING, Aerospike.indexType.DEFAULT) + }) - it('should drop an index', function (done) { - client.indexRemove(helper.namespace, testIndex.name, function (err) { - expect(err).to.be.null() - done() - }) + it('should drop an index', async function () { + // Wait for index creation to complete + this.timeout(10000) + await new Promise(resolve => setTimeout(resolve, 5000)) + + // Do query on the secondary index to ensure proper creation. + let query = client.query(helper.namespace, helper.set) + query.where(Aerospike.filter.equal(testIndex.bin, 'value')) + await query.results() + + await client.indexRemove(helper.namespace, testIndex.name) + + // Do query on the secondary index to ensure proper deletion + query = client.query(helper.namespace, helper.set) + query.where(Aerospike.filter.equal(testIndex.bin, 'value')) + try { + await query.results() + // Fail test if this code is reached + expect('fail').to.equal('now') + } catch (error) { + expect(error.code).to.equal(201) + expect('pass').to.equal('pass') + } }) it('should return a Promise if called without callback function', function () { diff --git a/test/query.js b/test/query.js index 717f346a..8e51c20a 100644 --- a/test/query.js +++ b/test/query.js @@ -24,6 +24,7 @@ const Query = require('../lib/query') const Job = require('../lib/job') const helper = require('./test_helper') const exp = Aerospike.exp +const Context = Aerospike.cdt.Context const AerospikeError = Aerospike.AerospikeError const GeoJSON = Aerospike.GeoJSON @@ -79,7 +80,37 @@ describe('Queries', function () { { name: 'filter', value: 1 }, { name: 'filter', value: 2 }, { name: 'filter', value: 3 }, - { name: 'filter', value: 4 } + { name: 'filter', value: 4 }, + + { name: 'nested int list match', li: { nested: [1, 5, 9] } }, + { name: 'nested int list non-match', li: { nested: [500, 501, 502] } }, + { name: 'nested int map match', mi: { nested: { a: 1, b: 5, c: 9 } } }, + { name: 'nested int map non-match', mi: { nested: { a: 500, b: 501, c: 502 } } }, + { name: 'nested string list match', ls: { nested: ['banana', 'blueberry'] } }, + { name: 'nested string list non-match', ls: { nested: ['tomato', 'cuccumber'] } }, + { name: 'nested string map match', ms: { nested: { a: 'banana', b: 'blueberry' } } }, + { name: 'nested string map non-match', ms: { nested: { a: 'tomato', b: 'cuccumber' } } }, + { name: 'nested string mapkeys match', mks: { nested: { banana: 1, blueberry: 2 } } }, + { name: 'nested string mapkeys non-match', mks: { nested: { tomato: 3, cuccumber: 4 } } }, + { name: 'nested point match', g: { nested: GeoJSON.Point(103.913, 1.308) } }, + { name: 'nested point non-match', g: { nested: GeoJSON.Point(-122.101, 37.421) } }, + { name: 'nested point list match', lg: { nested: [GeoJSON.Point(103.913, 1.308), GeoJSON.Point(105.913, 3.308)] } }, + { name: 'nested point list non-match', lg: { nested: [GeoJSON.Point(-122.101, 37.421), GeoJSON.Point(-120.101, 39.421)] } }, + { name: 'nested point map match', mg: { nested: { a: GeoJSON.Point(103.913, 1.308), b: GeoJSON.Point(105.913, 3.308) } } }, + { name: 'nested point map non-match', mg: { nested: { a: GeoJSON.Point(-122.101, 37.421), b: GeoJSON.Point(-120.101, 39.421) } } }, + { name: 'nested region match', g: { nested: GeoJSON.Polygon([102.913, 0.308], [102.913, 2.308], [104.913, 2.308], [104.913, 0.308], [102.913, 0.308]) } }, + { name: 'nested region non-match', g: { nested: GeoJSON.Polygon([-121.101, 36.421], [-121.101, 38.421], [-123.101, 38.421], [-123.101, 36.421], [-121.101, 36.421]) } }, + { name: 'nested region list match', lg: { nested: [GeoJSON.Polygon([102.913, 0.308], [102.913, 2.308], [104.913, 2.308], [104.913, 0.308], [102.913, 0.308])] } }, + { name: 'nested region list non-match', lg: { nested: [GeoJSON.Polygon([-121.101, 36.421], [-121.101, 38.421], [-123.101, 38.421], [-123.101, 36.421], [-121.101, 36.421])] } }, + { name: 'nested region map match', mg: { nested: { a: GeoJSON.Polygon([102.913, 0.308], [102.913, 2.308], [104.913, 2.308], [104.913, 0.308], [102.913, 0.308]) } } }, + { name: 'nested region map non-match', mg: { nested: [GeoJSON.Polygon([-121.101, 36.421], [-121.101, 38.421], [-123.101, 38.421], [-123.101, 36.421], [-121.101, 36.421])] } }, + { name: 'nested aggregate', nested: { value: 10 } }, + { name: 'nested aggregate', nested: { value: 20 } }, + { name: 'nested aggregate', nested: { value: 30 } }, + { name: 'nested aggregate', nested: { doubleNested: { value: 10 } } }, + { name: 'nested aggregate', nested: { doubleNested: { value: 20 } } }, + { name: 'nested aggregate', nested: { doubleNested: { value: 30 } } } + ] const numberOfSamples = samples.length const indexes = [ @@ -93,7 +124,18 @@ describe('Queries', function () { ['qidxStrMapKeys', 'mks', STRING, MAPKEYS], ['qidxGeo', 'g', GEO2DSPHERE], ['qidxGeoList', 'lg', GEO2DSPHERE, LIST], - ['qidxGeoMap', 'mg', GEO2DSPHERE, MAPVALUES] + ['qidxGeoMap', 'mg', GEO2DSPHERE, MAPVALUES], + // CDT context indexes + ['qidxNameNested', 'name', STRING, MAPKEYS, new Context().addMapKey('nested')], + ['qidxIntListNested', 'li', NUMERIC, LIST, new Context().addMapKey('nested')], + ['qidxIntMapNested', 'mi', NUMERIC, MAPVALUES, new Context().addMapKey('nested')], + ['qidxStrListNested', 'ls', STRING, LIST, new Context().addMapKey('nested')], + ['qidxStrMapNested', 'ms', STRING, MAPVALUES, new Context().addMapKey('nested')], + ['qidxStrMapKeysNested', 'mks', STRING, MAPKEYS, new Context().addMapKey('nested')], + ['qidxGeoListNested', 'lg', GEO2DSPHERE, LIST, new Context().addMapKey('nested')], + ['qidxGeoMapNested', 'mg', GEO2DSPHERE, MAPVALUES, new Context().addMapKey('nested')], + ['qidxAggregateMapNested', 'nested', STRING, MAPKEYS], + ['qidxAggregateMapDoubleNested', 'nested', STRING, MAPKEYS, new Context().addMapKey('doubleNested')] ] let keys = [] @@ -122,7 +164,7 @@ describe('Queries', function () { putgen.put(numberOfSamples, generators) .then((records) => { keys = records.map((rec) => rec.key) }) .then(() => Promise.all(indexes.map(idx => - helper.index.create(idx[0], testSet, idx[1], idx[2], idx[3])))), + helper.index.create(idx[0], testSet, idx[1], idx[2], idx[3], idx[4])))), helper.udf.register('udf.lua') ]) }) @@ -200,6 +242,22 @@ describe('Queries', function () { }) }) + it('should apply a stream UDF to the nested context', function (done) { + const args = { + filters: [filter.contains('name', 'value', MAPKEYS, new Context().addMapKey('nested'))] + } + const query = client.query(helper.namespace, testSet, args) + query.setUdf('udf', 'even') + const stream = query.foreach() + const results = [] + stream.on('error', error => { throw error }) + stream.on('data', record => results.push(record.bins)) + stream.on('end', () => { + expect(results.sort()).to.eql([]) + done() + }) + }) + describe('query.paginate()', function () { it('paginates with the correct amount of keys and pages', async function () { let recordsReceived = 0 @@ -292,6 +350,32 @@ describe('Queries', function () { } }) + it('Paginates correctly using query.results() on an index with a cdt context', async function () { + let recordTotal = 0 + let recordsReceived = 0 + let pageTotal = 0 + const lastPage = 1 + const maxRecs = 5 + const query = client.query(helper.namespace, testSet, { paginate: true, maxRecords: maxRecs, filters: [filter.contains('nested', 'value', MAPKEYS, new Context().addMapKey('doubleNested'))] }) + let results = [] + while (1) { + console.log(results) + results = await query.results() + console.log(results) + recordsReceived += results.length + results = [] + pageTotal += 1 + recordTotal += recordsReceived + if (recordsReceived !== maxRecs) { + expect(query.hasNextPage()).to.equal(false) + expect(pageTotal).to.equal(lastPage) + expect(recordTotal).to.equal(3) + break + } + recordsReceived = 0 + } + }) + it('Throw error when query.UDF is set and query.paginate is true', async function () { const maxRecs = 2 const query = client.query(helper.namespace, testSet, { paginate: true, maxRecords: maxRecs, filters: [filter.equal('name', 'filter')] }) @@ -481,10 +565,20 @@ describe('Queries', function () { verifyQueryResults(args, 'int list match', done) }) + it('should match integers in a list within a range in a nested context', function (done) { + const args = { filters: [filter.range('li', 3, 7, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested int list match', done) + }) + it('should match integers in a map within a range', function (done) { const args = { filters: [filter.range('mi', 3, 7, MAPVALUES)] } verifyQueryResults(args, 'int map match', done) }) + + it('should match integers in a map within a range in a nested context', function (done) { + const args = { filters: [filter.range('mi', 3, 7, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested int map match', done) + }) }) describe('filter.contains()', function () { @@ -493,26 +587,51 @@ describe('Queries', function () { verifyQueryResults(args, 'int list match', done) }) + it('should match lists containing an integer in a nested context', function (done) { + const args = { filters: [filter.contains('li', 5, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested int list match', done) + }) + it('should match maps containing an integer value', function (done) { const args = { filters: [filter.contains('mi', 5, MAPVALUES)] } verifyQueryResults(args, 'int map match', done) }) + it('should match maps containing an integer value in a nested context', function (done) { + const args = { filters: [filter.contains('mi', 5, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested int map match', done) + }) + it('should match lists containing a string', function (done) { const args = { filters: [filter.contains('ls', 'banana', LIST)] } verifyQueryResults(args, 'string list match', done) }) + it('should match lists containing a string in a nested context', function (done) { + const args = { filters: [filter.contains('ls', 'banana', LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested string list match', done) + }) + it('should match maps containing a string value', function (done) { const args = { filters: [filter.contains('ms', 'banana', MAPVALUES)] } verifyQueryResults(args, 'string map match', done) }) + it('should match maps containing a string value in a nested context', function (done) { + const args = { filters: [filter.contains('ms', 'banana', MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested string map match', done) + }) + it('should match maps containing a string key', function (done) { const args = { filters: [filter.contains('mks', 'banana', MAPKEYS)] } verifyQueryResults(args, 'string mapkeys match', done) }) + it('should match maps containing a string key in a nested context', function (done) { + const args = { filters: [filter.contains('mks', 'banana', MAPKEYS, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested string mapkeys match', done) + }) + it('throws a type error if the comparison value is of invalid type', function () { const fn = () => filter.contains('list', { foo: 'bar' }, LIST) expect(fn).to.throw(TypeError) @@ -532,12 +651,24 @@ describe('Queries', function () { verifyQueryResults(args, 'point list match', done) }) + it('should match locations in a list within a GeoJSON region in a nested context', function (done) { + const region = new GeoJSON({ type: 'Polygon', coordinates: [[[103, 1.3], [104, 1.3], [104, 1.4], [103, 1.4], [103, 1.3]]] }) + const args = { filters: [filter.geoWithinGeoJSONRegion('lg', region, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested point list match', done) + }) + it('should match locations in a map within a GeoJSON region', function (done) { const region = new GeoJSON({ type: 'Polygon', coordinates: [[[103, 1.3], [104, 1.3], [104, 1.4], [103, 1.4], [103, 1.3]]] }) const args = { filters: [filter.geoWithinGeoJSONRegion('mg', region, MAPVALUES)] } verifyQueryResults(args, 'point map match', done) }) + it('should match locations in a map within a GeoJSON region in a nested context', function (done) { + const region = new GeoJSON({ type: 'Polygon', coordinates: [[[103, 1.3], [104, 1.3], [104, 1.4], [103, 1.4], [103, 1.3]]] }) + const args = { filters: [filter.geoWithinGeoJSONRegion('mg', region, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested point map match', done) + }) + it('accepts a plain object as GeoJSON', function (done) { const region = { type: 'Polygon', coordinates: [[[103, 1.3], [104, 1.3], [104, 1.4], [103, 1.4], [103, 1.3]]] } const args = { filters: [filter.geoWithinGeoJSONRegion('g', region)] } @@ -556,10 +687,20 @@ describe('Queries', function () { verifyQueryResults(args, 'point list match', done) }) + it('should match locations in a list within a radius from another location in a nested context', function (done) { + const args = { filters: [filter.geoWithinRadius('lg', 103.9135, 1.3085, 15000, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested point list match', done) + }) + it('should match locations in a map within a radius from another location', function (done) { const args = { filters: [filter.geoWithinRadius('mg', 103.9135, 1.3085, 15000, MAPVALUES)] } verifyQueryResults(args, 'point map match', done) }) + + it('should match locations in a map within a radius from another location in a nested context', function (done) { + const args = { filters: [filter.geoWithinRadius('mg', 103.9135, 1.3085, 15000, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested point map match', done) + }) }) describe('filter.geoContainsGeoJSONPoint()', function () { @@ -575,12 +716,24 @@ describe('Queries', function () { verifyQueryResults(args, 'region list match', done) }) + it('should match regions in a list that contain a GeoJSON point in a nested context', function (done) { + const point = new GeoJSON({ type: 'Point', coordinates: [103.913, 1.308] }) + const args = { filters: [filter.geoContainsGeoJSONPoint('lg', point, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested region list match', done) + }) + it('should match regions in a map that contain a GeoJSON point', function (done) { const point = new GeoJSON({ type: 'Point', coordinates: [103.913, 1.308] }) const args = { filters: [filter.geoContainsGeoJSONPoint('mg', point, MAPVALUES)] } verifyQueryResults(args, 'region map match', done) }) + it('should match regions in a map that contain a GeoJSON point in a nested context', function (done) { + const point = new GeoJSON({ type: 'Point', coordinates: [103.913, 1.308] }) + const args = { filters: [filter.geoContainsGeoJSONPoint('mg', point, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested region map match', done) + }) + it('accepts a plain object as GeoJSON', function (done) { const point = { type: 'Point', coordinates: [103.913, 1.308] } const args = { filters: [filter.geoContainsGeoJSONPoint('g', point)] } @@ -599,10 +752,20 @@ describe('Queries', function () { verifyQueryResults(args, 'region list match', done) }) + it('should match regions in a list that contain a lng/lat coordinate pair in a nested context', function (done) { + const args = { filters: [filter.geoContainsPoint('lg', 103.913, 1.308, LIST, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested region list match', done) + }) + it('should match regions in a map that contain a lng/lat coordinate pair', function (done) { const args = { filters: [filter.geoContainsPoint('mg', 103.913, 1.308, MAPVALUES)] } verifyQueryResults(args, 'region map match', done) }) + + it('should match regions in a map that contain a lng/lat coordinate pair in a nested context', function (done) { + const args = { filters: [filter.geoContainsPoint('mg', 103.913, 1.308, MAPVALUES, new Context().addMapKey('nested'))] } + verifyQueryResults(args, 'nested region map match', done) + }) }) }) }) @@ -651,6 +814,30 @@ describe('Queries', function () { }) }) + it('should apply a user defined function and aggregate the results from a map', function (done) { + const args = { + filters: [filter.contains('nested', 'value', MAPKEYS)] + } + const query = client.query(helper.namespace, testSet, args) + query.apply('udf', 'count', function (error, result) { + if (error) throw error + expect(result).to.equal(3) + done() + }) + }) + + it('should apply a user defined function and aggregate the results from a nested map', function (done) { + const args = { + filters: [filter.contains('nested', 'value', MAPKEYS, new Context().addMapKey('doubleNested'))] + } + const query = client.query(helper.namespace, testSet, args) + query.apply('udf', 'count', function (error, result) { + if (error) throw error + expect(result).to.equal(3) + done() + }) + }) + it('should apply a user defined function with arguments and aggregate the results', function (done) { const args = { filters: [filter.equal('name', 'aggregate')] @@ -698,6 +885,16 @@ describe('Queries', function () { expect(job).to.be.instanceof(Job) }) }) + it('returns a Promise that resolves to a Job with a filter containing a CDT context', function () { + const args = { + filters: [filter.contains('nested', 'value', MAPKEYS, new Context().addMapKey('doubleNested'))] + } + const query = client.query(helper.namespace, testSet, args) + return query.background('udf', 'noop') + .then(job => { + expect(job).to.be.instanceof(Job) + }) + }) }) describe('query.operate()', function () { diff --git a/test/test_helper.js b/test/test_helper.js index c865d480..9682b92d 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -72,14 +72,15 @@ function IndexHelper (client) { this.client = client } -IndexHelper.prototype.create = function (indexName, setName, binName, dataType, indexType) { +IndexHelper.prototype.create = function (indexName, setName, binName, dataType, indexType, context) { const index = { ns: options.namespace, set: setName, bin: binName, index: indexName, type: indexType || Aerospike.indexType.DEFAULT, - datatype: dataType + datatype: dataType, + context } return this.client.createIndex(index) .then(job => job.wait(10))