Skip to content
Draft
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
325 changes: 126 additions & 199 deletions lib/client.js

Large diffs are not rendered by default.

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
13 changes: 13 additions & 0 deletions lib/new-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,23 @@ 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;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exports.throwNotSupported is assigned twice; the second assignment is redundant and makes it easier to introduce export-order bugs during future edits. Remove the duplicate export so each symbol is exported exactly once.

Suggested change
exports.throwNotSupported = throwNotSupported;

Copilot uses AI. Check for mistakes.
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;
10 changes: 9 additions & 1 deletion lib/types/result-set.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ class ResultSet {
*/
#encoder;

/**
* Internal representation of the page state, used for fetching the next page of results.
* @type {rust.PagingStateWrapper?}
* @package
*/
innerPageState;

/**
* @param {rust.QueryResultWrapper} result
* @param {_Encoder} encoder
* @param {rust.PagingStateResponseWrapper} [pagingState]
* @param {rust.PagingStateWrapper} [pagingState]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ This fix should constitute a separate commit.

*/
constructor(result, encoder, pagingState) {
// Old constructor logic only for purpose of unit tests.
Expand Down Expand Up @@ -103,6 +110,7 @@ class ResultSet {
this.pageState = null;

if (pagingState) {
this.innerPageState = pagingState;
let rawPageState = pagingState.getRawPageState();
this.pageState = rawPageState.toString("hex");
Object.defineProperty(this, "rawPageState", {
Expand Down
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
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc references PreparedInfo, but this file doesn't import it for type checking. With checkJs enabled, TypeScript will report Cannot find name 'PreparedInfo'. Add a type-only require (suppressed for eslint no-unused-vars) or change the annotation to an inline structural type.

Suggested change
* @param {PreparedInfo} columns
* @param {{ types: Array, colNames: Array }} columns

Copilot uses AI. Check for mistakes.
* @returns {Array} Returns an array of parameters.
* @throws {Error} In case a parameter with a specific name is not defined
*/
function adaptNamedParamsPrepared(params, columns) {
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adaptNamedParamsPrepared() is documented to accept Array|Object params, but it now unconditionally calls toLowerCaseProperties(params) which throws for null/undefined and produces incorrect results for arrays. Either restore the early-return guard for non-object params (and empty columns) or tighten the JSDoc/type contract to match the new required inputs.

Suggested change
function adaptNamedParamsPrepared(params, columns) {
function adaptNamedParamsPrepared(params, columns) {
if (!columns || !columns.types || columns.types.length === 0) {
return params;
}
if (params === null || params === undefined || Array.isArray(params) || typeof params !== "object") {
return params;
}

Copilot uses AI. Check for mistakes.
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
9 changes: 7 additions & 2 deletions src/requests/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,16 @@ impl QueryOptionsWrapper {

impl PreparedStatementWrapper {
/// Get array of expected types for this prepared statement.
pub fn get_expected_types(&self) -> Vec<ComplexType<'static>> {
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()
}
}
7 changes: 4 additions & 3 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,11 @@ impl SessionWrapper {

/// Prepares a statement through rust driver for a given session
/// Return expected types for the prepared statement
#[napi(ts_return_type = "Promise<Array<ComplexType>>")]
#[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
7 changes: 2 additions & 5 deletions test/integration/supported/client-execute-prepared-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,10 +540,7 @@ describe("Client @SERVER_API", function () {
done,
);
});

// No support for named parameters
// TODO: fix this test
/* describe("with named parameters", function () {
describe("with named parameters", function () {
vit("2.0", "should allow an array of parameters", function (done) {
const query = util.format(
"SELECT * FROM %s WHERE id1 = :id1",
Expand Down Expand Up @@ -626,7 +623,7 @@ describe("Client @SERVER_API", function () {
);
},
);
}); */
});

it("should encode and decode maps using Map polyfills", function (done) {
const client = newInstance({
Expand Down
Loading