diff --git a/lib/lists.js b/lib/lists.js index 35b02019..4a9688d7 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -1079,6 +1079,61 @@ exports.clear = function (bin) { return new ListOperation(opcodes.LIST_CLEAR, bin) } +/** + * @summary Creates map create operation. + * + * + * @param {string} bin - bin name. + * @param {number} order - list order. + * @parm {boolean} pad - If true, the context is allowed to be beyond list boundaries. In that case, nil + * list entries will be inserted to satisfy the context position. + * @param {boolean} persistIndex - If true, persist list index. A list index improves lookup performance, + * but requires more storage. A list index can be created for a top-level ordered list only. Nested and + * unordered list indexes are not supported. + * @param {number} ctx - optional path to nested list. If not defined, the top-level list is used. + * + * @returns {Object} Operation that can be passed to the {@link Client#operate} command. + * + * @example + * + * const Aerospike = require('aerospike') + * const lists = Aerospike.lists + * const key = new Aerospike.Key('test', 'demo', 'mapKey') + * + * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE! + * var config = { + * hosts: '192.168.33.10:3000', + * // Timeouts disabled, latency dependent on server location. Configure as needed. + * policies: { + * operate : new Aerospike.OperatePolicy({socketTimeout : 0, totalTimeout : 0}) + * } + * } + * + * Aerospike.connect(config).then(async client => { + * let ops = [ + * lists.create('list', lists.order.ORDERED, false, true) + * ] + * let result = await client.operate(key, ops) + * console.log(result.bins) // => { list: null } + * let record = await client.get(key) + * console.log(record.bins) // => { list: [] } + * + * await client.remove(key) + * client.close() + * }) + */ +exports.create = function (bin, order, pad = false, persistIndex = false, ctx) { + const op = new ListOperation(opcodes.LIST_CREATE, bin) + op.order = order + op.pad = pad + op.persistIndex = persistIndex + if (ctx === undefined) { + return op + } + + return op.withContext(ctx) +} + /** * @summary Sets the list element at the specified index to a new value. * @description This operation returns no result. diff --git a/lib/status.js b/lib/status.js index b0485370..d003cc3e 100644 --- a/lib/status.js +++ b/lib/status.js @@ -24,6 +24,37 @@ const as = require('bindings')('aerospike.node') * @description Database operation error codes. */ +/** + * One or more keys failed in a batch. + * @const {number} + */ +exports.BATCH_FAILED = exports.AEROSPIKE_BATCH_FAILED = as.status.AEROSPIKE_BATCH_FAILED + +/** + * No response received from server. + * @const {number} + */ +exports.NO_RESPONSE = exports.AEROSPIKE_NO_RESPONSE = as.status.AEROSPIKE_NO_RESPONSE + +/** + * Max errors limit reached. + * @const {number} + */ +exports.MAX_ERROR_RATE = exports.AEROSPIKE_MAX_ERROR_RATE = as.status.AEROSPIKE_MAX_ERROR_RATE + +/** + * Abort split batch retry and use normal node retry instead. + * Used internally and should not be returned to user. + * @const {number} + */ +exports.USE_NORMAL_RETRY = exports.AEROSPIKE_USE_NORMAL_RETRY = as.status.AEROSPIKE_USE_NORMAL_RETRY + +/** + * Max retries limit reached. + * @const {number} + */ +exports.ERR_MAX_RETRIES_EXCEEDED = exports.AEROSPIKE_ERR_MAX_RETRIES_EXCEEDED = as.status.AEROSPIKE_ERR_MAX_RETRIES_EXCEEDED + /** * Async command delay queue is full. * @const {number} diff --git a/src/main/list_operations.cc b/src/main/list_operations.cc index 21604bc1..2240fdd1 100644 --- a/src/main/list_operations.cc +++ b/src/main/list_operations.cc @@ -1032,6 +1032,29 @@ bool add_list_increment_op(as_operations *ops, const char *bin, return true; } +bool add_list_create_op(as_operations *ops, const char *bin, + as_cdt_ctx *context, Local op, LogInfo *log) +{ + as_list_order order; + if (get_uint32_property((uint32_t*)&order, op, "order", log) != AS_NODE_PARAM_OK) { + return false; + } + + bool pad; + if (get_bool_property(&pad, op, "pad", log) != AS_NODE_PARAM_OK) { + return false; + } + + bool persist_index; + if (get_bool_property(&persist_index, op, "persistIndex", log) != AS_NODE_PARAM_OK) { + return false; + } + + as_v8_debug(log, "order=%i, pad=%s, persist_index=%s", order, pad ? "true" : "false", persist_index ? "true" : "false"); + as_operations_list_create_all(ops, bin, context, order, pad, persist_index); + return true; +} + bool add_list_size_op(as_operations *ops, const char *bin, as_cdt_ctx *context, Local obj, LogInfo *log) { @@ -1084,7 +1107,8 @@ const ops_table_entry ops_table[] = { {"LIST_GET_BY_RANK", add_list_get_by_rank_op}, {"LIST_GET_BY_RANK_RANGE", add_list_get_by_rank_range_op}, {"LIST_INCREMENT", add_list_increment_op}, - {"LIST_SIZE", add_list_size_op}}; + {"LIST_SIZE", add_list_size_op}, + {"LIST_CREATE", add_list_create_op}}; int add_list_op(as_operations *ops, uint32_t opcode, Local op, LogInfo *log) diff --git a/src/main/policy.cc b/src/main/policy.cc index 3a695f11..a4ba8bd4 100644 --- a/src/main/policy.cc +++ b/src/main/policy.cc @@ -450,6 +450,11 @@ int batchwrite_policy_from_jsobject(as_policy_batch_write *policy, AS_NODE_PARAM_OK) { return rc; } + if ((rc = get_optional_uint32_property((uint32_t *)&policy->ttl, NULL, obj, + "ttl", log)) != + AS_NODE_PARAM_OK) { + return rc; + } if ((rc = get_optional_bool_property(&policy->durable_delete, NULL, obj, "durableDelete", log)) != AS_NODE_PARAM_OK) { diff --git a/test/lists.js b/test/lists.js index 961a825f..a9ae6b77 100644 --- a/test/lists.js +++ b/test/lists.js @@ -841,6 +841,69 @@ describe('client.operate() - CDT List operations', function () { }) }) + describe('lists.create', function () { + it('creates a new list', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('emptyList', lists.order.ORDERED))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5], emptyList: [] })) + .then(cleanup) + }) + + it('creates a new list with persist index true', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('emptyList', lists.order.ORDERED, false, true))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5], emptyList: [] })) + .then(cleanup) + }) + + + it('creates a new list with persist index true and pad true', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('emptyList', lists.order.ORDERED, true, true))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5], emptyList: [] })) + .then(cleanup) + }) + + it('creates a new list with pad true', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('emptyList', lists.order.ORDERED, true, false))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5], emptyList: [] })) + .then(cleanup) + }) + + context('with nested list context', function () { + helper.skipUnlessVersion('>= 4.6.0', this) + + it('creates a new list within a map', function () { + return initState() + .then(createRecord({ map: { c: 1, b: 2, a: 3 } })) + .then(operate(lists.create('map', lists.order.ORDERED).withContext(ctx => ctx.addMapKeyCreate('nested')))) + .then(assertRecordEql({ map: { c: 1, b: 2, a: 3, nested: []} })) + .then(cleanup) + }) + + it('creates a new list within a list', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('list', lists.order.UNORDERED, true, false).withContext(ctx => ctx.addListIndex(10)))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5, null, null, null, null, null, []] })) + .then(cleanup) + }) + + it('creates a new list within a list', function () { + return initState() + .then(createRecord({ list: [1, 2, 3, 4, 5] })) + .then(operate(lists.create('list', lists.order.UNORDERED, true, true).withContext(ctx => ctx.addListIndex(10)))) + .then(assertRecordEql({ list: [1, 2, 3, 4, 5, null, null, null, null, null, []] })) + .then(cleanup) + }) + }) + }) + describe('lists.set', function () { it('sets the item at the specified index', function () { return initState()