policy.js

// *****************************************************************************
// 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 as = require('bindings')('aerospike.node')

const BasePolicy = require('./policies/base_policy')
const ApplyPolicy = require('./policies/apply_policy')
const OperatePolicy = require('./policies/operate_policy')
const QueryPolicy = require('./policies/query_policy')
const ReadPolicy = require('./policies/read_policy')
const RemovePolicy = require('./policies/remove_policy')
const ScanPolicy = require('./policies/scan_policy')
const WritePolicy = require('./policies/write_policy')
const BatchPolicy = require('./policies/batch_policy')
const BatchApplyPolicy = require('./policies/batch_apply_policy')
const BatchReadPolicy = require('./policies/batch_read_policy')
const BatchRemovePolicy = require('./policies/batch_remove_policy')
const BatchWritePolicy = require('./policies/batch_write_policy')

const CommandQueuePolicy = require('./policies/command_queue_policy')
const HLLPolicy = require('./policies/hll_policy')
const InfoPolicy = require('./policies/info_policy')
const ListPolicy = require('./policies/list_policy')
const MapPolicy = require('./policies/map_policy')
const AdminPolicy = require('./policies/admin_policy')
/**
 * @module aerospike/policy
 *
 * @description The policy module defines policies and policy values that
 * define the behavior of database operations. Most {@link Client} methods,
 * including scans and queries, accept a policy object, that affects how the
 * database operation is executed, by specifying timeouts, transactional
 * behavior, etc. Global defaults for specific types of database operations can
 * also be set through the client config, when a new {@link Client} instance is
 * created.
 *
 * Different policies apply to different types of database operations:
 *
 * * {@link ApplyPolicy} - Applies to {@link Client#apply}.
 * * {@link OperatePolicy} - Applies to {@link Client#operate} as well as {@link Client#append}, {@link Client#prepend} and {@link Client#add}.
 * * {@link QueryPolicy} - Applies to {@link Query#apply}, {@link Query#background} and {@link Query#foreach}.
 * * {@link ReadPolicy} - Applies to {@link Client#exists}, {@link Client#get} and {@link Client#select}.
 * * {@link RemovePolicy} - Applies to {@link Client#remove}.
 * * {@link ScanPolicy} - Applies to {@link Scan#background} and {@link Scan#foreach}.
 * * {@link WritePolicy} - Applies to {@link Client#put}.
 * * {@link BatchPolicy} - Applies to {@link Client#batchRead} as well as the
 *   deprecated {@link Client#batchExists}, {@link Client#batchGet}, and {@link
 *   Client#batchSelect} operations.
 * * {@link BatchApplyPolicy} - Applies to {@link Client#batchApply}.
 * * {@link BatchReadPolicy} - Applies to {@link Client#batchRead}.
 * * {@link BatchRemovePolicy} - Applies to {@link Client#batchRemove}.
 * * {@link BatchWritePolicy} - Applies to {@link Client#batchWrite}.
 * * {@link CommandQueuePolicy} - Applies to global command queue {@link module:aerospike.setupGlobalCommandQueue
 * Aerospike.setupGlobalCommandQueue}
 * * {@link HLLPolicy} - Applies to {@link module:aerospike/hll|HLL} operations
 * * {@link InfoPolicy} - Applies to {@link Client#info}, {@link
 *   Client#infoAny}, {@link Client#infoAll} as well as {@link
 *   Client#createIndex}, {@link Client#indexRemove}, {@link Client#truncate},
 *   {@link Client#udfRegister} and {@link Client#udfRemove}.
 * * {@link ListPolicy} - Applies to List operations defined in {@link module:aerospike/lists}.
 * * {@link MapPolicy} - Applies to Map operations defined in {@link module:aerospike/maps}.
 * * {@link AdminPolicy} - Applies to {@link @Client#changePassword}, {@link Client#changePassword},
 *   {@link Client#createUser}, {@link Client#createRole}, {@link Client#dropRole}, {@link Client#dropUser},
 *   {@link Client#grantPrivileges}, {@link Client#grantRoles}, {@link Client#queryRole},
 *   {@link Client#queryRoles}, {@link Client#queryUser}, {@link Client#queryUsers},
 *   {@link Client#revokePrivileges}, {@link Client#revokeRoles}, {@link Client#setQuotas},
 *   and {@link Client#setWhitelist}, .
 *
 * Base policy {@link BasePolicy} class which defines common policy
 * values that apply to all database operations
 * (except `InfoPolicy`, `AdminPolicy`, `MapPolicy` and `ListPolicy`).
 *
 * This module also defines global values for the following policy settings:
 *
 * * {@link module:aerospike/policy.commitLevel|commitLevel} - Specifies the
 * number of replicas required to be successfully committed before returning
 * success in a write operation to provide the desired consistency guarantee.
 * * {@link module:aerospike/policy.exists|exists} - Specifies the behavior for
 * writing the record depending whether or not it exists.
 * * {@link module:aerospike/policy.gen|gen} - Specifies the behavior of record
 * modifications with regard to the generation value.
 * * {@link module:aerospike/policy.key|key} - Specifies the behavior for
 * whether keys or digests should be sent to the cluster.
 * * {@link module:aerospike/policy.readModeAP|readModeAP} - How duplicates
 * should be consulted in a read operation.
 * * {@link module:aerospike/policy.readModeSC|readModeSC} - Determines SC read
 * consistency options.
 * * {@link module:aerospike/policy.replica|replica} - Specifies which
 * partition replica to read from.
 *
 * @example
 *
 * const Aerospike = require('aerospike')
 *
 * const config = {
 *   hosts: '192.168.33.10:3000'
 * }
 *
 * const key = new Aerospike.Key('test', 'demo', 'k1')
 *
 * Aerospike.connect(config)
 *   .then(client => {
 *     let record = {i: 1234}
 *
 *     // Override policy for put command
 *     let policy = new Aerospike.policy.WritePolicy({
 *       exists: Aerospike.policy.exists.CREATE,
 *       key: Aerospike.policy.key.SEND,
 *       socketTimeout: 0,
 *       totalTimeout: 0
 *     })
 *
 *     return client.put(key, record, {}, policy)
 *       .then(() => client.close())
 *       .catch(error => {
 *         client.close()
 *         if (error.code === Aerospike.status.ERR_RECORD_EXISTS) {
 *           console.info('record already exists')
 *         } else {
 *           return Promise.reject(error)
 *         }
 *       })
 *   })
 *   .catch(error => console.error('Error:', error))
 */

/**
 * @summary The generation policy specifies how to handle record writes based
 * on record generation.
 *
 * @description To use the <code>EQ</code> or <code>GT</code> generation policy
 * (see below), the generation value to use for the comparison needs to be
 * specified in the metadata parameter (<code>meta</code>) of the {@link
 * Client#put} operation.
 *
 * @type Object
 * @property IGNORE - Do not use record generation to restrict writes.
 * @property EQ - Update/delete record if expected generation is equal to
 * server generation. Otherwise, fail.
 * @property GT - Update/delete record if expected generation greater than the
 * server generation. Otherwise, fail. This is useful for restore after backup.
 *
 * @example <caption>Update record, only if generation matches</caption>
 *
 * const Aerospike = require('aerospike')
 * const key = new Aerospike.Key('test', 'test', 'myKey')
 * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE!
 * var config = {
 *   hosts: '192.168.33.10:3000',
 *   // Timeouts disabled, latency dependent on server location. Configure as needed.
 *   policies: {
 *     write : new Aerospike.WritePolicy({socketTimeout : 0, totalTimeout : 0})
 *   }
 * }
 * Aerospike.connect(config).then(async (client) => {
 *   await client.put(key, { foo: 'bar' })
 *
 *   const record = await client.get(key)
 *   const gen = record.gen // Current generation of the record. (1 for new record.)
 *   // Perform some operation using record. Some other process might update the
 *   // record in the meantime, which would change the generation value.
 *   if (Math.random() < 0.1) await client.put(key, { foo: 'fox' })
 *
 *   try {
 *     // Update record only if generation is still the same.
 *     const meta = { gen }
 *     const policy = { gen: Aerospike.policy.gen.EQ }
 *     await client.put(key, { status: 'updated' }, meta, policy)
 *     console.log('Record updated successfully.')
 *   } catch (error) {
 *     if (error.code == Aerospike.status.ERR_RECORD_GENERATION) {
 *       console.error('Failed to update record, because generation did not match.')
 *     }
 *   }
 *
 *   client.close()
 * })
 */
exports.gen = as.policy.gen

/**
 * Specifies the behavior for whether keys or digests should be sent to the
 * cluster.
 *
 * @type Object
 *
 * @property DIGEST - Send the digest value of the key. This is the recommended
 * mode of operation. This calculates the digest and sends the digest to the
 * server. The digest is only calculated on the client, and not the server.
 *
 * @property SEND - Send the key, in addition to the digest value. If you want
 * keys to be returned when scanning or querying, the keys must be stored on
 * the server. This policy causes a write operation to store the key. Once the
 * key is stored, the server will keep it - there is no need to use this policy
 * on subsequent updates of the record. If this policy is used on read or
 * delete operations, or on subsequent updates of a record with a stored key,
 * the key sent will be compared with the key stored on the server. A mismatch
 * will cause <code>ERR_RECORD_KEY_MISMATCH</code> to be returned.
 */
exports.key = as.policy.key

/**
 * Specifies the behavior for writing the record depending whether or not it
 * exists.
 *
 * @type Object
 *
 * @property IGNORE - Write the record, regardless of existence.  (I.e. create
 * or update.)
 * @property CREATE - Create a record, ONLY if it doesn't exist.
 * @property UPDATE - Update a record, ONLY if it exists.
 * @property REPLACE - Completely replace a record, ONLY if it exists.
 * @property CREATE_OR_REPLACE - Completely replace a record if it exists,
 * otherwise create it.
 */
exports.exists = as.policy.exists

/**
 * Specifies which partition replica to read from.
 *
 * @property MASTER - Read from the partition master replica node.
 * @property ANY - Distribute reads across nodes containing key's master and
 * replicated partition in round-robin fashion. Currently restricted to master
 * and one prole.
 * @property SEQUENCE - Always try node containing master partition first. If
 * the command times out and the policy settings allow for an automatic retry,
 * try the node containing the prole partition. Currently restricted to master
 * and one prole.
 * @property PREFER_RACK - Try node on the same rack as the client first. If
 * there are no nodes on the same rack, use <code>SEQUENCE</code> instead.
 * {@link Config#rackAware rackAware} config, {@link Config#rackId rackId}
 * config, and server rack configuration must also be set to enable this
 * functionality.
 */
exports.replica = as.policy.replica

/**
 * @summary Read policy for AP (availability) namespaces.
 *
 * @description How duplicates should be consulted in a read operation.
 * Only makes a difference during migrations and only applicable in AP mode.
 *
 * @type Object
 *
 * @property ONE - Involve a single node in the read operation.
 * @property ALL - Involve all duplicates in the read operation.
 */
exports.readModeAP = as.policy.readModeAP

/**
 * @summary Read policy for SC (strong consistency) namespaces.
 *
 * @description Determines SC read consistency options.
 *
 * @type Object
 *
 * @property SESSION - Ensures this client will only see an increasing sequence
 * of record versions. Server only reads from master. This is the default.
 * @property LINEARIZE - Ensures ALL clients will only see an increasing
 * sequence of record versions. Server only reads from master.
 * @property ALLOW_REPLICA - Server may read from master or any full
 * (non-migrating) replica. Increasing sequence of record versions is not
 * guaranteed.
 * @property ALLOW_UNAVAILABLE - Server may read from master or any full
 * (non-migrating) replica or from unavailable partitions. Increasing sequence
 * of record versions is not guaranteed.
 */
exports.readModeSC = as.policy.readModeSC

/**
 * Specifies the number of replicas required to be successfully committed
 * before returning success in a write operation to provide the desired
 * consistency guarantee.
 *
 * @type Object
 *
 * @property ALL - Return success only after successfully committing all
 * replicas.
 * @property MASTER - Return success after successfully committing the master
 * replica.
 */
exports.commitLevel = as.policy.commitLevel

/**
 * A base class extended to client policies.
 *
 * @summary {@link BasePolicy} class
 */
exports.BasePolicy = BasePolicy

/**
 * A policy affecting the behavior of apply operations.
 *
 * @summary {@link ApplyPolicy} class
 */
exports.ApplyPolicy = ApplyPolicy

/**
 * A policy affecting the behavior of operate operations.
 *
 * @summary {@link OperatePolicy} class
 */
exports.OperatePolicy = OperatePolicy

/**
  * A policy affecting the behavior of query operations.
  *
  * @summary {@link QueryPolicy} class
  */
exports.QueryPolicy = QueryPolicy

/**
  * A policy affecting the behavior of read operations.
  *
  * @summary {@link ReadPolicy} class
  */
exports.ReadPolicy = ReadPolicy

/**
  * A policy affecting the behavior of remove operations.
  *
  * @summary {@link RemovePolicy} class
  */
exports.RemovePolicy = RemovePolicy

/**
  * A policy affecting the behavior of scan operations.
  *
  * @summary {@link ScanPolicy} class
  */
exports.ScanPolicy = ScanPolicy

/**
  * A policy affecting the behavior of write operations.
  *
  * @summary {@link WritePolicy} class
  */
exports.WritePolicy = WritePolicy

/**
 * A policy affecting the behavior of batch operations.
 *
 * @summary {@link BatchPolicy} class
 */
exports.BatchPolicy = BatchPolicy

/**
 * A policy affecting the behavior of batchApply operations.
 *
 * @summary {@link BatchApplyPolicy} class
 */
exports.BatchApplyPolicy = BatchApplyPolicy

/**
 * A policy affecting the behavior of batchRead operations.
 *
 * @summary {@link BatchReadPolicy} class
 */
exports.BatchReadPolicy = BatchReadPolicy

/**
 * A policy affecting the behavior of batchRemove operations.
 *
 * @summary {@link BatchRemovePolicy} class
 */
exports.BatchRemovePolicy = BatchRemovePolicy

/**
 * A policy affecting the behavior of batchWrite operations.
 *
 * @summary {@link BatchWritePolicy} class
 */
exports.BatchWritePolicy = BatchWritePolicy

/**
 * A policy affecting the use of the global command queue.
 *
 * @summary {@link CommandQueuePolicy} class
 */
exports.CommandQueuePolicy = CommandQueuePolicy

/**
 * A policy affecting the behavior of HLL operations.
 *
 * @summary {@link HLLPolicy} class
 */
exports.HLLPolicy = HLLPolicy

/**
 * A policy affecting the behavior of info operations.
 *
 * @summary {@link InfoPolicy} class
 */
exports.InfoPolicy = InfoPolicy

/**
 * A policy affecting the behavior of admin operations.
 *
 * @summary {@link AdminPolicy} class
 */
exports.AdminPolicy = AdminPolicy

/**
 * A policy affecting the behavior of list operations.
 *
 * @summary {@link ListPolicy} class
 */
exports.ListPolicy = ListPolicy

/**
 * A policy affecting the behavior of map operations.
 *
 * @summary {@link MapPolicy} class
 */
exports.MapPolicy = MapPolicy

function policyClass (type) {
  switch (type) {
    case 'base': return BasePolicy
    case 'apply': return ApplyPolicy
    case 'batch': return BatchPolicy
    case 'operate': return OperatePolicy
    case 'query': return QueryPolicy
    case 'read': return ReadPolicy
    case 'remove': return RemovePolicy
    case 'scan': return ScanPolicy
    case 'write': return WritePolicy
    case 'batchRead': return BatchReadPolicy
    case 'batchRemove': return BatchRemovePolicy
    case 'batchWrite': return BatchWritePolicy
    case 'batchApply': return BatchApplyPolicy
    case 'commandQueue': return CommandQueuePolicy
    case 'hll': return HLLPolicy
    case 'info': return InfoPolicy
    case 'list': return ListPolicy
    case 'map': return MapPolicy
    case 'admin': return AdminPolicy
    default:
      throw new TypeError(`Unknown policy type: "${type}"`)
  }
}

/**
 * @private
 * @throws {TypeError} if the type is not a valid policy type
 */
exports.createPolicy = function (type, values) {
  if (values === null || typeof values === 'undefined') {
    return undefined
  }
  const Klass = policyClass(type)
  if (values instanceof Klass) {
    return values
  }
  return new Klass(values)
}