Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 39 additions & 68 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const events = require("events");
const util = require("util");
const { throwNotSupported } = require("./new-utils.js");
const { throwNotSupported, isNamedParameters } = require("./new-utils.js");

const utils = require("./utils.js");
const errors = require("./errors.js");
Expand All @@ -20,14 +20,15 @@ const {
const promiseUtils = require("./promise-utils");
const rust = require("../index");
const ResultSet = require("./types/result-set.js");
const { encodeParams, convertComplexType } = require("./types/cql-utils.js");
const { encodeParams } = require("./types/cql-utils.js");
const { PreparedCache } = require("./cache.js");
const Encoder = require("./encoder.js");
const { HostMap } = require("./host.js");

// Imports for the purpose of type hints in JS docs.
// eslint-disable-next-line no-unused-vars
const { QueryOptions } = require("./query-options.js");
const { PreparedInfo } = require("./prepared.js");

/**
* Represents a database client that maintains multiple connections to the cluster nodes, providing methods to
Expand Down Expand Up @@ -164,19 +165,6 @@ class Client extends events.EventEmitter {
return fullOptions;
}

/**
* Manually prepare query into prepared statement
* @param {string} query
* @returns {Promise<list<Object | string>>}
* Returns a tuple of type object (the format expected by the encoder) and prepared statement wrapper
* @package
*/
async prepareQuery(query) {
let expectedTypes = await this.rustClient.prepareStatement(query);
let res = [expectedTypes.map((t) => convertComplexType(t)), query];
return res;
}

/**
* Attempts to connect to one of the [contactPoints]{@link ClientOptions} and discovers the rest the nodes of the
* cluster.
Expand Down Expand Up @@ -318,25 +306,14 @@ class Client extends events.EventEmitter {

/**
* Wrapper for executing queries by rust driver
* @param {string | list<Object | string>} query
* @param {Array} params
* @param {string | PreparedInfo} query
* @param {Array | Object} params
* @param {ExecutionOptions} execOptions
* @returns {Promise<ResultSet>}
* @package
*/
async rustyExecute(query, params, execOptions) {
if (
// !execOptions.isPrepared() &&
params &&
!Array.isArray(params)
// && !types.protocolVersion.supportsNamedParameters(version)
) {
throw new Error(`TODO: Implement any support for named parameters`);
// // Only Cassandra 2.1 and above supports named parameters
// throw new errors.ArgumentError(
// "Named parameters for simple statements are not supported, use prepare flag",
// );
}
let withNamedParameters = isNamedParameters(params, execOptions);

if (!this.connected) {
// TODO: Check this logic and decide if it's needed. Probably do it while implementing (better) connection
Expand All @@ -348,26 +325,24 @@ class Client extends events.EventEmitter {
let result;

if (execOptions.isPrepared()) {
/**
* @type {PreparedInfo}
*/
let prepared = query;
// If the statement is already prepared, skip the preparation process
// Otherwise call Rust part to prepare a statement
if (typeof query === "string") {
query = await this.prepareQuery(query);
prepared = await PreparedInfo.create(query, this.rustClient);
}
if (withNamedParameters) {
params = utils.adaptNamedParamsPrepared(params, prepared);
}

/**
* @type {string}
*/
let statement = query[1];
/**
* @type {Object}
*/
let expectedTypes = query[0];

let encoded = encodeParams(expectedTypes, params, this.#encoder);
let encoded = encodeParams(prepared.types, params, this.#encoder);

// Execute query
result = await this.rustClient.executePreparedUnpagedEncoded(
statement,
prepared.query,
encoded,
rustOptions,
);
Expand Down Expand Up @@ -511,25 +486,14 @@ class Client extends events.EventEmitter {
/**
* Execute a single page of query
* @param {string} query
* @param {Array} params
* @param {Array | Object} params
* @param {ExecutionOptions} execOptions
* @param {rust.PagingStateWrapper|Buffer} [pageState]
* @returns {Promise<Array<rust.PagingStateResponseWrapper, ResultSet>>} should be Promise<[rust.PagingStateResponseWrapper, ResultSet]>
* @private
*/
async #rustyPaged(query, params, execOptions, pageState) {
if (
!execOptions.isPrepared() &&
params &&
!Array.isArray(params)
// && !types.protocolVersion.supportsNamedParameters(version)
) {
throw new Error(`TODO: Implement any support for named parameters`);
// // Only Cassandra 2.1 and above supports named parameters
// throw new errors.ArgumentError(
// "Named parameters for simple statements are not supported, use prepare flag",
// );
}
let withNamedParameters = isNamedParameters(params, execOptions);

if (!this.connected) {
// TODO: Check this logic and decide if it's needed. Probably do it while implementing (better) connection
Expand All @@ -554,26 +518,25 @@ class Client extends events.EventEmitter {
const rustOptions = execOptions.getRustOptions();
let result;
if (execOptions.isPrepared()) {
/**
* @type {PreparedInfo}
*/
let prepared = query;
// If the statement is already prepared, skip the preparation process
// Otherwise call Rust part to prepare a statement
if (typeof query === "string") {
query = await this.prepareQuery(query);
prepared = await PreparedInfo.create(query, this.rustClient);
}

/**
* @type {string}
*/
let statement = query[1];
/**
* @type {Object}
*/
let expectedTypes = query[0];
if (withNamedParameters) {
params = utils.adaptNamedParamsPrepared(params, prepared);
}

let encoded = encodeParams(expectedTypes, params, this.#encoder);
let encoded = encodeParams(prepared.types, params, this.#encoder);

// Execute query
result = await this.rustClient.executeSinglePageEncoded(
statement,
prepared.query,
encoded,
rustOptions,
pageState,
Expand Down Expand Up @@ -749,11 +712,18 @@ class Client extends events.EventEmitter {
if (shouldBePrepared) {
let prepared = preparedCache.getElement(statement);
if (!prepared) {
prepared = await this.prepareQuery(statement);
prepared = await PreparedInfo.create(
statement,
this.rustClient,
);
preparedCache.storeElement(statement, prepared);
}
types = prepared[0];
statement = prepared[1];
types = prepared.types;
statement = prepared.query;

if (params && !Array.isArray(params)) {
params = utils.adaptNamedParamsPrepared(params, prepared);
}
} else {
types = hints[i] || [];
}
Expand Down Expand Up @@ -850,6 +820,7 @@ class Client extends events.EventEmitter {
* @returns {void}
*/
}

/**
* Callback used by execution methods.
* @callback ResultCallback
Expand Down
3 changes: 2 additions & 1 deletion lib/concurrent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _Client = require("../client");
const utils = require("../utils");
const { Stream } = require("stream");
const { PreparedCache } = require("../cache");
const { PreparedInfo } = require("../prepared");

/**
* Utilities for concurrent query execution with the DataStax Node.js Driver.
Expand Down Expand Up @@ -155,7 +156,7 @@ class ArrayBasedExecutor {
try {
let prepared = this._cache.getElement(query);
if (!prepared) {
prepared = await (this._client.prepareQuery(query));
prepared = await PreparedInfo.create(query, this._client.rustClient);
this._cache.storeElement(query, prepared);
}
await this._client
Expand Down
14 changes: 13 additions & 1 deletion lib/new-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,22 @@ function ensure64SignedInteger(number, name) {
}
}

function isNamedParameters(params, execOptions) {
if (params && !Array.isArray(params)) {
if (!execOptions.isPrepared()) {
throw new customErrors.ArgumentError(
"Named parameters for simple statements are not supported, use prepare flag",
);
}
return true;
}
return false;
}

exports.throwNotSupported = throwNotSupported;
exports.napiErrorHandler = napiErrorHandler;
exports.throwNotSupported = throwNotSupported;
exports.bigintToLong = bigintToLong;
exports.arbitraryValueToBigInt = arbitraryValueToBigInt;
exports.isNamedParameters = isNamedParameters;
exports.ensure32SignedInteger = ensure32SignedInteger;
exports.ensure64SignedInteger = ensure64SignedInteger;
29 changes: 29 additions & 0 deletions lib/prepared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const _rust = require("../index");
const { convertComplexType } = require("./types/cql-utils");

class PreparedInfo {
/**
* @param {Array<type>} types
* @param {string} query
* @param {Array<string>} colNames
*/
constructor(types, query, colNames) {
this.types = types;
this.query = query;
this.colNames = colNames;
}

/**
* @param {string} query
* @param {_rust.SessionWrapper} client
* @returns {Promise<PreparedInfo>}
*/
static async create(query, client) {
let expectedTypes = await client.prepareStatement(query);
let types = expectedTypes.map((t) => convertComplexType(t[0]));
let colNames = expectedTypes.map((t) => t[1].toLowerCase());
return new PreparedInfo(types, query, colNames);
}
}

module.exports.PreparedInfo = PreparedInfo;
14 changes: 4 additions & 10 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,26 +288,20 @@ function validateFn(fn, name) {
* If the params are passed as an associative array (Object),
* it adapts the object into an array with the same order as columns
* @param {Array|Object} params
* @param {Array} columns
* @param {PreparedInfo} columns
* @returns {Array} Returns an array of parameters.
* @throws {Error} In case a parameter with a specific name is not defined
*/
function adaptNamedParamsPrepared(params, columns) {
if (!params || Array.isArray(params) || !columns || columns.length === 0) {
// params is an array or there aren't parameters
return params;
}
const paramsArray = new Array(columns.length);
const paramsArray = new Array(columns.types.length);
params = toLowerCaseProperties(params);
const keys = {};
for (let i = 0; i < columns.length; i++) {
const name = columns[i].name;
for (let i = 0; i < columns.types.length; i++) {
const name = columns.colNames[i];

if (!Object.prototype.hasOwnProperty.call(params, name)) {
throw new errors.ArgumentError(`Parameter "${name}" not defined`);
}
paramsArray[i] = params[name];
keys[name] = i;
}
return paramsArray;
}
Expand Down
11 changes: 8 additions & 3 deletions src/requests/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@ impl QueryOptionsWrapper {
}

impl PreparedStatementWrapper {
/// Get array of expected types for this prepared statement.
pub fn get_expected_types(&self) -> Vec<ComplexType<'static>> {
/// Get array of (expected type, variable name) pairs for this prepared statement.
pub fn get_expected_types(&self) -> Vec<(ComplexType<'static>, String)> {
self.prepared
.get_variable_col_specs()
.iter()
.map(|e| ComplexType::new_owned(e.typ().clone()))
.map(|e| {
(
ComplexType::new_owned(e.typ().clone()),
e.name().to_string(),
)
})
.collect()
}
}
11 changes: 6 additions & 5 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,13 @@ impl SessionWrapper {
.await
}

/// Prepares a statement through rust driver for a given session
/// Return expected types for the prepared statement
#[napi(ts_return_type = "Promise<Array<ComplexType>>")]
/// Prepares a statement through rust driver for a given session.
/// Returns (expected type, variable name) pairs for the prepared statement.
#[napi(ts_return_type = "Promise<Array<[ComplexType, string]>>")]
pub async fn prepare_statement(
&self,
statement: String,
) -> JsResult<Vec<ComplexType<'static>>> {
) -> JsResult<Vec<(ComplexType<'static>, String)>> {
with_custom_error_async(async || {
let statement: Statement = statement.into();
let w = PreparedStatementWrapper {
Expand All @@ -177,7 +177,8 @@ impl SessionWrapper {
.add_prepared_statement(&statement) // TODO: change for add_prepared_statement_to_owned after it is made public
.await?,
};
ConvertedResult::Ok(w.get_expected_types())
let types = w.get_expected_types();
ConvertedResult::Ok(types)
})
.await
}
Expand Down
7 changes: 3 additions & 4 deletions test/integration/supported/client-batch-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -914,9 +914,7 @@ describe("Client @SERVER_API", function () {
);
},
);
// No support for named parameters
// TODO: Fix this test
/* vit("2.0", "should allow named parameters", function (done) {
vit("2.0", "should allow named parameters", function (done) {
const client = newInstance();
const id1 = types.Uuid.random();
const id2 = types.Uuid.random();
Expand All @@ -928,6 +926,7 @@ describe("Client @SERVER_API", function () {
table1,
),
params: {
// eslint-disable-next-line camelcase
text_SAMPLE: "named params",
paramID: id1,
time: types.TimeUuid.now(),
Expand Down Expand Up @@ -990,7 +989,7 @@ describe("Client @SERVER_API", function () {
);
},
);
}); */
});

vit(
"2.0",
Expand Down
Loading
Loading