Skip to content
Merged
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
6 changes: 3 additions & 3 deletions lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const _rust = require("../index");

class PreparedCache {
/**
* @type {Map<string, _rust.PreparedStatementWrapper>}
* @type {Map<string, list<Object | string>>}
*/
#cache;

Expand All @@ -13,7 +13,7 @@ class PreparedCache {
/**
*
* @param {string} key
* @returns {_rust.PreparedStatementWrapper}
* @returns {list<Object | string>}
*/
getElement(key) {
return this.#cache[key];
Expand All @@ -22,7 +22,7 @@ class PreparedCache {
/**
*
* @param {string} key
* @param {_rust.PreparedStatementWrapper} element
* @param {list<Object | string>} element
*/
storeElement(key, element) {
this.#cache[key] = element;
Expand Down
129 changes: 73 additions & 56 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const ExecOptions = require("./execution-options");
const promiseUtils = require("./promise-utils");
const rust = require("../index");
const ResultSet = require("./types/result-set.js");
const { parseParams, convertHints } = require("./types/cql-utils.js");
const { encodeParams, convertComplexType } = require("./types/cql-utils.js");
const queryOptions = require("./query-options.js");
const { PreparedCache } = require("./cache.js");
const Encoder = require("./encoder.js");

/**
* Represents a database client that maintains multiple connections to the cluster nodes, providing methods to
Expand All @@ -41,6 +42,7 @@ const { PreparedCache } = require("./cache.js");
* console.log(row['key']);
*/
class Client extends events.EventEmitter {
#encoder;
/**
* Creates a new instance of {@link Client}.
* @param {clientOptions.ClientOptions} options The options for this instance.
Expand All @@ -49,6 +51,7 @@ class Client extends events.EventEmitter {
super();
this.rustOptions = clientOptions.setRustOptions(options);

/** @type {clientOptions.ClientOptions} */
this.options = clientOptions.extend(
{ logEmitter: this.emit.bind(this), id: types.Uuid.random() },
options,
Expand Down Expand Up @@ -81,6 +84,9 @@ class Client extends events.EventEmitter {
* @type {ClientMetrics}
*/
this.metrics = this.options.metrics;

// TODO: This field is currently hardcoded. Should be implemented properly
this.#encoder = new Encoder(0x04, this.options.encoding);
}

/**
Expand Down Expand Up @@ -142,11 +148,14 @@ class Client extends events.EventEmitter {
/**
* Manually prepare query into prepared statement
* @param {string} query
* @returns {Promise<rust.PreparedStatementWrapper>}
* @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) {
return await this.rustClient.prepareStatement(query);
let expectedTypes = await this.rustClient.prepareStatement(query);
let res = [expectedTypes.map((t) => convertComplexType(t)), query];
return res;
}

/**
Expand Down Expand Up @@ -293,7 +302,7 @@ class Client extends events.EventEmitter {

/**
* Wrapper for executing queries by rust driver
* @param {string | rust.PreparedStatementWrapper} query
* @param {string | list<Object | string>} query
* @param {Array} params
* @param {ExecOptions.ExecutionOptions} execOptions
* @returns {Promise<ResultSet>}
Expand Down Expand Up @@ -325,40 +334,44 @@ class Client extends events.EventEmitter {
if (execOptions.isPrepared()) {
// If the statement is already prepared, skip the preparation process
// Otherwise call Rust part to prepare a statement
let statement =
query instanceof rust.PreparedStatementWrapper
? query
: await this.rustClient.prepareStatement(query);
if (typeof query === "string") {
query = await this.prepareQuery(query);
}

// Parse parameters according to expected types
let parsedParams = parseParams(
statement.getExpectedTypes(),
params,
false,
);
/**
* @type {string}
*/
let statement = query[1];
/**
* @type {Object}
*/
let expectedTypes = query[0];

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

// Execute query
result = await this.rustClient.executePreparedUnpaged(
result = await this.rustClient.executePreparedUnpagedEncoded(
statement,
parsedParams,
encoded,
rustOptions,
);
} else {
// We do not accept already prepared statements for unprepared queries
if (query instanceof rust.PreparedStatementWrapper) {
throw new Error(
"Unexpected prepared statement wrapper for unprepared queries",
);
if (typeof query !== "string") {
throw new Error("Expected to obtain a string query");
}
// Get expected types from user provided hints
let expectedTypes = convertHints(execOptions.getHints() || []);

// Parse parameters according to provided hints, with type guessing
let parsedParams = parseParams(expectedTypes, params, true);
let encoded = encodeParams(
execOptions.getHints() || [],
params,
this.#encoder,
);

// Execute query
result = await this.rustClient.queryUnpaged(
result = await this.rustClient.queryUnpagedEncoded(
query,
parsedParams,
encoded,
rustOptions,
);
}
Expand Down Expand Up @@ -519,39 +532,44 @@ class Client extends events.EventEmitter {
if (execOptions.isPrepared()) {
// If the statement is already prepared, skip the preparation process
// Otherwise call Rust part to prepare a statement
let statement =
query instanceof rust.PreparedStatementWrapper
? query
: await this.rustClient.prepareStatement(query);
if (typeof query === "string") {
query = await this.prepareQuery(query);
}

// Parse parameters according to expected types
let parsedParams = parseParams(
statement.getExpectedTypes(),
params,
);
/**
* @type {string}
*/
let statement = query[1];
/**
* @type {Object}
*/
let expectedTypes = query[0];

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

// Execute query
result = await this.rustClient.executeSinglePage(
result = await this.rustClient.executeSinglePageEncoded(
statement,
parsedParams,
encoded,
rustOptions,
pageState,
);
} else {
// We do not accept already prepared statements for unprepared queries
if (query instanceof rust.PreparedStatementWrapper) {
throw new Error(
"Unexpected prepared statement wrapper for unprepared queries",
);
if (typeof query !== "string") {
throw new Error("Expected to obtain a string query");
}
// Get expected types from user provided hints
const expectedTypes = convertHints(execOptions.getHints() || []);
// Parse parameters according to provided hints, with type guessing
const parsedParams = parseParams(expectedTypes, params, true);
let encoded = encodeParams(
execOptions.getHints() || [],
params,
this.#encoder,
);

// Execute query
result = await this.rustClient.querySinglePage(
result = await this.rustClient.querySinglePageEncoded(
query,
parsedParams,
encoded,
rustOptions,
pageState,
);
Expand Down Expand Up @@ -652,7 +670,7 @@ class Client extends events.EventEmitter {

/**
* Async-only version of {@link Client#batch()} .
* @param {Array.<string>|Array.<{query, params}>}queries
* @param {Array.<string>|Array.<{query: string, params: Array}>}queries
* @param {queryOptions.QueryOptions} options
* @returns {Promise<ResultSet>}
* @private
Expand Down Expand Up @@ -681,9 +699,6 @@ class Client extends events.EventEmitter {
if (!element) {
throw new errors.ArgumentError(`Invalid query at index ${i}`);
}
/**
* @type {rust.PreparedStatementWrapper | string}
*/
let statement =
typeof element === "string" ? element : element.query;
let params = element.params || [];
Expand All @@ -696,18 +711,17 @@ class Client extends events.EventEmitter {
if (shouldBePrepared) {
let prepared = preparedCache.getElement(statement);
if (!prepared) {
prepared =
await this.rustClient.prepareStatement(statement);
prepared = await this.prepareQuery(statement);
preparedCache.storeElement(statement, prepared);
}
types = prepared.getExpectedTypes();
statement = prepared;
types = prepared[0];
statement = prepared[1];
} else {
types = convertHints(hints[i] || []);
types = hints[i] || [];
}

if (params) {
params = parseParams(types, params, shouldBePrepared === false);
params = encodeParams(types, params, this.#encoder);
}
allQueries.push(statement);
parametersRows.push(params);
Expand All @@ -717,7 +731,10 @@ class Client extends events.EventEmitter {
let batch = shouldBePrepared
? rust.createPreparedBatch(allQueries, rustOptions)
: rust.createUnpreparedBatch(allQueries, rustOptions);
let wrappedResult = await this.rustClient.batch(batch, parametersRows);
let wrappedResult = await this.rustClient.batchEncoded(
batch,
parametersRows,
);
return new ResultSet(wrappedResult);
}

Expand Down
13 changes: 11 additions & 2 deletions lib/encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,16 @@ class Encoder {
* - A `Number` with one of the values of {@link module:types~dataTypes dataTypes}.
* - An `Object` containing the `type.code` as one of the values of
* {@link module:types~dataTypes dataTypes} and `type.info`.
* @returns {Buffer}
* @returns {Buffer | null | undefined}
* Returns `null` for null values, `undefined` for unset values and buffer for all other values.
* @throws {TypeError} When there is an encoding error
*/
encode(value, typeInfo) {
// Here, we parse the user provided value. This can be:
// - null which represents a CQL null value.
// - types.unset which represents an unset value
// - undefined which depending on the options, we change into one of the above values
// - a regular value
if (value === undefined) {
value =
this.encodingOptions.useUndefinedAsUnset &&
Expand All @@ -247,6 +253,8 @@ class Encoder {
: null;
}

// And here, for the the internal representation, we change the types.unset into undefined
// This is only internal convention and it does not interfere with the meaning of the value provided by the user
if (value === types.unset) {
if (!types.protocolVersion.supportsUnset(this.protocolVersion)) {
throw new TypeError(
Expand All @@ -255,7 +263,7 @@ class Encoder {
);
}

return value;
return undefined;
}

if (value === null || value instanceof Buffer) {
Expand All @@ -274,6 +282,7 @@ class Encoder {
} else if (typeof typeInfo === "string") {
type = dataTypes.getByName(typeInfo);
}

if (typeof typeInfo.code === "number") {
type.code = typeInfo.code;
type.info = typeInfo.info;
Expand Down
2 changes: 1 addition & 1 deletion lib/new-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
const customErrors = require("./errors");
const util = require("./utils");
const util = require("util");

const Long = require("long");

Expand Down
Loading