// *****************************************************************************
// Copyright 2013-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.
// *****************************************************************************
'use strict'
const MAX_NAMESPACE_NAME_LENGTH = 32
const MAX_SET_NAME_LENGTH = 64
const DIGEST_LENGTH = 20
/**
* @class Key
*
* @summary A key uniquely identifies a record in the Aerospike database within a given namespace.
*
* @description
*
* ###### Key Digests
* In your application, you must specify the namespace, set and the key itself
* to read and write records. When a key is sent to the database, the key value
* and its set are hashed into a 160-bit digest. When a database operation
* returns a key (e.g. Query or Scan operations) it might contain either the
* set and key value, or just the digest.
*
* @param {string} ns - The Namespace to which the key belongs.
* @param {string} set - The Set to which the key belongs.
* @param {(string|number|Buffer)} key - The unique key value. Keys can be
* strings, integers or an instance of the Buffer class.
* @param {string} [digest] - The digest value of the key.
*
* @example <caption>Creating a new {@link Key} instance</caption>
*
* const Aerospike = require('aerospike')
* const Key = Aerospike.Key
*
* var key1 = new Key('test', 'demo', 12345)
* var key2 = new Key('test', 'demo', 'abcde')
* var key3 = new Key('test', 'demo', Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]))
*/
function Key (ns, set, key, digest) {
/** @member {string} Key#ns */
if (!isValidNamespace(ns)) {
throw new TypeError('Namespace must be a valid string (max. length 32)')
}
this.ns = ns
/** @member {string} [Key#set] */
if (isSet(set) && !isValidSetName(set)) {
throw new TypeError('Set must be a valid string (max. length 64)')
}
this.set = set
/** @member {(string|integer|Buffer)} [Key#key] */
const hasKey = isSet(key)
if (hasKey) validateKey(key)
this.key = key
/**
* @member {Buffer} [Key#digest]
*
* @summary The 160-bit digest used by the Aerospike server to uniquely
* identify a record within a namespace.
*/
const hasDigest = isSet(digest)
if (hasDigest && !isValidDigest(digest)) {
throw new TypeError('Digest must be a 20-byte Buffer')
}
this.digest = digest || null
if (!(hasKey || hasDigest)) {
throw new TypeError('Either key or digest must be set')
}
}
/**
* @private
*/
Key.fromASKey = function (keyObj) {
if (!keyObj) return null
return new Key(keyObj.ns, keyObj.set, keyObj.key, keyObj.digest)
}
Key.prototype.equals = function (other) {
return this.ns === other.ns &&
((!isSet(this.set) && !isSet(other.set)) || this.set === other.set) &&
((!isSet(this.key) && !isSet(other.key)) || this.key === other.key) &&
(!isSet(this.digest) || !isSet(other.digest) || this.digest.equals(other.digest))
}
function isSet (value) {
return typeof value !== 'undefined' && value !== null
}
function isValidNamespace (ns) {
return (typeof ns === 'string') &&
(ns.length > 0) &&
(ns.length <= MAX_NAMESPACE_NAME_LENGTH)
}
function isValidSetName (set) {
return (typeof set === 'string') &&
(set.length > 0) &&
(set.length <= MAX_SET_NAME_LENGTH)
}
function validateStringKey (key) {
if (key.length === 0) {
throw new TypeError('Invalid user key: Empty string not allowed')
}
}
function validateNumberKey (key) {
if (!Number.isInteger(key)) {
throw new TypeError('Invalid user key: Only integer numbers are allowed')
}
}
const isInt64 = global.BigInt ? require('./bigint').isInt64 : () => {}
function validateBigIntKey (key) {
if (!isInt64(key)) {
throw new TypeError('Invalid user key: BigInt key is outside valid range -2^63 ... 2^63-1')
}
}
function validateBufferKey (key) {
if (Buffer.byteLength(key) === 0) {
throw new TypeError('Invalid user key: Empty Buffer not allowed')
}
}
function validateKey (key) {
switch (typeof key) {
case 'string': return validateStringKey(key)
case 'number': return validateNumberKey(key)
case 'bigint': return validateBigIntKey(key)
case 'object':
if (Buffer.isBuffer(key)) {
return validateBufferKey(key)
}
// eslint-disable-next-line: no-fallthrough
default:
throw new TypeError('Invalid user key: Must be string, integer, BigInt, or Buffer')
}
}
function isValidDigest (digest) {
return (Buffer.isBuffer(digest) && digest.length === DIGEST_LENGTH)
}
module.exports = Key