aerospike
Version:
Aerospike Client Library
715 lines (455 loc) • 24.4 kB
text/typescript
// *****************************************************************************
// Copyright 2022-2023 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// *****************************************************************************
/* eslint-env mocha */
/* global expect */
import {GeoJSON} from '../lib/aerospike.js'
import type { maps as Maps, AerospikeBins, RecordMetadata, Key, AerospikeRecord, AerospikeExp, operations, exp as expModule, cdt} from '../lib/aerospike.js';
import * as Aerospike from '../lib/aerospike.js';
const Context: typeof cdt.Context = Aerospike.cdt.Context
import { expect, assert } from 'chai';
import * as helper from './test_helper.ts';
const exp: typeof expModule = Aerospike.exp
const op: typeof operations = Aerospike.operations
const pathSelectFlags: any = exp.pathSelectFlags
const pathModifyFlags: any = exp.pathModifyFlags
const loopVarPart: any = exp.loopVarPart
const type: any = exp.type
const keygen = helper.keygen
const tempBin = 'ExpVar'
describe('Path Operations', async function () {
const client = helper.client
helper.skipUnlessVersion('>= 5.0.0', this)
const key: Key = new Aerospike.Key(helper.namespace, helper.set, 1)
const addAllChildren: cdt.Context = new Context().addAllChildren()
const doubleAddAllChildren: cdt.Context = new Context().addAllChildren().addAllChildren()
async function verifySelectByPath(bin: string, context: cdt.Context, flags: number, expected: any) {
const ops: operations.Operation[] = [
op.selectByPath(bin, flags, context)
]
const result: any = await client.operate(key, ops)
expect(result.bins[bin]).to.eql(expected)
}
async function verifyModifyByPath(bin: string, context: cdt.Context, expression: AerospikeExp, flags: number, expected: any) {
const ops: operations.Operation[] = [
op.modifyByPath(bin, expression, flags, context),
]
await client.operate(key, ops)
const result: any = await client.get(key)
expect(expected).to.eql(result.bins[bin])
}
const record = {
c_example: {
book: [{'title': 'abc', 'price': 1.48}, {'title': 'def', 'price': 2.78}]
},
floatMap: {a: 1.5, b: 3.0, c: 4.5},
floatList: [2.4, 4.8, 7.2],
// n=nested
nFloatList: [[2.4, 4.8, 7.2]],
// dn=doubleNested
dnFloatList: [[[2.4, 4.8, 7.2]]],
intList: [2, 4, 6],
nIntList: [[2, 4, 6]],
dnIntList: [[[2, 4, 6]]],
strList: ['bob', 'tom', 'harry'],
nStrList: [['bob', 'tom', 'harry']],
dnStrList: [[['bob', 'tom', 'harry']]],
blobList: [Buffer.from('bob'), Buffer.from('tom'), Buffer.from('harry')],
nBlobList: [[Buffer.from('bob'), Buffer.from('tom'), Buffer.from('harry')]],
dnBlobList: [[[Buffer.from('bob'), Buffer.from('tom'), Buffer.from('harry')]]],
boolList: [false, true, false],
nBoolList: [[false, true, false]],
dnBoolList: [[[false, true, false]]],
nilList: [null, null, null],
geoList: [new GeoJSON.Point(50.913, 50.308), new GeoJSON.Point(0.913, 0.308), new GeoJSON.Point(0.913, 0.308)],
mapList: [{a: 1}, {b: 2}, {c: 3}],
listList: [[1], [2], [3]],
}
context('Positive tests', function () {
context('modifyByPath', function () {
context('arguments', function () {
context('bin', function () {
it('accepts exp.mapBin', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('c_example', addAllChildren, modExpression, pathModifyFlags.DEFAULT, {book: 14})
})
})
context('valueTypes', function () {
it('accepts exp.type.MAP', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('floatMap', addAllChildren, modExpression, pathModifyFlags.DEFAULT, {a: 14, b: 14, c: 14})
})
it('accepts exp.type.LIST', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('floatList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [14, 14, 14])
})
})
context('flags', function () {
context('pathSelectFlags', function () {
context('MATCHING_TREE', function () {
const flags = pathModifyFlags.DEFAULT
const noFailFlags = pathModifyFlags.DEFAULT | pathModifyFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(0)
expect(noFailFlags).to.eql(16)
})
it('returns the correct value when used with operate', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('floatMap', addAllChildren, modExpression, flags, {a: 14, b: 14, c: 14})
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('floatMap', addAllChildren, modExpression, flags, {a: 14, b: 14, c: 14})
})
})
context('NO_FAIL', function () {
it('equals the correct numeric value', async function () {
expect(pathModifyFlags.NO_FAIL).to.eql(16)
})
})
})
})
context('modExp', function () {
it('modifies with standard expression', async function () {
const modExpression = exp.float(14.0)
await verifyModifyByPath('c_example', addAllChildren, modExpression, pathModifyFlags.DEFAULT, {book: 14})
})
it('modifies with result remove expression', async function () {
const modExpression = exp.resultRemove()
await verifyModifyByPath('c_example', addAllChildren, modExpression, pathModifyFlags.DEFAULT, {})
})
it('modifies with remove result expression', async function () {
const modExpression = exp.removeResult()
await verifyModifyByPath('c_example', addAllChildren, modExpression, pathModifyFlags.DEFAULT, {})
})
it('modifies with standard loop variable expression', async function () {
const modExpression = exp.mul(exp.loopVarFloat(exp.loopVarPart.VALUE), exp.float(3.7))
await verifyModifyByPath('floatList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ 8.88, 17.76, 26.64 ])
})
it('modifies with nested loop variable expression', async function () {
const modExpression = exp.mul(exp.loopVarFloat(exp.loopVarPart.VALUE), exp.float(3.7))
await verifyModifyByPath('nFloatList', doubleAddAllChildren, modExpression, pathModifyFlags.DEFAULT, [[ 8.88, 17.76, 26.64 ]])
})
it('modifies with double nested loop variable expression', async function () {
const ctx: cdt.Context = new Context().addListIndex(0).addAllChildren().addAllChildren()
const modExpression = exp.mul(exp.loopVarFloat(exp.loopVarPart.VALUE), exp.float(3.7))
await verifyModifyByPath('dnFloatList', ctx, modExpression, pathModifyFlags.DEFAULT, [[[ 8.88, 17.76, 26.64 ]]] )
})
context('loopVar', function () {
it('use loopVarFloat Expression', async function () {
const modExpression = exp.cond(exp.gt(exp.loopVarFloat(exp.loopVarPart.VALUE), exp.float(3.6)), exp.float(1.0), exp.float(0.0))
await verifyModifyByPath('floatList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ 0.0, 1.0, 1.0 ] )
})
it('use loopVarInt Expression', async function () {
const modExpression = exp.cond(exp.gt(exp.loopVarInt(exp.loopVarPart.VALUE), exp.int(2)), exp.int(1), exp.int(0))
await verifyModifyByPath('intList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ 0, 1, 1 ] )
})
it('use loopVarStr Expression', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarStr(exp.loopVarPart.VALUE), exp.str("bob")), exp.str("pass"), exp.str("fail"))
await verifyModifyByPath('strList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ "pass", "fail", "fail" ] )
})
it('use loopVarBlob Expression', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarBlob(exp.loopVarPart.VALUE), exp.bytes(Buffer.from('bob'))), exp.bytes(Buffer.from('pass')), exp.bytes(Buffer.from('fail')))
await verifyModifyByPath('blobList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ Buffer.from('pass'), Buffer.from('fail'), Buffer.from('fail') ] )
})
it('use loopVarBool Expression', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarBool(exp.loopVarPart.VALUE), exp.bool(true)), exp.bool(false), exp.bool(true))
await verifyModifyByPath('boolList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ true, false, true ] )
})
it('use loopVarNil Expression', async function () {
const expression = exp.eq(exp.loopVarNil(exp.loopVarPart.VALUE), exp.nil())
const ctx: cdt.Context = new Context().addAllChildrenWithFilter(expression)
expect(expression[2].intVal).to.eql(exp.type.NIL)
await verifySelectByPath('nilList', ctx, pathSelectFlags.MAP_VALUE, [ null, null, null ] )
})
it('use loopVarGeo Expression', async function () {
const modExpression = exp.cond(exp.cmpGeo(exp.geo(new GeoJSON.Circle(50.913, 50.308, 4)), exp.loopVarGeoJSON(exp.loopVarPart.VALUE)), exp.bool(true), exp.bool(false))
await verifyModifyByPath('geoList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ true, false, false ] )
})
it('use loopVarMap Expression', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarMap(exp.loopVarPart.VALUE), exp.map({b: 2})), exp.bool(false), exp.bool(true))
await verifyModifyByPath('mapList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ true, false, true ] )
})
it('use loopVarList Expression', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarList(exp.loopVarPart.VALUE), exp.list([1])), exp.bool(false), exp.bool(true))
await verifyModifyByPath('listList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ false, true, true ] )
})
it('loopVarPart', async function () {
it('use loopVarStr Expression with loopVarPart.VALUE', async function () {
const modExpression = exp.cond(exp.eq(exp.loopVarStr(exp.loopVarPart.VALUE), exp.str("bob")), exp.str("pass"), exp.str("fail"))
await verifyModifyByPath('strList', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ "pass", "fail", "fail" ] )
})
it('use loopVarStr Expression with loopVarPart.KEY', async function () {
const loopVar = exp.loopVarStr(exp.loopVarPart.KEY)
expect(loopVar[2].intVal).to.eql(0)
const modExpression = exp.cond(exp.eq(loopVar, exp.str('a')), exp.str('pass'), exp.str('fail'))
await verifyModifyByPath('floatMap', addAllChildren, modExpression, pathModifyFlags.DEFAULT, { a: 'pass', b: 'fail', c: 'fail' } )
})
it('use loopVarInt Expression with loopVarPart.INDEX', async function () {
const loopVar = exp.loopVarInt(exp.loopVarPart.INDEX)
const modExpression = exp.cond(exp.eq(loopVar, exp.int(1)), exp.bool(true), exp.bool(false))
expect(loopVar[2].intVal).to.eql(2)
await verifyModifyByPath('floatMap', addAllChildren, modExpression, pathModifyFlags.DEFAULT, [ false, true, false ] )
})
})
})
})
})
})
context('selectByPath', function () {
context('arguments', function () {
context('bin', function () {
it('accepts exp.mapBin', async function () {
const ctx: cdt.Context = new Context().addMapKey('book').addAllChildren().addMapKey('price')
await verifySelectByPath('c_example', ctx, pathSelectFlags.MAP_VALUE, [1.48, 2.78])
})
})
context('context', function () {
it('Adds addAllChildren', async function () {
await verifySelectByPath('floatList', addAllChildren, pathSelectFlags.VALUE, record.floatList)
})
it('Adds addAllChildren twice', async function () {
await verifySelectByPath('nFloatList', doubleAddAllChildren, pathSelectFlags.VALUE, record.nFloatList[0])
})
it('Adds addAllChildren with nested value', async function () {
const ctx: cdt.Context = new Context().addListIndex(0).addAllChildren()
await verifySelectByPath('dnFloatList', ctx, pathSelectFlags.VALUE, record.dnFloatList[0])
})
})
context('valueTypes', function () {
it('accepts exp.type.LIST', async function () {
await verifySelectByPath('floatList', addAllChildren, pathSelectFlags.VALUE, record.floatList)
})
it('accepts exp.type.MAP', async function () {
await verifySelectByPath('floatMap', addAllChildren, pathSelectFlags.MATCHING_TREE, record.floatMap)
})
})
context('flags', function () {
context('pathSelectFlags', function () {
context('MATCHING_TREE', function () {
const flags = pathSelectFlags.MATCHING_TREE
const noFailFlags = pathSelectFlags.MATCHING_TREE | pathSelectFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(0)
expect(noFailFlags).to.eql(16)
})
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('floatMap', addAllChildren, flags, record.floatMap)
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('floatMap', addAllChildren, noFailFlags, record.floatMap)
})
})
context('VALUE', function () {
const flags = pathSelectFlags.VALUE
const noFailFlags = pathSelectFlags.VALUE | pathSelectFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(1)
expect(noFailFlags).to.eql(17)
})
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('floatMap', addAllChildren, flags, Object.values(record.floatMap))
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('floatMap', addAllChildren, noFailFlags, Object.values(record.floatMap))
})
})
context('LIST_VALUE', function () {
const flags = pathSelectFlags.LIST_VALUE
const noFailFlags = pathSelectFlags.LIST_VALUE | pathSelectFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(1)
expect(noFailFlags).to.eql(17)
})
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('nFloatList', addAllChildren, flags, record.nFloatList)
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('nFloatList', addAllChildren, noFailFlags, record.nFloatList)
})
})
context('MAP_VALUE', function () {
const flags = pathSelectFlags.MAP_VALUE
const noFailFlags = pathSelectFlags.MAP_VALUE | pathSelectFlags.NO_FAIL
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('floatMap', addAllChildren, flags, Object.values(record.floatMap))
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('floatMap', addAllChildren, noFailFlags, Object.values(record.floatMap))
})
})
context('MAP_KEY', function () {
const flags = pathSelectFlags.MAP_KEY
const noFailFlags = pathSelectFlags.MAP_KEY | pathSelectFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(2)
expect(noFailFlags).to.eql(18)
})
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('floatMap', addAllChildren, flags, Object.keys(record.floatMap))
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('floatMap', addAllChildren, noFailFlags, Object.keys(record.floatMap))
})
})
context('MAP_KEY_VALUE', function () {
const flags = pathSelectFlags.MAP_KEY_VALUE
const noFailFlags = pathSelectFlags.MAP_KEY_VALUE | pathSelectFlags.NO_FAIL
it('equals the correct numeric value', async function () {
expect(flags).to.eql(3)
expect(noFailFlags).to.eql(19)
})
it('returns the correct value when used with operate', async function () {
await verifySelectByPath('floatMap', addAllChildren, flags, Object.entries(record.floatMap).flat())
})
it('returns the correct value when used with operate and NO_FAIL', async function () {
await verifySelectByPath('floatMap', addAllChildren, noFailFlags, Object.entries(record.floatMap).flat())
})
})
context('NO_FAIL', function () {
it('equals the correct numeric value', async function () {
expect(pathSelectFlags.NO_FAIL).to.eql(16)
})
})
})
})
})
})
})
context('Negative tests', function () {
context('selectByPath', function () {
context('arguments', function () {
context('bin', function () {
it('Does not accept non-string values', async function () {
const ops: operations.Operation[] = [
op.selectByPath(2 as any, exp.pathSelectFlags.MATCHING_TREE, addAllChildren)
]
try{
const result: any = await client.operate(key, ops)
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("Operations array invalid")
}
})
})
context('context', function () {
it('Does not accept non-context values', async function () {
try{
const ops: operations.Operation[] = [
op.selectByPath('floatMap', exp.pathSelectFlags.MATCHING_TREE, null as any)
]
}
catch(error: any){
expect(error.message).to.eql("ctx must be a CDT Context")
}
})
})
context('pathSelectFlags', function () {
it('Does not accept non-number values', async function () {
const ops: operations.Operation[] = [
op.selectByPath('floatMap', 'invalid' as any, addAllChildren)
]
try{
const result: any = await client.operate(key, ops)
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("Operations array invalid")
}
})
})
})
})
context('modifyByPath', function () {
context('arguments', function () {
context('bin', function () {
it('Does not accept non-string values', async function () {
const ops: operations.Operation[] = [
op.modifyByPath(2 as any, exp.float(14.0), exp.pathModifyFlags.DEFAULT, addAllChildren),
]
try{
await client.operate(key, ops)
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("Operations array invalid")
}
})
})
context('context', function () {
it('Does not accept non-number values', async function () {
try{
const ops: operations.Operation[] = [
op.modifyByPath('floatMap', exp.float(14.0), exp.pathModifyFlags.DEFAULT, null as any),
]
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("ctx must be a CDT Context")
}
})
})
context('modExp', function () {
it('Does not accept non-expression values', async function () {
const ops: operations.Operation[] = [
op.modifyByPath('floatMap', null as any, exp.pathModifyFlags.DEFAULT, addAllChildren),
]
try{
await client.operate(key, ops)
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("Operations array invalid")
}
})
})
context('pathModifyFlags', function () {
it('Does not accept non-number values', async function () {
const ops: operations.Operation[] = [
op.modifyByPath('floatMap', exp.float(14.0), null as any, addAllChildren),
]
try{
await client.operate(key, ops)
assert.fail("An error should have been caught!")
}
catch(error: any){
expect(error.message).to.eql("Operations array invalid")
}
})
})
})
})
})
context('Typescript', function () {
it('selectByPath', function () {
const ops: operations.Operation[] = [
op.selectByPath('floatMap', pathSelectFlags.VALUE, addAllChildren)
]
})
it('modifyByPath', function () {
const ops: operations.Operation[] = [
op.modifyByPath('floatMap', exp.float(14.0), exp.pathModifyFlags.DEFAULT, addAllChildren),
]
})
})
beforeEach(async () => { /* setup */
await client.put(key, record)
})
afterEach(async () => { /* setup */
await client.remove(key)
})
})