@phenixrts/aerospike
Version:
Aerospike Client Library
462 lines (414 loc) • 15.6 kB
JavaScript
// *****************************************************************************
// Copyright 2020 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 */
const Aerospike = require('../lib/aerospike')
const helper = require('./test_helper')
const hll = Aerospike.hll
const status = Aerospike.status
const {
assertError,
assertRecordEql,
assertResultEql,
assertResultSatisfy,
cleanup,
createRecord,
expectError,
initState,
operate
} = require('./util/statefulAsyncTest')
const isDouble = (number) => typeof number === 'number' && parseInt(number, 10) !== number
describe('client.operate() - HyperLogLog operations', function () {
helper.skipUnlessVersion('>= 4.9.0', this)
// HLL object representing the set ('jaguar', 'leopard', 'lion', 'tiger')
// with an index bit size of 8, and minhash bit size of 0.
const hllCats = Buffer.from([0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
describe('hll.init', function () {
it('initializes a HLL bin value', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.init('hll', 10),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [10, 0] }))
.then(cleanup())
})
it('initializes a HLL bin value with minhash bits', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.init('hll', 10, 6),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [10, 6] }))
.then(cleanup())
})
it('re-initializes an existing HLL bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.add('hll', ['tiger', 'leopard'], 10)))
.then(operate([
hll.init('hll', 12, 4),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [12, 4] }))
.then(cleanup())
})
context('with HLL policy', function () {
context('with create-only write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY
}
it('returns an error if the bin already exists', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.add('hll', ['tiger'], 8)))
.then(expectError())
.then(operate(
hll.init('hll', 10).withPolicy(policy)
))
.then(assertError(status.ERR_BIN_EXISTS))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY | hll.writeFlags.NO_FAIL
}
it('does not re-initialize the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.add('hll', ['tiger', 'cheetah'], 8)))
.then(operate(
hll.init('hll', 12).withPolicy(policy)
))
.then(operate(hll.getCount('hll')))
.then(assertResultEql({ hll: 2 }))
.then(cleanup())
})
})
})
context('with update-only write flag', function () {
const policy = {
writeFlags: hll.writeFlags.UPDATE_ONLY
}
it('returns an error if the bin does not yet exist', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate(
hll.init('hll', 10, 6).withPolicy(policy)
))
.then(assertError(status.ERR_BIN_NOT_FOUND))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: hll.writeFlags.UPDATE_ONLY | hll.writeFlags.NO_FAIL
}
it('does not initialize the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(
hll.init('hll', 10, 6).withPolicy(policy)
))
.then(assertRecordEql({ foo: 'bar' }))
.then(cleanup())
})
})
})
})
})
describe('hll.add', function () {
it('initializes a new HLL value if it does not exist', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.add('hll', ['jaguar', 'tiger', 'tiger', 'leopard', 'lion', 'jaguar'], 8)))
.then(assertResultEql({ hll: 4 }))
.then(assertRecordEql({ hll: hllCats, foo: 'bar' }))
.then(cleanup())
})
it('returns an error if the bin is of wrong type', function () {
return initState()
.then(createRecord({ hll: 'not a HLL set' }))
.then(expectError())
.then(operate(hll.add('hll', ['jaguar', 'tiger', 'tiger', 'leopard', 'lion', 'jaguar'], 8)))
.then(assertError(status.ERR_BIN_INCOMPATIBLE_TYPE))
.then(cleanup())
})
context('with HLL policy', function () {
context('with create-only write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY
}
it('returns an error if bin already exist', async function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.init('hll', 12)))
.then(expectError())
.then(operate(hll.add('hll', ['tiger', 'tiger', 'leopard'], 8).withPolicy(policy)))
.then(assertError(status.ERR_BIN_EXISTS))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY | hll.writeFlags.NO_FAIL
}
it('does not update the bin if it already exists', async function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(hll.add('hll', ['tiger', 'lion'], 8)))
.then(operate(hll.add('hll', ['tiger', 'leopard', 'cheetah'], 8).withPolicy(policy)))
.then(operate(hll.getCount('hll')))
.then(assertResultEql({ hll: 2 }))
.then(cleanup())
})
})
})
})
})
describe('hll.setUnion', function () {
it('sets a union of the HLL objects with the HLL bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['tiger', 'lynx', 'cheetah', 'tiger'], 8),
hll.setUnion('hll', [hllCats]),
hll.getCount('hll')
]))
.then(assertResultEql({ hll: 6 }))
.then(cleanup())
})
it('returns an error if the index bit count does not match', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate([
hll.add('hll', ['tiger', 'lynx', 'cheetah', 'tiger'], 12),
hll.setUnion('hll', [hllCats]) // index bit size = 8
]))
.then(assertError(status.ERR_OP_NOT_APPLICABLE))
.then(cleanup())
})
context('with HLL policy', function () {
context('with create-only write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY
}
it('returns an error if the bin already exists', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate([
hll.add('hll', ['tiger', 'lynx', 'cheetah', 'tiger'], 8),
hll.setUnion('hll', [hllCats]).withPolicy(policy)
]))
.then(assertError(status.ERR_BIN_EXISTS))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: hll.writeFlags.CREATE_ONLY | hll.writeFlags.NO_FAIL
}
it('does not update the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['tiger'], 8),
hll.setUnion('hll', [hllCats]).withPolicy(policy),
hll.getCount('hll')
]))
.then(assertResultEql({ hll: 1 }))
.then(cleanup())
})
})
})
context('with update-only write flag', function () {
const policy = {
writeFlags: hll.writeFlags.UPDATE_ONLY
}
it('returns an error if the bin does not exist', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate(
hll.setUnion('hll', [hllCats]).withPolicy(policy)
))
.then(assertError(status.ERR_BIN_NOT_FOUND))
.then(cleanup())
})
context('with no-fail write flag', function () {
const policy = {
writeFlags: hll.writeFlags.UPDATE_ONLY | hll.writeFlags.NO_FAIL
}
it('does not create the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate(
hll.setUnion('hll', [hllCats]).withPolicy(policy)
))
.then(assertRecordEql({ foo: 'bar' }))
.then(cleanup())
})
})
})
context('with allow-fold write flag', function () {
const policy = {
writeFlags: hll.writeFlags.ALLOW_FOLD
}
it('folds the result to the lowest index bit size', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['tiger', 'lynx', 'cheetah', 'tiger'], 12),
hll.setUnion('hll', [hllCats]).withPolicy(policy), // index bit size = 8
hll.describe('hll')
]))
.then(assertResultEql({ hll: [8, 0] }))
.then(cleanup())
})
})
})
})
describe('hll.refreshCount', function () {
it('updates and then returns the cached count', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['tiger', 'lynx', 'cheetah', 'tiger'], 8),
hll.add('hll', ['lion', 'tiger', 'puma', 'puma']),
hll.fold('hll', 6),
hll.refreshCount('hll')
]))
.then(assertResultEql({ hll: 5 }))
.then(cleanup())
})
})
describe('hll.fold', function () {
it('folds the index bit count to the specified value', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.init('hll', 16),
hll.fold('hll', 8),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [8, 0] }))
.then(cleanup())
})
it('returns an error if the minhash count is not zero', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(expectError())
.then(operate([
hll.init('hll', 16, 8),
hll.fold('hll', 8)
]))
.then(assertError(status.ERR_OP_NOT_APPLICABLE))
.then(cleanup())
})
})
describe('hll.getCount', function () {
it('returns the estimated number of elements in the bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['leopard', 'tiger', 'tiger', 'jaguar'], 8),
hll.getCount('hll')
]))
.then(assertResultEql({ hll: 3 }))
.then(cleanup())
})
})
describe('hll.getUnion', function () {
it('returns the union of the HLL objects with the HLL bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['leopard', 'lynx', 'tiger', 'tiger', 'cheetah', 'lynx'], 8),
hll.getUnion('hll', [hllCats])
]))
.then(assertResultSatisfy(({ hll }) => Buffer.isBuffer(hll)))
.then(cleanup())
})
})
describe('hll.getUnionCount', function () {
it('returns the element count of the union of the HLL objects with the HLL bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['leopard', 'lynx', 'tiger', 'tiger', 'cheetah', 'lynx'], 8),
hll.getUnionCount('hll', [hllCats])
]))
.then(assertResultEql(({ hll: 6 })))
.then(cleanup())
})
})
describe('hll.getIntersectCount', function () {
it('returns the element count of the intersection of the HLL objects with the HLL bin', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['leopard', 'lynx', 'tiger', 'tiger', 'cheetah', 'lynx'], 8),
hll.getIntersectCount('hll', [hllCats])
]))
.then(assertResultEql(({ hll: 2 })))
.then(cleanup())
})
})
describe('hll.getSimilarity', function () {
it('returns the similarity of the HLL objects', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.add('hll', ['leopard', 'lynx', 'tiger', 'tiger', 'cheetah', 'lynx'], 8),
hll.getSimilarity('hll', [hllCats])
]))
.then(assertResultSatisfy(({ hll }) => isDouble(hll)))
.then(cleanup())
})
})
describe('hll.describe', function () {
it('returns the index and minhash bit counts', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.init('hll', 16, 5),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [16, 5] }))
.then(cleanup())
})
it('returns the index count, with minhash zero', function () {
return initState()
.then(createRecord({ foo: 'bar' }))
.then(operate([
hll.init('hll', 16),
hll.describe('hll')
]))
.then(assertResultEql({ hll: [16, 0] }))
.then(cleanup())
})
})
})