UNPKG

oracle-nosqldb

Version:

Node.js driver for Oracle NoSQL Database

201 lines (185 loc) 6.99 kB
/*- * Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl/ */ 'use strict'; const isPosInt = require('./utils').isPosInt; const NoSQLArgumentError = require('./error').NoSQLArgumentError; /** * Defines classes related to SQL statement and query execution such as * {@link PreparedStatement}. */ /** * @classdesc A class encapsulating a prepared query statement. It includes * state that can be sent to a server and executed without re-parsing the * query. It includes bind variables which may be set for each successive use * of the query. PreparedStatement object is returned as a result of * {@link NoSQLClient#prepare} method. It can be passed to * {@link NoSQLClient#query} and {@link NoSQLClient#queryIterable} methods for * execution and be reused for multiple queries, potentially with different * values of bind variables. * <p> * You may share an instance of {@link PreparedStatements} by queries running * async-concurrently, e.g. queries invoked concurrently by different async * functions. This is referred to as async-safety: * <br> * An instance of {@link PreparedStatement} is async-safe if bind variables * are not used. If bind variables are used, it is not async-safe. In this * case, you can construct additional instances of {@link PreparedStatement} * using {@link PreparedStatement#copyStatement} method in order to share it * among async-concurrent queries. * @extends {PrepareResult} * @hideconstructor */ class PreparedStatement { /** * Sets and gets the bindings object explicitly. Bindings object is an * object which properties contain the bind variables for this prepared * statement. For each variable, binding object has property which name * is the variable name and the value is the variable value. Note that * "$" in the variable name is included in its property name. For * positional variables, the names are determined by the query engine. * @type {object} * @example //Setting bindings * prepStmt.bindings = { * $id: 100, * $name: 'John' * }; * //This is equivalent to: * prepStmt.set('$id', 100); * prepStmt.set('$name', 'John'); */ set bindings(value) { this._bindings = value; } get bindings() { return this._bindings; } /** * Binds a variable to use for the query. The variable can be identified * either by its name or its position. * <p> * To bind by name, pass a name of the variable as it was declared in * <em>DECLARE</em> statement of the query. * <p> * You can also bind a variable by its position within the query string. * The positions start at 1. The variable that appears first in the query * text has position 1, the variable that appears second has position 2 * and so on. Binding by position is useful for queries where bind * variables identified by "?" are used instead of named variables (but it * can be used for both types of variables). * <p> * Existing variables with the same name or position are silently * overwritten. The names, positions and types are validated when the * query is executed. * * @example // Using PreparedStatement, binding variables by name. * let client = new NoSQLClient(//..... * let prepStmt = await client.prepare( * 'DECLARE $id INTEGER; $sal DOUBLE; SELECT id, firstName, lastName ' + * 'FROM Emp WHERE id <= $id AND salary <= $sal'); * ps.set('$id', 1100); * .set('$sal', 100500); * for await(const res of client.queryIterable(stmt)) { * //..... * } * ps.set('$id', 2000); * for await(const res of client.queryIterable(stmt)) { * //..... * } * //..... * @example // Binding variables by position. * let prepStmt = await client.prepare( * 'SELECT id, firstName, lastName FROM Emp WHERE ' + * 'id <= ? AND salary <= ?'); * ps.set(1, 1100) * .set(2, 100500); * //..... * @param {string|number} nameOrPosition Name or position of the variable * @param {FieldValue} val Value of the variable of the appropriate type * @returns {PreparedStatement} This instance for chaining * @throws {NoSQLArgumentError} If binding by position and the position is * invalid. */ set(nameOrPosition, val) { if (!this._bindings) { this._bindings = {}; } let key = nameOrPosition; if (typeof key === 'number') { if (!isPosInt(key)) { throw new NoSQLArgumentError( `Invalid bind variable position: ${key}`); } if (this._varNames == null) { key = '#' + key; } else { if (key > this._varNames.length) { throw new NoSQLArgumentError(`Invalid bind variable \ position: ${key}, exceeds total ${this._varNames.length}`); } key = this._varNames[key - 1]; } } this._bindings[key] = val; return this; } /** * Clears all variables in bindings for this prepared statement. * @returns {PreparedStatement} This instance for chaining */ clearAll() { delete this._bindings; return this; } /** * SQL text of this prepared statement. * @type {string} * @readonly */ get sql() { return this._sql; } /** * Query execution plan printout if was requested by * {@link NoSQLClient#prepare} (see <em>opt.getQueryPlan</em>), otherwise * undefined. * @type {string} * @readonly */ get queryPlan() { return this._queryPlanStr; } /** * JSON representation of the query result schema if was requested by * {@link NoSQLClient#prepare} (see <em>opt.getResultSchema</em>), * otherwise undefined. * @type {string} * @readonly */ get resultSchema() { return this._schema; } /** * Returns a copy of this prepared statement without its variables. * <p> * This method returns a new instance of {@link PreparedStatement} that * shares this object's prepared query, which is immutable, but does not * share its variables. Use this method when you need to execute the same * prepared query async-concurrently (call this method to create a new copy * for each additional concurrent query). * @returns {PreparedStatement} A copy of this prepared statement without * its variables */ copyStatement() { const res = Object.assign({ __proto__: PreparedStatement.prototype }, this); delete res._bindings; return res; } } module.exports = { PreparedStatement };