aerospike
Version:
Aerospike Client Library
467 lines (407 loc) • 18.4 kB
text/typescript
// *****************************************************************************
// Copyright 2019-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 Aerospike, { Client } from 'aerospike';
import { expect } from 'chai';
import * as helper from './test_helper';
const status_codes = Aerospike.status
const bits = Aerospike.bitwise
const {
assertError,
assertRecordEql,
assertResultEql,
cleanup,
createRecord,
expectError,
initState,
operate
} = require('./util/statefulAsyncTest')
describe('client.operate() - Bitwise operations', function () {
helper.skipUnlessSupportsFeature(Aerospike.features.BLOB_BITS, this)
describe('bitwise.resize', function () {
it('grows the bytes value', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02]) }))
.then(operate(bits.resize('bits', 4)))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02, 0x00, 0x00]) }))
.then(cleanup())
})
it('shrinks the bytes value', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(operate(bits.resize('bits', 2)))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02]) }))
.then(cleanup())
})
context('with resize flags', function () {
context('with resize from front flag', function () {
const resizeFlags = bits.resizeFlags.FROM_FRONT
it('grows the value from the front', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02]) }))
.then(operate(bits.resize('bits', 4, resizeFlags)))
.then(assertRecordEql({ bits: Buffer.from([0x00, 0x00, 0x01, 0x02]) }))
.then(cleanup())
})
})
context('with grow only flag', function () {
const resizeFlags = bits.resizeFlags.GROW_ONLY
it('returns an error when trying to shrink the value', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(expectError())
.then(operate(bits.resize('bits', 2, resizeFlags)))
.then(assertError(status_codes.ERR_REQUEST_INVALID))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(cleanup())
})
})
context('with shrink only flag', function () {
const resizeFlags = bits.resizeFlags.SHRINK_ONLY
it('returns an error when trying to grow the value', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(expectError())
.then(operate(bits.resize('bits', 4, resizeFlags)))
.then(assertError(status_codes.ERR_REQUEST_INVALID))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(cleanup())
})
})
})
context('with bitwise policy', function () {
context('with create-only write flag', function () {
const policy = {
writeFlags: bits.writeFlags.CREATE_ONLY
}
it('creates a new byte value bin and initializes it with zeros', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertRecordEql({ bits: Buffer.from([0x00, 0x00, 0x00, 0x00]), foo: 'bar' }))
.then(cleanup())
})
it('returns an error if the bin already exists', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(expectError())
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertError(status_codes.ERR_BIN_EXISTS))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: bits.writeFlags.CREATE_ONLY | bits.writeFlags.NO_FAIL
}
it('does not update the bin', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x02, 0x03]) }))
.then(cleanup())
})
})
})
context('with update-only write flag', function () {
const policy = {
writeFlags: bits.writeFlags.UPDATE_ONLY
}
it('updates an existing byte value', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]) }))
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertRecordEql({ bits: Buffer.from([0x00, 0x01, 0x02, 0x03]) }))
.then(cleanup())
})
it('returns an error if the bin does not exists', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertError(status_codes.ERR_BIN_NOT_FOUND))
.then(assertRecordEql({ foo: 'bar' }))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: bits.writeFlags.UPDATE_ONLY | bits.writeFlags.NO_FAIL
}
it('does not create the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(bits.resize('bits', 4).withPolicy(policy)))
.then(assertRecordEql({ foo: 'bar' }))
.then(cleanup())
})
})
})
})
})
describe('bitwise.insert', function () {
it('inserts value at the stated offset', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02]) }))
.then(operate(bits.insert('bits', 1, Buffer.from([0x03, 0x04]))))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x03, 0x04, 0x02]) }))
.then(cleanup())
})
})
describe('bitwise.remove', function () {
it('removes number of bytes from the stated offset', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]) }))
.then(operate(bits.remove('bits', 1, 3)))
.then(assertRecordEql({ bits: Buffer.from([0x01, 0x05]) }))
.then(cleanup())
})
})
describe('bitwise.set', function () {
context('with value as Buffer', function () {
it('sets value on bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.set('bits', 13, 3, Buffer.from([0b11100000]))))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000111, 0b00000011, 0b00000100, 0b00000101]) }))
.then(cleanup())
})
})
context('with value as Integer', function () {
it('sets value on bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.set('bits', 1, 8, 127)))
.then(assertRecordEql({ bits: Buffer.from([0b00111111, 0b11000010, 0b00000011, 0b0000100, 0b00000101]) }))
.then(cleanup())
})
})
it('throws a TypeError if passed an unsupported value type', function () {
expect(() => { bits.set('bin', 0, 0, 3.1416) }).to.throw(TypeError)
})
context('with bitwise policy', function () {
context('with no-fail flag', function () {
const policy = {
writeFlags: bits.writeFlags.UPDATE_ONLY | bits.writeFlags.NO_FAIL
}
it('sets value on bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000000]) }))
.then(operate([
bits.set('bits', 4, 8, Buffer.from([0b10101010])).withPolicy(policy)
]))
.then(assertRecordEql({
bits: Buffer.from([0b00000000])
}))
.then(cleanup())
})
})
context('with partial flag', function () {
const policy = {
writeFlags: bits.writeFlags.UPDATE_ONLY | bits.writeFlags.NO_FAIL | bits.writeFlags.PARTIAL
}
it('sets value on bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000000]) }))
.then(operate([
bits.set('bits', 4, 8, Buffer.from([0b10101010])).withPolicy(policy)
]))
.then(assertRecordEql({
bits: Buffer.from([0b00001010])
}))
.then(cleanup())
})
})
})
})
describe('bitwise.or', function () {
it('performs bitwise "or" on value and bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.or('bits', 17, 6, Buffer.from([0b10101000]))))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b01010111, 0b00000100, 0b00000101]) }))
.then(cleanup())
})
})
describe('bitwise.xor', function () {
it('performs bitwise "xor" on value and bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.xor('bits', 17, 6, Buffer.from([0b10101100]))))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b01010101, 0b00000100, 0b00000101]) }))
.then(cleanup())
})
})
describe('bitwise.and', function () {
it('performs bitwise "and" on value and bitmap at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.and('bits', 23, 9, Buffer.from([0b00111100, 0b10000000]))))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000010, 0b00000000, 0b00000101]) }))
.then(cleanup())
})
})
describe('bitwise.not', function () {
it('negates bitmap starting at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.not('bits', 25, 6)))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b01111010, 0b00000101]) }))
.then(cleanup())
})
})
describe('bitwise.lshift', function () {
it('shifts left bitmap start at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.lshift('bits', 32, 8, 3)))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00101000]) }))
.then(cleanup())
})
})
describe('bitwise.rshift', function () {
it('shifts right bitmap start at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.rshift('bits', 0, 9, 1)))
.then(assertRecordEql({ bits: Buffer.from([0b00000000, 0b11000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(cleanup())
})
})
describe('bitwise.add', function () {
it('adds value to bitmap starting at bitOffset for bitSize', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.add('bits', 24, 16, 128, false)))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b10000101]) }))
.then(cleanup())
})
context('with overflow', function () {
context('on overflow fail', function () {
const FAIL = bits.overflow.FAIL
it('returns an error if the addition overflows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b11111111]) }))
.then(expectError())
.then(operate(bits.add('bits', 0, 8, 1, false).onOverflow(FAIL)))
.then(assertError(status_codes.ERR_OP_NOT_APPLICABLE))
.then(assertRecordEql({ bits: Buffer.from([0b11111111]) }))
.then(cleanup())
})
})
context('on overflow saturate', function () {
const SATURATE = bits.overflow.SATURATE
it('sets max value if the addition overlows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b11111100]) }))
.then(operate(bits.add('bits', 0, 8, 100, false).onOverflow(SATURATE)))
.then(assertRecordEql({ bits: Buffer.from([0b11111111]) }))
.then(cleanup())
})
})
context('on overflow wrap', function () {
const WRAP = bits.overflow.WRAP
it('wraps the value if the addition overflows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b11111110]) }))
.then(operate(bits.add('bits', 0, 8, 2, false).onOverflow(WRAP)))
.then(assertRecordEql({ bits: Buffer.from([0b00000000]) }))
.then(cleanup())
})
})
})
})
describe('bitwise.subtract', function () {
it('subracts value from bitmap starting at bitOffset for bitSize', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.subtract('bits', 24, 16, 128, false)))
.then(assertRecordEql({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000011, 0b10000101]) }))
.then(cleanup())
})
context('with overflow', function () {
context('on overflow fail', function () {
const FAIL = bits.overflow.FAIL
it('returns an error if the subtraction underflows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000100]) }))
.then(expectError())
.then(operate(bits.subtract('bits', 0, 8, 10, false).onOverflow(FAIL)))
.then(assertError(status_codes.ERR_OP_NOT_APPLICABLE))
.then(assertRecordEql({ bits: Buffer.from([0b00000100]) }))
.then(cleanup())
})
})
context('on overflow saturate', function () {
const SATURATE = bits.overflow.SATURATE
it('sets min value if the subtraction underflows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000100]) }))
.then(operate(bits.subtract('bits', 0, 8, 10, false).onOverflow(SATURATE)))
.then(assertRecordEql({ bits: Buffer.from([0b00000000]) }))
.then(cleanup())
})
})
context('on overflow wrap', function () {
const WRAP = bits.overflow.WRAP
it('wraps the value if the subtraction underflows', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000100]) }))
.then(operate(bits.subtract('bits', 0, 8, 10, false).onOverflow(WRAP)))
.then(assertRecordEql({ bits: Buffer.from([0b11111010]) }))
.then(cleanup())
})
})
})
})
describe('bitwise.get', function () {
it('returns bits from bitmap starting at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.get('bits', 9, 5)))
.then(assertResultEql({ bits: Buffer.from([0b10000000]) }))
.then(cleanup())
})
})
describe('bitwise.getInt', function () {
it('returns integer from bitmap starting at offset for size', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.getInt('bits', 8, 16, false)))
.then(assertResultEql({ bits: 16899 }))
.then(cleanup())
})
})
describe('bitwise.lscan', function () {
it('returns interger bit offset of the first specified value bit in bitmap', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.lscan('bits', 24, 8, true)))
.then(assertResultEql({ bits: 5 }))
.then(cleanup())
})
})
describe('bitwise.rscan', function () {
it('returns interger bit offset of the last specified value bit in bitmap', function () {
return initState()
.then(createRecord({ bits: Buffer.from([0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101]) }))
.then(operate(bits.rscan('bits', 32, 8, true)))
.then(assertResultEql({ bits: 7 }))
.then(cleanup())
})
})
})