Source: execution-options.js

"use strict";

const utils = require("./utils");
const types = require("./types");
const errors = require("./errors");
const _rust = require("../index");
const { queryOptionsIntoWrapper } = require("./query-options");

const proxyExecuteKey = "ProxyExecute";

/**
 * A base class that represents a wrapper around the user provided query options with getter methods and proper
 * default values.
 *
 * Note that getter methods might return `undefined` when not set on the query options or default
 * {@link Client} options.
 */
class ExecutionOptions {
    /**
     * @type {_rust.QueryOptionsWrapper?}
     */
    #rustWrapper;

    /**
     * Creates a new instance of {@link ExecutionOptions}.
     */
    constructor() {}

    /**
     * Creates rust options wrapper for this execution options unless it already exists.
     * Goal of this function is to avoid wrapping the same options multiple times.
     * @package
     */
    wrapOptionsIfNotWrappedYet() {
        if (!this.#rustWrapper) {
            this.#rustWrapper = queryOptionsIntoWrapper(this);
        }
    }

    /**
     * Get this options as rust wrapper.
     * @returns {_rust.QueryOptionsWrapper}
     * @package
     */
    getRustOptions() {
        this.wrapOptionsIfNotWrappedYet();
        return this.#rustWrapper;
    }

    /**
     * Creates an empty instance, where all methods return undefined, used internally.
     * @ignore
     * @return {ExecutionOptions}
     */
    static empty() {
        return new ExecutionOptions();
    }

    /**
     * Determines if the stack trace before the query execution should be maintained.
     * @abstract
     * @returns {Boolean}
     */
    getCaptureStackTrace() {}

    /**
     * Gets the [Consistency level]{@link module:types~consistencies} to be used for the execution.
     * @abstract
     * @returns {Number}
     */
    getConsistency() {}

    /**
     * Key-value payload to be passed to the server. On the server side, implementations of QueryHandler can use
     * this data.
     * @abstract
     * @returns {Object}
     */
    getCustomPayload() {}

    /**
     * Gets the amount of rows to retrieve per page.
     * @abstract
     * @returns {Number}
     */
    getFetchSize() {}

    /**
     * When a fixed host is set on the query options and the query plan for the load-balancing policy is not used, it
     * gets the host that should handle the query.
     * @returns {Host}
     */
    getFixedHost() {}

    /**
     * Gets the type hints for parameters given in the query, ordered as for the parameters.
     * @abstract
     * @returns {Array|Array<Array>}
     */
    getHints() {}

    /**
     * Determines whether the driver must retrieve the following result pages automatically.
     *
     * This setting is only considered by the [Client#eachRow()]{@link Client#eachRow} method.
     * @abstract
     * @returns {Boolean}
     */
    isAutoPage() {}

    /**
     * Determines whether its a counter batch. Only valid for [Client#batch()]{@link Client#batch}, it will be ignored by
     * other methods.
     * @abstract
     * @returns {Boolean} A `Boolean` value, it can't be `undefined`.
     */
    isBatchCounter() {}

    /**
     * Determines whether the batch should be written to the batchlog. Only valid for
     * [Client#batch()]{@link Client#batch}, it will be ignored by other methods.
     * @abstract
     * @returns {Boolean} A `Boolean` value, it can't be `undefined`.
     */
    isBatchLogged() {}

    /**
     * Determines whether the query can be applied multiple times without changing the result beyond the initial
     * application.
     * @abstract
     * @returns {Boolean}
     */
    isIdempotent() {}

    /**
     * Determines whether the query must be prepared beforehand.
     * @abstract
     * @returns {Boolean} A `Boolean` value, it can't be `undefined`.
     */
    isPrepared() {}

    /**
     * Determines whether query tracing is enabled for the execution.
     * @abstract
     * @returns {Boolean}
     */
    isQueryTracing() {}

    /**
     * Gets the keyspace for the query when set at query options level.
     *
     * Note that this method will return `undefined` when the keyspace is not set at query options level.
     * It will only return the keyspace name when the user provided a different keyspace than the current
     * {@link Client} keyspace.
     * @abstract
     * @returns {String}
     */
    getKeyspace() {}

    /**
     * Gets the load balancing policy used for this execution.
     * @returns {LoadBalancingPolicy} A `LoadBalancingPolicy` instance, it can't be `undefined`.
     */
    getLoadBalancingPolicy() {}

    /**
     * Determines if the query should be paged.
     * @abstract
     * @returns {boolean}
     */
    isPaged() {}

    /**
     * Gets the Buffer representing the paging state.
     * @abstract
     * @returns {Buffer}
     */
    getPageState() {}

    /**
     * Internal method that gets the preferred host.
     * @abstract
     * @ignore
     */
    getPreferredHost() {}

    /**
     * Gets the query options as provided to the execution method without setting the default values.
     * @returns {QueryOptions}
     */
    getRawQueryOptions() {}

    /**
     * Gets the timeout in milliseconds to be used for the execution per coordinator.
     *
     * A value of `0` disables client side read timeout for the execution. Default: `undefined`.
     * @abstract
     * @returns {Number}
     */
    getReadTimeout() {}

    /**
     * Gets the [retry policy]{@link module:policies/retry} to be used.
     * @abstract
     * @returns {RetryPolicy} A `RetryPolicy` instance, it can't be `undefined`.
     */
    getRetryPolicy() {}

    /**
     * Internal method to obtain the row callback, for "by row" results.
     * @abstract
     * @ignore
     */
    getRowCallback() {}

    /**
     * Internal method to get or generate a timestamp for the request execution.
     * @ignore
     * @returns {Long|null}
     */
    getOrGenerateTimestamp() {}

    /**
     * Gets the index of the parameters that are part of the partition key to determine the routing.
     * @abstract
     * @ignore
     * @returns {Array}
     */
    getRoutingIndexes() {}

    /**
     * Gets the partition key(s) to determine which coordinator should be used for the query.
     * @abstract
     * @returns {Buffer|Array<Buffer>}
     */
    getRoutingKey() {}

    /**
     * Gets the array of the parameters names that are part of the partition key to determine the
     * routing. Only valid for non-prepared requests.
     * @abstract
     * @ignore
     */
    getRoutingNames() {}

    /**
     * Gets the the consistency level to be used for the serial phase of conditional updates.
     * @abstract
     * @returns {Number}
     */
    getSerialConsistency() {}

    /**
     * Gets the provided timestamp for the execution in microseconds from the unix epoch (00:00:00, January 1st, 1970).
     *
     * When a timestamp generator is used, this method returns `undefined`.
     * @abstract
     * @returns {Number|Long|undefined|null}
     */
    getTimestamp() {}

    /**
     * @param {Array} hints
     * @abstract
     * @ignore
     */
    setHints(hints) {}

    /**
     * Sets the keyspace for the execution.
     * @ignore
     * @abstract
     * @param {String} keyspace
     */
    setKeyspace(keyspace) {}

    /**
     * @abstract
     * @ignore
     */
    setPageState() {}

    /**
     * Internal method that sets the preferred host.
     * @abstract
     * @ignore
     */
    setPreferredHost() {}

    /**
     * Sets the index of the parameters that are part of the partition key to determine the routing.
     * @param {Array} routingIndexes
     * @abstract
     * @ignore
     */
    setRoutingIndexes(routingIndexes) {}

    /**
     * Sets the routing key.
     * @abstract
     * @ignore
     */
    setRoutingKey(value) {}
}

/**
 * Internal implementation of {@link ExecutionOptions} that uses the value from the client options and execution
 * profile into account.
 * @ignore
 */
class DefaultExecutionOptions extends ExecutionOptions {
    /**
     * Creates a new instance of {@link ExecutionOptions}.
     * @param {QueryOptions} queryOptions
     * @param {Client} client
     * @param {Function|null} rowCallback
     */
    constructor(queryOptions, client, rowCallback) {
        super();

        this._queryOptions = queryOptions;
        this._rowCallback = rowCallback;
        this._routingKey = this._queryOptions.routingKey;
        this._hints = this._queryOptions.hints;
        this._keyspace = this._queryOptions.keyspace;
        this._routingIndexes = this._queryOptions.routingIndexes;
        this._pageState =
            typeof this._queryOptions.pageState === "string"
                ? utils.allocBufferFromString(
                      this._queryOptions.pageState,
                      "hex",
                  )
                : this._queryOptions.pageState;
        this._preferredHost = null;

        this._client = client;
        this._defaultQueryOptions = client.options.queryOptions;
        this._profile = client.profileManager.getProfile(
            this._queryOptions.executionProfile,
        );

        // Build a custom payload object designed for DSE-specific functionality
        this._customPayload = DefaultExecutionOptions.createCustomPayload(
            this._queryOptions,
            this._defaultQueryOptions,
        );

        if (!this._profile) {
            throw new errors.ArgumentError(
                `Execution profile "${this._queryOptions.executionProfile}" not found`,
            );
        }
    }

    /**
     * Creates a payload for given user.
     * @param {QueryOptions} userOptions
     * @param {QueryOptions} defaultQueryOptions
     * @private
     */
    static createCustomPayload(userOptions, defaultQueryOptions) {
        let customPayload =
            userOptions.customPayload || defaultQueryOptions.customPayload;
        const executeAs =
            userOptions.executeAs || defaultQueryOptions.executeAs;

        if (executeAs) {
            if (!customPayload) {
                customPayload = {};
                customPayload[proxyExecuteKey] =
                    utils.allocBufferFromString(executeAs);
            } else if (!customPayload[proxyExecuteKey]) {
                // Avoid appending to the existing payload object
                customPayload = utils.extend({}, customPayload);
                customPayload[proxyExecuteKey] =
                    utils.allocBufferFromString(executeAs);
            }
        }

        return customPayload;
    }

    /**
     * Creates a new instance {@link ExecutionOptions}, based on the query options.
     * @param {QueryOptions|null} queryOptions
     * @param {Client} client
     * @param {Function|null} [rowCallback]
     * @ignore
     * @return {ExecutionOptions}
     */
    static create(queryOptions, client, rowCallback) {
        if (!queryOptions || typeof queryOptions === "function") {
            // queryOptions can be null/undefined and could be of type function when is an optional parameter
            queryOptions = utils.emptyObject;
        }
        return new DefaultExecutionOptions(queryOptions, client, rowCallback);
    }

    getCaptureStackTrace() {
        return ifUndefined(
            this._queryOptions.captureStackTrace,
            this._defaultQueryOptions.captureStackTrace,
        );
    }

    getConsistency() {
        return ifUndefined3(
            this._queryOptions.consistency,
            this._profile.consistency,
            this._defaultQueryOptions.consistency,
        );
    }

    getCustomPayload() {
        return this._customPayload;
    }

    getFetchSize() {
        return ifUndefined(
            this._queryOptions.fetchSize,
            this._defaultQueryOptions.fetchSize,
        );
    }

    getFixedHost() {
        return this._queryOptions.host;
    }

    getHints() {
        return this._hints;
    }

    isAutoPage() {
        return ifUndefined(
            this._queryOptions.autoPage,
            this._defaultQueryOptions.autoPage,
        );
    }

    isBatchCounter() {
        return ifUndefined(this._queryOptions.counter, false);
    }

    isBatchLogged() {
        return ifUndefined3(
            this._queryOptions.logged,
            this._defaultQueryOptions.logged,
            true,
        );
    }

    isIdempotent() {
        return ifUndefined(
            this._queryOptions.isIdempotent,
            this._defaultQueryOptions.isIdempotent,
        );
    }

    /**
     * Determines if the query execution must be prepared beforehand.
     * @return {Boolean}
     */
    isPrepared() {
        return ifUndefined(
            this._queryOptions.prepare,
            this._defaultQueryOptions.prepare,
        );
    }

    isQueryTracing() {
        return ifUndefined(
            this._queryOptions.traceQuery,
            this._defaultQueryOptions.traceQuery,
        );
    }

    getKeyspace() {
        return this._keyspace;
    }

    getLoadBalancingPolicy() {
        return this._profile.loadBalancing;
    }

    getOrGenerateTimestamp() {
        let result = this.getTimestamp();

        if (result === undefined) {
            const generator = this._client.options.policies.timestampGeneration;

            if (
                types.protocolVersion.supportsTimestamp(
                    this._client.controlConnection.protocolVersion,
                ) &&
                generator
            ) {
                result = generator.next(this._client);
            } else {
                result = null;
            }
        }

        return typeof result === "number"
            ? types.Long.fromNumber(result)
            : result;
    }

    isPaged() {
        return ifUndefined(
            this._queryOptions.paged,
            this._defaultQueryOptions.paged,
        );
    }

    getPageState() {
        return this._pageState;
    }

    /**
     * Gets the profile defined by the user or the default profile
     * @internal
     * @ignore
     */
    getProfile() {
        return this._profile;
    }

    getRawQueryOptions() {
        return this._queryOptions;
    }

    getReadTimeout() {
        return ifUndefined3(
            this._queryOptions.readTimeout,
            this._profile.readTimeout,
            this._client.options.socketOptions.readTimeout,
        );
    }

    getRetryPolicy() {
        return ifUndefined3(
            this._queryOptions.retry,
            this._profile.retry,
            this._client.options.policies.retry,
        );
    }

    getRoutingIndexes() {
        return this._routingIndexes;
    }

    getRoutingKey() {
        return this._routingKey;
    }

    getRoutingNames() {
        return this._queryOptions.routingNames;
    }

    /**
     * Internal method to obtain the row callback, for "by row" results.
     * @ignore
     */
    getRowCallback() {
        return this._rowCallback;
    }

    getSerialConsistency() {
        return ifUndefined3(
            this._queryOptions.serialConsistency,
            this._profile.serialConsistency,
            this._defaultQueryOptions.serialConsistency,
        );
    }

    getTimestamp() {
        return this._queryOptions.timestamp;
    }

    /**
     * Internal property to set the custom payload.
     * @ignore
     * @internal
     * @param {Object} payload
     */
    setCustomPayload(payload) {
        this._customPayload = payload;
    }

    /**
     * @param {Array} hints
     */
    setHints(hints) {
        this._hints = hints;
    }

    /**
     * @param {String} keyspace
     */
    setKeyspace(keyspace) {
        this._keyspace = keyspace;
    }

    /**
     * @param {Buffer} pageState
     */
    setPageState(pageState) {
        this._pageState = pageState;
    }

    /**
     * @param {Array} routingIndexes
     */
    setRoutingIndexes(routingIndexes) {
        this._routingIndexes = routingIndexes;
    }

    setRoutingKey(value) {
        this._routingKey = value;
    }
}

function ifUndefined(v1, v2) {
    return v1 !== undefined ? v1 : v2;
}

function ifUndefined3(v1, v2, v3) {
    if (v1 !== undefined) {
        return v1;
    }
    return v2 !== undefined ? v2 : v3;
}

module.exports = { ExecutionOptions, DefaultExecutionOptions, proxyExecuteKey };