exp.js

// *****************************************************************************
// Copyright 2021-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 as = require('bindings')('aerospike.node')
const exp = as.exp
const writeFlags = as.expWriteFlags
const readFlags = as.expReadFlags
const BIN_TYPE_UNDEF = 0

/**
 * @module aerospike/exp
 * @summary {@link module:aerospike/exp|aerospike/exp} module
 *
 * @description This module defines a filter expression that is
 * a mechanism for additional filtering.
 *
 * The resultset of a primary index (PI) query (scan) or secondary
 * index (SI) query operation can be filtered through the QueryPolicy
 * and ScanPolicy classes. It can also filter single key operations and
 * batch operations through the filterExpression field of their policy class.
 *
 * Filter Expressions replace PredicateExpression filtering,
 * which was deprecated in server 5.2 and
 * removed in server 6.0.
 *
 * @see {@link ReadPolicy#filterExpression}
 * @see {@link OperatePolicy#filterExpression}
 * @see {@link RemovePolicy#filterExpression}
 * @see {@link WritePolicy#filterExpression}
 * @see {@link BatchPolicy#filterExpression}
 * @see {@link ScanPolicy#filterExpression}
 * @see {@link QueryPolicy#filterExpression}
 *
 * @example
 *
 * <caption>Expressions using the operate API</caption>
 * const Aerospike = require('aerospike')
 * const op = Aerospike.operations
 * const exp = Aerospike.exp
 * const key = new Aerospike.Key('test', 'demo', 'mykey1')
 * const tempBin = 'ExpVar' // this bin is to hold expression read operation output
 *
 * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE!
 * const 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}),
 *     write : new Aerospike.WritePolicy({socketTimeout : 0, totalTimeout : 0})
 *   }
 * }
 *
 * var ops = [
 *   op.append('a', 'xyz'),
 *   op.incr('b', 10),
 *   exp.operations.read(tempBin,
 *           exp.add(exp.binInt('b'), exp.binInt('b')),
 *          0),
 *   op.read('a'),
 *   op.read('b')
 * ]
 *
 * Aerospike.connect(config, (error, client) => {
 *   if (error) throw error
 *   client.put(key, { a: 'abc', b: 42 }, (error) => {
 *     if (error) throw error
 *     client.operate(key, ops, (error, record) => {
 *       if (error) throw error
 *       console.log(record.bins) // => { a: 'abcxyz', b: 52, ExpVar: 104 }
 *       client.close()
 *     })
 *   })
 * })
 *
 * @example
 *
 * <caption>Expressions using the query API</caption>
 * const Aerospike = require('aerospike')
 * const op = Aerospike.operations
 * const exp = Aerospike.exp
 * const key = new Aerospike.Key('test', 'demo', 'mykey1')
 * const tempBin = 'ExpVar' // this bin is to hold expression read operation output
 *
 * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE!
 * const config = {
 *   hosts: '192.168.33.10:3000',
 *   // Timeouts disabled, latency dependent on server location. Configure as needed.
 *   policies: {
 *     query : new Aerospike.QueryPolicy({socketTimeout : 0, totalTimeout : 0}),
 *     write : new Aerospike.WritePolicy({socketTimeout : 0, totalTimeout : 0, exists : Aerospike.policy.exists.REPLACE})
 *   }
 * }
 *
 * Aerospike.connect(config, (error, client) => {
 *   if (error) throw error
 *   client.put(key, { a: 'abc', b: 42 }, (error) => {
 *     if (error) throw error
 *     var query = client.query('test', 'demo')
 *     const queryPolicy = { filterExpression:  exp.eq(exp.binInt('b'), exp.int(42))}
 *     query.nobins = false
 *     const stream = query.foreach(queryPolicy)
 *     stream.on('error', (error) => {
 *       console.error(error)
 *       throw error
 *     })
 *     stream.on('data', (record) => {
 *       console.info(record.bins) // => { a: 'abc', b: 42}
 *     })
 *     stream.on('end', () => {
 *       client.close()
 *     })
 *   })
 * })
 *
 */

/*********************************************************************************
 * VALUE EXPRESSIONS
 *********************************************************************************/

const _valueExp = (op, valName) => (value) => [{ op, [valName]: value }]

const _valueExpBytes = (op, valName, sizeName) => (value, size) => [{ op, [valName]: value, [sizeName]: size }]

/**
 * Create boolean value.
 *
 * @function
 * @param {boolean} value boolean value.
 * @return {AerospikeExp}
*/
exports.bool = _valueExp(exp.ops.VAL_BOOL, 'boolVal')

/**
 * Create 64 bit signed integer value.
 *
 * @function
 * @param {number} number value integer value.
 * @return {AerospikeExp}
 */
exports.int = _valueExp(exp.ops.VAL_INT, 'intVal')
const _int = exports.int

/**
 * Create 64 bit unsigned integer value.
 *
 * @function
 * @param {number} number value unsigned integer value.
 * @return {AerospikeExp}
 */
exports.uint = _valueExp(exp.ops.VAL_UINT, 'uintVal')

/**
 * Create 64 bit floating point value.
 *
 * @function
 * @param {number} value floating point value.
 * @return {AerospikeExp}
*/
exports.float = _valueExp(exp.ops.VAL_FLOAT, 'floatVal')

/**
 * Create string value.
 *
 * @function
 * @param {string} value string value.
 * @return {AerospikeExp}
 */
exports.str = _valueExp(exp.ops.VAL_STR, 'strVal')

/**
 * Create byte array value.
 * *
 * @function
 * @param {string[]} value byte array value.
 * @param {number} size number of bytes.
 * @return {AerospikeExp}
 */
exports.bytes = _valueExpBytes(exp.ops.VAL_BYTES, 'bytesVal', 'sz')

/**
 * Create geojson value.
 *
 * @function
 * @param {Object} value geojson value.
 * @return {AerospikeExp}
 */
exports.geo = _valueExp(exp.ops.VAL_GEO, 'value')

/**
 * Create 'nil' value.
 *
 * @function
 * @return {AerospikeExp}
 */
exports.nil = () => [{ op: exp.ops.AS_VAL, value: null }]

/**
 * Create 'inf' value.
 *
 * @function
 * @return {AerospikeExp}
 */
exports.inf = () => [{ op: exp.ops.AS_VAL, inf: null }]

/**
 * Create 'wildcard' value.
 *
 * @function
 * @return {AerospikeExp}
 */
exports.wildcard = () => [{ op: exp.ops.AS_VAL, wildcard: null }]

const _val = _valueExp(exp.ops.AS_VAL, 'value')

/**
 * Create list value.
 *
 * @function
 * @param {array} value list value
 * @return {AerospikeExp}
 */
exports.list = _val

/**
 * Create map value.
 *
 * @function
 * @param {array} value map value
 * @return {AerospikeExp}
 */
exports.map = _val

/*********************************************************************************
 * KEY EXPRESSIONS
 *********************************************************************************/

const _keyTypeExp = (type) => () => [
  { op: exp.ops.KEY, count: 2 },
  ..._int(type)
]

/**
 * Create expression that returns the key as an integer. Returns 'unknown' if
 * the key is not an integer.
 *
 * @function
 * @param {number} integer value Integer value of the key if the key is an integer.
 * @return {AerospikeExp}
*/
exports.keyInt = _keyTypeExp(exp.type.INT)

/**
 * Create expression that returns the key as an string. Returns 'unknown' if
 * the key is not a string.
 *
 * @function
 * @param {string} string value String value of the key if the key is a string.
 * @return {AerospikeExp}
 */
exports.keyStr = _keyTypeExp(exp.type.STR)

/**
 * Create expression that returns the key as an blob. Returns 'unknown' if
 * the key is not an blob.
 *
 * @function
 * @param {Object} blob Blob value of the key if the key is a blob.
 * @return {AerospikeExp}
 */
exports.keyBlob = _keyTypeExp(exp.type.BLOB)

/**
 * Create expression that returns if the primary key is stored in the record meta
 * data as a boolean expression. This would occur when "policy write key" is
 * SEND on record write.
 *
 * @function
 * @param {boolean} - value True if the record has a stored key, false otherwise.
 * @return {AerospikeExp}
 */
exports.keyExist = () => [{ op: exp.ops.KEY_EXIST, count: 1 }]

/*********************************************************************************
 * BIN EXPRESSIONS
 *********************************************************************************/

const _rawStr = (value) => [{ op: exp.ops.VAL_RAWSTR, strVal: value }]

const _binTypeExp = (type) => (binName) => [
  { op: exp.ops.BIN, count: 3 },
  ..._int(type),
  ..._rawStr(binName)
]

/**
 * Create expression that returns a bin as a boolean value. Returns 'unknown'
 * if the bin is not a boolean.
 *
 * @function
 * @param {string }binName Bin name.
 * @return {AerospikeExp} boolean bin
 */
exports.binBool = _binTypeExp(exp.type.BOOL)

/**
 * Create expression that returns a bin as a signed integer. Returns 'unknown'
 * if the bin is not an integer.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} integer bin
 */
exports.binInt = _binTypeExp(exp.type.INT)

/**
 * Create expression that returns a bin as a float. Returns 'unknown' if the bin
 * is not an float.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} float bin
 */
exports.binFloat = _binTypeExp(exp.type.FLOAT)

/**
 * Create expression that returns a bin as a string. Returns 'unknown' if the
 * bin is not an string.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} string bin
 */
exports.binStr = _binTypeExp(exp.type.STR)

/**
 * Create expression that returns a bin as a blob. Returns 'unknown' if the bin
 * is not an blob.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} blob bin
 */
exports.binBlob = _binTypeExp(exp.type.BLOB)

/**
 * Create expression that returns a bin as a geojson. Returns 'unknown' if the
 * bin is not geojson.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} geojson bin
 */
exports.binGeo = _binTypeExp(exp.type.GEOJSON)

/**
 * Create expression that returns a bin as a list. Returns 'unknown' if the bin
 * is not an list.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} list bin
 */
exports.binList = _binTypeExp(exp.type.LIST)

/**
 * Create expression that returns a bin as a map. Returns 'unknown' if the bin
 * is not an map.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} map bin
 */
exports.binMap = _binTypeExp(exp.type.MAP)

/**
 * Create expression that returns a bin as a HyperLogLog (hll). Returns
 * 'unknown' if the bin is not a HyperLogLog (hll).
 *
 * @function
 * @param {string} binName Bin name.
 * @return {AerospikeExp} hll bin
 */
exports.binHll = _binTypeExp(exp.type.HLL)

exports.binType = (binName) => [
  { op: exp.ops.BIN_TYPE, count: 2 },
  ..._rawStr(binName)
]
const _binType = exports.binType

/**
 * Create expression that returns if bin of specified name exists.
 *
 * @function
 * @param {string} binName Bin name.
 * @return {boolean} - value True if the bin exists, false otherwise.
 */
exports.binExists = (binName) => _ne(_binType(binName), _int(BIN_TYPE_UNDEF))

/*********************************************************************************
 * METADATA EXPRESSIONS
 *********************************************************************************/

const _metaExp = (op) => () => [{ op, count: 1 }]

/**
 * Create expression that returns record set name string. This expression usually
 * evaluates quickly because record meta data is cached in memory.
 *
 * @function
 * @return {AerospikeExp} string value Name of the set this record belongs to.
 */
exports.setName = _metaExp(exp.ops.SET_NAME)

/**
 * Create expression that returns record size on disk. If server storage-engine is
 * memory, then zero is returned. This expression usually evaluates quickly
 * because record meta data is cached in memory.
 * Requires server version between 5.3.0 inclusive and 7.0 exclusive.
 * Use {@link #recordSize} for server version 7.0+.
 *
 * @function
 * @return {AerospikeExp} integer value Uncompressed storage size of the record.
 */
exports.deviceSize = _metaExp(exp.ops.DEVICE_SIZE)

/**
 * Create expression that returns record last update time expressed as 64 bit
 * integer nanoseconds since 1970-01-01 epoch.
 *
 * @function
 * @return {AerospikeExp} integer value When the record was last updated.
 */
exports.lastUpdate = _metaExp(exp.ops.LAST_UPDATE)

/**
 * Create expression that returns milliseconds since the record was last updated.
 * This expression usually evaluates quickly because record meta data is cached
 * in memory.
 *
 * @function
 * @return {AerospikeExp} integer value Number of milliseconds since last updated.
 */
exports.sinceUpdate = _metaExp(exp.ops.SINCE_UPDATE)

/**
 * Create expression that returns record expiration time expressed as 64 bit
 * integer nanoseconds since 1970-01-01 epoch.
 *
 * @function
 * @return {AerospikeExp} integer value Expiration time in nanoseconds since 1970-01-01.
 */
exports.voidTime = _metaExp(exp.ops.VOID_TIME)

/**
 * Create expression that returns record expiration time (time to live) in integer
 * seconds.
 *
 * @function
 * @return {AerospikeExp} integer value Number of seconds till the record will expire,
 *                         returns -1 if the record never expires.
 */
exports.ttl = _metaExp(exp.ops.TTL)

/**
 * Create expression that returns if record has been deleted and is still in
 * tombstone state. This expression usually evaluates quickly because record
 * meta data is cached in memory.
 *
 * @function
 * @return {AerospikeExp} - value True if the record is a tombstone, false otherwise.
 */
exports.isTombstone = _metaExp(exp.ops.IS_TOMBSTONE)

/**
 * Create expression that returns record size in memory when either the
 * storage-engine is memory or data-in-memory is true, otherwise returns 0.
 * This expression usually evaluates quickly because record meta data is cached
 * in memory.
 * Requires server version between 5.3.0 inclusive and 7.0 exclusive.
 * Use {@link #recordSize} for server version 7.0+.
 *
 * @function
 * @return {AerospikeExp} integer value memory size of the record.
 */
exports.memorySize = _metaExp(exp.ops.MEMORY_SIZE)

/**
 * Create expression that returns the record size. This expression usually evaluates
 * quickly because record meta data is cached in memory.
 * Requires server version 7.0+. This expression replaces {@link #deviceSize} and
 * {@link #memorySize} since those older expressions are equivalent on server version 7.0+.
 *
 * @function
 * @return {AerospikeExp} integer value size of the record in Megabytes.
 */
exports.recordSize = _metaExp(exp.ops.RECORD_SIZE)

/**
 * Create expression that returns record digest modulo as integer.
 *
 * @function
 * @param {number} mod Divisor used to divide the digest to get a remainder.
 * @return {AerospikeExp} integer value Value in range 0 and mod (exclusive)..
 */
exports.digestModulo = _metaExp(exp.ops.DIGEST_MODULO)

/*********************************************************************************
 * COMPARISON EXPRESSIONS
 *********************************************************************************/

const _cmpExp = (op) => (left, right) => [{ op, count: 3 }].concat(left, right)

/**
 * Create equals (==) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.eq = _cmpExp(exp.ops.CMP_EQ)

/**
 * Create not equal (!=) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.ne = _cmpExp(exp.ops.CMP_NE)
const _ne = exports.ne

/**
 * Create a greater than (>) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.gt = _cmpExp(exp.ops.CMP_GT)

/**
 * Create a greater than or equals (>=) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.ge = _cmpExp(exp.ops.CMP_GE)

/**
 * Create a less than (<) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.lt = _cmpExp(exp.ops.CMP_LT)

/**
 * Create a less than or equals (<=) expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.le = _cmpExp(exp.ops.CMP_LE)

/**
 * Create expression that performs a regex match on a string bin or value
 * expression.
 *
 * @function
 * @param {number} options POSIX regex flags defined in regex.h.
 * @param {string} regex POSIX regex string.
 * @param {AerospikeExp} cmpStr String expression to compare against.
 * @return {AerospikeExp} - boolean value
 */
exports.cmpRegex = (options, regex, cmpStr) => [
  { op: exp.ops.CMP_REGEX, count: 4 },
  ..._int(options),
  ..._rawStr(regex),
  ...cmpStr
]

/**
 * Create a point within region or region contains point expression.
 *
 * @function
 * @param {number} left left expression in comparison.
 * @param {number} right right expression in comparison.
 * @return {AerospikeExp} - boolean value
 */
exports.cmpGeo = _cmpExp(exp.ops.CMP_GEO)

/*********************************************************************************
 * LOGICAL EXPRESSIONS
 *********************************************************************************/

/**
 * Create "not" (!) operator expression.
 *
 * @function
 * @param {AerospikeExp} expr Boolean expression to negate.
 * @return {AerospikeExp} - boolean value
 */
exports.not = (expr) => [
  { op: exp.ops.NOT, count: 2 },
  ...expr
]

const _VAExp = (op) => (...expr) => [].concat(
  { op },
  ...expr,
  { op: exp.ops.END_OF_VA_ARGS }
)

/**
 * Create "and" (&&) operator that applies to a variable number of expressions.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of boolean expressions.
 * @return {AerospikeExp} - boolean value
 */
exports.and = _VAExp(exp.ops.AND)

/**
 * Create "or" (||) operator that applies to a variable number of expressions.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of boolean expressions.
 * @return {AerospikeExp} - boolean value
 */
exports.or = _VAExp(exp.ops.OR)

/**
 * Create expression that returns true if only one of the expressions are true.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of boolean expressions.
 * @return {AerospikeExp} - boolean value
 */
exports.exclusive = _VAExp(exp.ops.EXCLUSIVE)

/*********************************************************************************
 * ARITHMETIC EXPRESSIONS
 *********************************************************************************/

/**
 * Create "add" (+) operator that applies to a variable number of expressions.
 * Return the sum of all arguments.
 * All arguments must be the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number[]} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.add = _VAExp(exp.ops.ADD)

/**
 * Create "subtract" (-) operator that applies to a variable number of expressions.
 * If only one argument is provided, return the negation of that argument.
 * Otherwise, return the sum of the 2nd to Nth argument subtracted from the 1st
 * argument. All arguments must resolve to the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number[]} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.sub = _VAExp(exp.ops.SUB)

/**
 * Create "multiply" (*) operator that applies to a variable number of expressions.
 * Return the product of all arguments. If only one argument is supplied, return
 * that argument. All arguments must resolve to the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number[]} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.mul = _VAExp(exp.ops.MUL)

/**
 * Create "divide" (/) operator that applies to a variable number of expressions.
 * If there is only one argument, returns the reciprocal for that argument.
 * Otherwise, return the first argument divided by the product of the rest.
 * All arguments must resolve to the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number[]} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.div = _VAExp(exp.ops.DIV)

const _paramsExp = (op) => (...params) => [
  { op, count: params.length + 1 },
  ...params
]

/**
 * Create "pow" operator that raises a "base" to the "exponent" power.
 * All arguments must resolve to floats.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} base Base value.
 * @param {number} exponent Exponent value.
 * @return {AerospikeExp} float value
 */
exports.pow = _paramsExp(exp.ops.POW)

/**
 * Create "log" operator for logarithm of "num" with base "base".
 * All arguments must resolve to floats.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} num Number.
 * @param {number}base Base value.
 * @return {AerospikeExp} float value
 */
exports.log = _paramsExp(exp.ops.LOG)

/**
 * Create "modulo" (%) operator that determines the remainder of "numerator"
 * divided by "denominator". All arguments must resolve to integers.
 * Requires server version 5.6.0+.
 *
 * @function
 * @return {AerospikeExp} integer value
 */
exports.mod = _paramsExp(exp.ops.MOD)

/**
 * Create operator that returns absolute value of a number.
 * All arguments must resolve to integer or float.
 * Requires server version 5.6.0+.
 *
 * @function
 * @return {AerospikeExp} number value
 */
exports.abs = _paramsExp(exp.ops.ABS)

/**
 * Create expression that rounds a floating point number down to the closest integer value.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} num Floating point value to round down.
 * @return {AerospikeExp} float-value
 */
exports.floor = _paramsExp(exp.ops.FLOOR)

/**
 * Create expression that rounds a floating point number up to the closest integer value.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} num Floating point value to round up.
 * @return {AerospikeExp} integer-value
 */
exports.ceil = _paramsExp(exp.ops.CEIL)

/**
 * Create expression that converts a float to an integer.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} num Integer to convert to a float
 * @return {AerospikeExp} float value
 */
exports.toInt = _paramsExp(exp.ops.TO_INT)

/**
 * Create expression that converts an integer to a float.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {number} num Integer to convert to a float
 * @return {AerospikeExp} float value
 */
exports.toFloat = _paramsExp(exp.ops.TO_FLOAT)

/**
 * Create integer "and" (&) operator that is applied to two or more integers.
 * All arguments must resolve to integers.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of integer expressions.
 * @return {AerospikeExp} integer value
 */
exports.intAnd = _VAExp(exp.ops.INT_AND)

/**
 * Create integer "or" (|) operator that is applied to two or more integers.
 * All arguments must resolve to integers.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of integer expressions.
 * @return {AerospikeExp} integer value
 */
exports.intOr = _VAExp(exp.ops.INT_OR)

/**
 * Create integer "xor" (^) operator that is applied to two or more integers.
 * All arguments must resolve to integers.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of integer expressions.
 * @return {AerospikeExp} integer value
 */
exports.intXor = _VAExp(exp.ops.INT_XOR)

/**
 * Create integer "not" (~) operator.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} expr Integer expression.
 * @return {AerospikeExp} integer value
 */
exports.intNot = _paramsExp(exp.ops.INT_NOT)

/**
 * Create integer "left shift" (<<) operator.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} value Integer expression.
 * @param {number} shift Number of bits to shift by.
 * @return {AerospikeExp} integer value
 */
exports.intLshift = _paramsExp(exp.ops.INT_LSHIFT)

/**
 * Create integer "logical right shift" (>>>) operator.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} value Integer expression.
 * @param {number} shift Number of bits to shift by.
 * @return {AerospikeExp} integer value
 */
exports.intRshift = _paramsExp(exp.ops.INT_RSHIFT)

/**
 * Create integer "arithmetic right shift" (>>) operator.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} value Integer expression.
 * @param {number} shift Number of bits to shift by.
 * @return {AerospikeExp} integer value
 */
exports.intArshift = _paramsExp(exp.ops.INT_ARSHIFT)

/**
 * Create expression that returns count of integer bits that are set to 1.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp}
 * @return {AerospikeExp} integer value
 */
exports.intCount = _paramsExp(exp.ops.INT_COUNT)

/**
 * Create expression that scans integer bits from left (most significant bit) to
 * right (least significant bit), looking for a search bit value. When the
 * search value is found, the index of that bit (where the most significant bit is
 * index 0) is returned. If "search" is true, the scan will search for the bit
 * value 1. If "search" is false it will search for bit value 0.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp}
 * @return {AerospikeExp} integer value
 */
exports.intLscan = _paramsExp(exp.ops.INT_LSCAN)

/**
 * Create expression that scans integer bits from right (least significant bit) to
 * left (most significant bit), looking for a search bit value. When the
 * search value is found, the index of that bit (where the most significant bit is
 * index 0) is returned. If "search" is true, the scan will search for the bit
 * value 1. If "search" is false it will search for bit value 0.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp}
 * @return {AerospikeExp} integer value
 */
exports.intRscan = _paramsExp(exp.ops.INT_RSCAN)

/**
 * Create expression that returns the minimum value in a variable number of expressions.
 * All arguments must be the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.min = _VAExp(exp.ops.MIN)

/**
 * Create expression that returns the maximum value in a variable number of expressions.
 * All arguments must be the same type (integer or float).
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of integer or float expressions.
 * @return {AerospikeExp} integer or float value
 */
exports.max = _VAExp(exp.ops.MAX)

/*********************************************************************************
 * FLOW CONTROL AND VARIABLE EXPRESSIONS
 *********************************************************************************/
/**
 * Conditionally select an expression from a variable number of expression pairs
 * followed by default expression action. Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp}
 * @return {AerospikeExp} first action expression where bool expression is true or action-default.
 */
exports.cond = _VAExp(exp.ops.COND)

/**
 * Define variables and expressions in scope.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {AerospikeExp} ... Variable number of expression def followed by a scoped
 *  expression.
 * @return {AerospikeExp} result of scoped expression.
 */
exports.let = _VAExp(exp.ops.LET)

/**
 * Assign variable to an expression that can be accessed later.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {string} varName Variable name.
 * @param {AerospikeExp} expr The variable is set to the result of expr.
 * @return {AerospikeExp} A variable name expression pair.
 */
exports.def = (varName, expr) => [
  ..._rawStr(varName),
  ...expr
]

/**
 * Retrieve expression value from a variable.
 * Requires server version 5.6.0+.
 *
 * @function
 * @param {string} varName Variable name.
 * @return {AerospikeExp} value stored in variable.
 */
exports.var = (varName) => [
  { op: exp.ops.VAR, count: 2 },
  ..._rawStr(varName)
]

/**
 * The {@link module:aerospike/exp/lists|aerospike/exp/lists} module defines functions
 * for expressions on the List datatype.
 *
 * @summary List expressions.
 */
exports.lists = require('./exp_lists')

/**
 * The {@link module:aerospike/exp/maps|aerospike/exp/maps} module defines functions
 * for expressions on the Map datatype.
 *
 * @summary Map expressions.
 */
exports.maps = require('./exp_maps')

/**
 * The {@link module:aerospike/exp/bit|aerospike/exp/bit} module defines functions
 * for expressions on the Blob datatype
 *
 * @summary Blob expressions.
 */
exports.bit = require('./exp_bit')

/**
 * The {@link module:aerospike/exp/hll|aerospike/exp/hll} module defines functions
 * for expressions on the HyperLogLog datatype
 *
 * @summary HyperLogLog expressions.
 */
exports.hll = require('./exp_hll')

/**
 *
 * @readonly
 * @enum {number}
 * @description Expression read bit flags. Use BITWISE OR to combine flags.
 */
exports.expReadFlags = {
  /**
   * Default.
   * @const {number}
   */
  DEFAULT: readFlags.DEFAULT,

  /**
   * Ignore failures caused by the expression resolving to unknown or a non-bin type.
   * @const {number}
   */
  EVAL_NO_FAIL: readFlags.EVAL_NO_FAIL
}

/**
 *
 * @readonly
 * @enum {number}
 * @description Expression write bit flags. Use BITWISE OR to combine flags.
 */
exports.expWriteFlags = {
  /**
   * Default.
   * @const {number}
   */
  DEFAULT: writeFlags.DEFAULT,

  /**
   * If bin does not exist, a new bin will be created.
   * @const {number}
   */
  CREATE_ONLY: writeFlags.CREATE_ONLY,

  /**
   * If bin exists, the bin will be overwritten.
   * @const {number}
   */
  UPDATE_ONLY: writeFlags.UPDATE_ONLY,

  /**
   * If expression results in nil value, then delete the bin.
   * @const {number}
   */
  ALLOW_DELETE: writeFlags.ALLOW_DELETE,

  /**
   *  Do not raise error if operation is denied.
   * @const {number}
   */
  POLICY_NO_FAIL: writeFlags.POLICY_NO_FAIL,

  /**
   * Ignore failures caused by the expression resolving to unknown or a non-bin type.
   * @const {number}
   */
  EVAL_NO_FAIL: writeFlags.EVAL_NO_FAIL
}