You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
543 lines
17 KiB
JavaScript
543 lines
17 KiB
JavaScript
/*
|
|
* Copyright DataStax, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
'use strict';
|
|
const util = require('util');
|
|
|
|
const { FrameWriter } = require('./writers');
|
|
const types = require('./types');
|
|
const utils = require('./utils');
|
|
const { ExecutionOptions } = require('./execution-options');
|
|
const packageInfo = require('../package.json');
|
|
|
|
/**
|
|
* Options for the execution of the query / prepared statement
|
|
* @private
|
|
*/
|
|
const queryFlag = {
|
|
values: 0x01,
|
|
skipMetadata: 0x02,
|
|
pageSize: 0x04,
|
|
withPagingState: 0x08,
|
|
withSerialConsistency: 0x10,
|
|
withDefaultTimestamp: 0x20,
|
|
withNameForValues: 0x40,
|
|
withKeyspace: 0x80,
|
|
withPageSizeBytes: 0x40000000,
|
|
withContinuousPaging: 0x80000000
|
|
};
|
|
|
|
/**
|
|
* Options for the execution of a batch request from protocol v3 and above
|
|
* @private
|
|
*/
|
|
const batchFlag = {
|
|
withSerialConsistency: 0x10,
|
|
withDefaultTimestamp: 0x20,
|
|
withNameForValues: 0x40,
|
|
withKeyspace: 0x80
|
|
};
|
|
|
|
/**
|
|
* Options for execution of a prepare request from protocol DSE_V2 and above
|
|
* @private
|
|
*/
|
|
const prepareFlag = {
|
|
withKeyspace: 0x01
|
|
};
|
|
|
|
const batchType = {
|
|
logged: 0,
|
|
unlogged: 1,
|
|
counter: 2
|
|
};
|
|
|
|
/**
|
|
* Abstract class Request
|
|
*/
|
|
class Request {
|
|
constructor() {
|
|
this.length = 0;
|
|
}
|
|
|
|
/**
|
|
* @abstract
|
|
* @param {Encoder} encoder
|
|
* @param {Number} streamId
|
|
* @throws {TypeError}
|
|
* @returns {Buffer}
|
|
*/
|
|
write(encoder, streamId) {
|
|
throw new Error('Method must be implemented');
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance using the same constructor as the current instance, copying the properties.
|
|
* @return {Request}
|
|
*/
|
|
clone() {
|
|
const newRequest = new (this.constructor)();
|
|
const keysArray = Object.keys(this);
|
|
for (let i = 0; i < keysArray.length; i++) {
|
|
const key = keysArray[i];
|
|
newRequest[key] = this[key];
|
|
}
|
|
return newRequest;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a execute query (given a prepared queryId)
|
|
* @param {String} query
|
|
* @param {Buffer} queryId
|
|
* @param {Array} params
|
|
* @param options
|
|
*/
|
|
class ExecuteRequest extends Request {
|
|
/**
|
|
* @param {String} query
|
|
* @param queryId
|
|
* @param params
|
|
* @param {ExecutionOptions} execOptions
|
|
* @param meta
|
|
*/
|
|
constructor(query, queryId, params, execOptions, meta) {
|
|
super();
|
|
|
|
this.query = query;
|
|
this.queryId = queryId;
|
|
this.params = params;
|
|
this.meta = meta;
|
|
this.options = execOptions || ExecutionOptions.empty();
|
|
this.consistency = this.options.getConsistency() || types.consistencies.one;
|
|
// Only QUERY request parameters are encoded as named parameters
|
|
// EXECUTE request parameters are always encoded as positional parameters
|
|
this.namedParameters = false;
|
|
}
|
|
|
|
getParamType(index) {
|
|
const columnInfo = this.meta.columns[index];
|
|
return columnInfo ? columnInfo.type : null;
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
//v1: <queryId>
|
|
// <n><value_1>....<value_n><consistency>
|
|
//v2: <queryId>
|
|
// <consistency><flags>[<n><value_1>...<value_n>][<result_page_size>][<paging_state>][<serial_consistency>]
|
|
//v3: <queryId>
|
|
// <consistency><flags>[<n>[name_1]<value_1>...[name_n]<value_n>][<result_page_size>][<paging_state>][<serial_consistency>][<timestamp>]
|
|
const frameWriter = new FrameWriter(types.opcodes.execute);
|
|
let headerFlags = this.options.isQueryTracing() ? types.frameFlags.tracing : 0;
|
|
if (this.options.getCustomPayload()) {
|
|
//The body may contain the custom payload
|
|
headerFlags |= types.frameFlags.customPayload;
|
|
frameWriter.writeCustomPayload(this.options.getCustomPayload());
|
|
}
|
|
frameWriter.writeShortBytes(this.queryId);
|
|
if(types.protocolVersion.supportsResultMetadataId(encoder.protocolVersion)) {
|
|
frameWriter.writeShortBytes(this.meta.resultId);
|
|
}
|
|
this.writeQueryParameters(frameWriter, encoder);
|
|
|
|
// Record the length of the body of the request before writing it
|
|
this.length = frameWriter.bodyLength;
|
|
|
|
return frameWriter.write(encoder.protocolVersion, streamId, headerFlags);
|
|
}
|
|
|
|
/**
|
|
* Writes v1 and v2 execute query parameters
|
|
* @param {FrameWriter} frameWriter
|
|
* @param {Encoder} encoder
|
|
* @param {Boolean} [isQuery] True if query, otherwise assumed to be execute request.
|
|
*/
|
|
writeQueryParameters(frameWriter, encoder, isQuery) {
|
|
//v1: <n><value_1>....<value_n><consistency>
|
|
//v2: <consistency><flags>[<n><value_1>...<value_n>][<result_page_size>][<paging_state>][<serial_consistency>]
|
|
//v3: <consistency><flags>[<n>[name_1]<value_1>...[name_n]<value_n>][<result_page_size>][<paging_state>][<serial_consistency>][<timestamp>]
|
|
//dse_v1: <consistency><flags>[<n>[name_1]<value_1>...[name_n]<value_n>][<result_page_size>][<paging_state>]
|
|
// [<serial_consistency>][<timestamp>][continuous_paging_options]
|
|
//dse_v2: <consistency><flags>[<n>[name_1]<value_1>...[name_n]<value_n>][<result_page_size>][<paging_state>]
|
|
// [<serial_consistency>][<timestamp>][keyspace][continuous_paging_options]
|
|
let flags = 0;
|
|
|
|
const timestamp = this.options.getOrGenerateTimestamp();
|
|
|
|
if (types.protocolVersion.supportsPaging(encoder.protocolVersion)) {
|
|
flags |= (this.params && this.params.length) ? queryFlag.values : 0;
|
|
flags |= (this.options.getFetchSize() > 0) ? queryFlag.pageSize : 0;
|
|
flags |= this.options.getPageState() ? queryFlag.withPagingState : 0;
|
|
flags |= this.options.getSerialConsistency() ? queryFlag.withSerialConsistency : 0;
|
|
flags |= timestamp !== null && timestamp !== undefined ? queryFlag.withDefaultTimestamp : 0;
|
|
flags |= this.namedParameters ? queryFlag.withNameForValues : 0;
|
|
|
|
// Don't inject keyspace for EXECUTE requests as inherited from prepared statement.
|
|
const supportsKeyspace = isQuery && types.protocolVersion.supportsKeyspaceInRequest(encoder.protocolVersion);
|
|
flags |= supportsKeyspace && this.options.getKeyspace() ? queryFlag.withKeyspace : 0;
|
|
|
|
frameWriter.writeShort(this.consistency);
|
|
if (types.protocolVersion.uses4BytesQueryFlags(encoder.protocolVersion)) {
|
|
frameWriter.writeInt(flags);
|
|
}
|
|
else {
|
|
frameWriter.writeByte(flags);
|
|
}
|
|
}
|
|
|
|
if (this.params && this.params.length) {
|
|
frameWriter.writeShort(this.params.length);
|
|
for (let i = 0; i < this.params.length; i++) {
|
|
let paramValue = this.params[i];
|
|
if (flags & queryFlag.withNameForValues) {
|
|
//parameter is composed by name / value
|
|
frameWriter.writeString(paramValue.name);
|
|
paramValue = paramValue.value;
|
|
}
|
|
frameWriter.writeBytes(encoder.encode(paramValue, this.getParamType(i)));
|
|
}
|
|
}
|
|
|
|
if (!types.protocolVersion.supportsPaging(encoder.protocolVersion)) {
|
|
if (!this.params || !this.params.length) {
|
|
//zero parameters
|
|
frameWriter.writeShort(0);
|
|
}
|
|
frameWriter.writeShort(this.consistency);
|
|
return;
|
|
}
|
|
if (flags & queryFlag.pageSize) {
|
|
frameWriter.writeInt(this.options.getFetchSize());
|
|
}
|
|
if (flags & queryFlag.withPagingState) {
|
|
frameWriter.writeBytes(this.options.getPageState());
|
|
}
|
|
if (flags & queryFlag.withSerialConsistency) {
|
|
frameWriter.writeShort(this.options.getSerialConsistency());
|
|
}
|
|
if (flags & queryFlag.withDefaultTimestamp) {
|
|
frameWriter.writeLong(timestamp);
|
|
}
|
|
if (flags & queryFlag.withKeyspace) {
|
|
frameWriter.writeString(this.options.getKeyspace());
|
|
}
|
|
}
|
|
}
|
|
|
|
class QueryRequest extends ExecuteRequest {
|
|
/**
|
|
* @param {String} query
|
|
* @param params
|
|
* @param {ExecutionOptions} [execOptions]
|
|
* @param {Boolean} [namedParameters]
|
|
*/
|
|
constructor(query, params, execOptions, namedParameters) {
|
|
super(query, null, params, execOptions, null);
|
|
this.hints = this.options.getHints() || utils.emptyArray;
|
|
this.namedParameters = namedParameters;
|
|
}
|
|
|
|
getParamType(index) {
|
|
return this.hints[index];
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
//v1: <query><consistency>
|
|
//v2: <query>
|
|
// <consistency><flags>[<n><value_1>...<value_n>][<result_page_size>][<paging_state>][<serial_consistency>]
|
|
//v3: <query>
|
|
// <consistency><flags>[<n>[name_1]<value_1>...[name_n]<value_n>][<result_page_size>][<paging_state>][<serial_consistency>][<timestamp>]
|
|
const frameWriter = new FrameWriter(types.opcodes.query);
|
|
let headerFlags = this.options.isQueryTracing() ? types.frameFlags.tracing : 0;
|
|
if (this.options.getCustomPayload()) {
|
|
//The body may contain the custom payload
|
|
headerFlags |= types.frameFlags.customPayload;
|
|
frameWriter.writeCustomPayload(this.options.getCustomPayload());
|
|
}
|
|
|
|
frameWriter.writeLString(this.query);
|
|
|
|
if (!types.protocolVersion.supportsPaging(encoder.protocolVersion)) {
|
|
frameWriter.writeShort(this.consistency);
|
|
} else {
|
|
//Use the same fields as the execute writer
|
|
this.writeQueryParameters(frameWriter, encoder, true);
|
|
}
|
|
|
|
// Record the length of the body of the request before writing it
|
|
this.length = frameWriter.bodyLength;
|
|
|
|
return frameWriter.write(encoder.protocolVersion, streamId, headerFlags);
|
|
}
|
|
}
|
|
|
|
class PrepareRequest extends Request {
|
|
constructor(query, keyspace) {
|
|
super();
|
|
this.query = query;
|
|
this.keyspace = keyspace;
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.prepare);
|
|
frameWriter.writeLString(this.query);
|
|
if (types.protocolVersion.supportsPrepareFlags(encoder.protocolVersion)) {
|
|
const flags = this.keyspace && types.protocolVersion.supportsKeyspaceInRequest(encoder.protocolVersion) ? prepareFlag.withKeyspace : 0;
|
|
frameWriter.writeInt(flags);
|
|
if (flags & prepareFlag.withKeyspace) {
|
|
frameWriter.writeString(this.keyspace);
|
|
}
|
|
}
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
}
|
|
}
|
|
|
|
class StartupRequest extends Request {
|
|
|
|
/**
|
|
* Creates a new instance of {@link StartupRequest}.
|
|
* @param {Object} [options]
|
|
* @param [options.cqlVersion]
|
|
* @param [options.noCompact]
|
|
* @param [options.clientId]
|
|
* @param [options.applicationName]
|
|
* @param [options.applicationVersion]
|
|
*/
|
|
constructor(options) {
|
|
super();
|
|
this.options = options || {};
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.startup);
|
|
|
|
const startupOptions = {
|
|
CQL_VERSION: this.options.cqlVersion || '3.0.0',
|
|
DRIVER_NAME: packageInfo.description,
|
|
DRIVER_VERSION: packageInfo.version
|
|
};
|
|
|
|
if(this.options.noCompact) {
|
|
startupOptions['NO_COMPACT'] = 'true';
|
|
}
|
|
|
|
if (this.options.clientId) {
|
|
startupOptions['CLIENT_ID'] = this.options.clientId.toString();
|
|
}
|
|
|
|
if (this.options.applicationName) {
|
|
startupOptions['APPLICATION_NAME'] = this.options.applicationName;
|
|
}
|
|
|
|
if (this.options.applicationVersion) {
|
|
startupOptions['APPLICATION_VERSION'] = this.options.applicationVersion;
|
|
}
|
|
|
|
frameWriter.writeStringMap(startupOptions);
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
}
|
|
}
|
|
|
|
class RegisterRequest extends Request {
|
|
constructor(events) {
|
|
super();
|
|
this.events = events;
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.register);
|
|
frameWriter.writeStringList(this.events);
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents an AUTH_RESPONSE request
|
|
* @param {Buffer} token
|
|
*/
|
|
class AuthResponseRequest extends Request {
|
|
constructor(token) {
|
|
super();
|
|
this.token = token;
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.authResponse);
|
|
frameWriter.writeBytes(this.token);
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a protocol v1 CREDENTIALS request message
|
|
*/
|
|
class CredentialsRequest extends Request {
|
|
constructor(username, password) {
|
|
super();
|
|
this.username = username;
|
|
this.password = password;
|
|
}
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.credentials);
|
|
frameWriter.writeStringMap({ username:this.username, password:this.password });
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
}
|
|
}
|
|
|
|
class BatchRequest extends Request {
|
|
/**
|
|
* Creates a new instance of BatchRequest.
|
|
* @param {Array.<{query, params, [info]}>} queries Array of objects with the properties query and params
|
|
* @param {ExecutionOptions} execOptions
|
|
*/
|
|
constructor(queries, execOptions) {
|
|
super();
|
|
this.queries = queries;
|
|
this.options = execOptions;
|
|
this.hints = execOptions.getHints() || utils.emptyArray;
|
|
this.type = batchType.logged;
|
|
|
|
if (execOptions.isBatchCounter()) {
|
|
this.type = batchType.counter;
|
|
} else if (!execOptions.isBatchLogged()) {
|
|
this.type = batchType.unlogged;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a batch request
|
|
*/
|
|
write(encoder, streamId) {
|
|
//v2: <type><n><query_1>...<query_n><consistency>
|
|
//v3: <type><n><query_1>...<query_n><consistency><flags>[<serial_consistency>][<timestamp>]
|
|
//dseV1+: similar to v3/v4, flags is an int instead of a byte
|
|
if (!this.queries || !(this.queries.length > 0)) {
|
|
throw new TypeError(util.format('Invalid queries provided %s', this.queries));
|
|
}
|
|
const frameWriter = new FrameWriter(types.opcodes.batch);
|
|
let headerFlags = this.options.isQueryTracing() ? types.frameFlags.tracing : 0;
|
|
if (this.options.getCustomPayload()) {
|
|
//The body may contain the custom payload
|
|
headerFlags |= types.frameFlags.customPayload;
|
|
frameWriter.writeCustomPayload(this.options.getCustomPayload());
|
|
}
|
|
frameWriter.writeByte(this.type);
|
|
frameWriter.writeShort(this.queries.length);
|
|
const self = this;
|
|
this.queries.forEach(function eachQuery(item, i) {
|
|
const hints = self.hints[i];
|
|
const params = item.params || utils.emptyArray;
|
|
let getParamType;
|
|
if (item.queryId) {
|
|
// Contains prepared queries
|
|
frameWriter.writeByte(1);
|
|
frameWriter.writeShortBytes(item.queryId);
|
|
getParamType = i => item.meta.columns[i].type;
|
|
}
|
|
else {
|
|
// Contains string queries
|
|
frameWriter.writeByte(0);
|
|
frameWriter.writeLString(item.query);
|
|
getParamType = hints ? (i => hints[i]) : (() => null);
|
|
}
|
|
|
|
frameWriter.writeShort(params.length);
|
|
params.forEach((param, index) => frameWriter.writeBytes(encoder.encode(param, getParamType(index))));
|
|
}, this);
|
|
|
|
frameWriter.writeShort(this.options.getConsistency());
|
|
|
|
if (types.protocolVersion.supportsTimestamp(encoder.protocolVersion)) {
|
|
// Batch flags
|
|
let flags = this.options.getSerialConsistency() ? batchFlag.withSerialConsistency : 0;
|
|
const timestamp = this.options.getOrGenerateTimestamp();
|
|
flags |= timestamp !== null && timestamp !== undefined ? batchFlag.withDefaultTimestamp : 0;
|
|
|
|
flags |= this.options.getKeyspace() && types.protocolVersion.supportsKeyspaceInRequest(encoder.protocolVersion)
|
|
? batchFlag.withKeyspace : 0;
|
|
|
|
if (types.protocolVersion.uses4BytesQueryFlags(encoder.protocolVersion)) {
|
|
frameWriter.writeInt(flags);
|
|
}
|
|
else {
|
|
frameWriter.writeByte(flags);
|
|
}
|
|
|
|
if (flags & batchFlag.withSerialConsistency) {
|
|
frameWriter.writeShort(this.options.getSerialConsistency());
|
|
}
|
|
|
|
if (flags & batchFlag.withDefaultTimestamp) {
|
|
frameWriter.writeLong(timestamp);
|
|
}
|
|
|
|
if (flags & batchFlag.withKeyspace) {
|
|
frameWriter.writeString(this.options.getKeyspace());
|
|
}
|
|
}
|
|
|
|
// Set the length of the body of the request before writing it
|
|
this.length = frameWriter.bodyLength;
|
|
|
|
return frameWriter.write(encoder.protocolVersion, streamId, headerFlags);
|
|
}
|
|
|
|
clone() {
|
|
return new BatchRequest(this.queries, this.options);
|
|
}
|
|
}
|
|
|
|
function CancelRequest(operationId) {
|
|
this.streamId = null;
|
|
this.operationId = operationId;
|
|
}
|
|
|
|
util.inherits(CancelRequest, Request);
|
|
|
|
CancelRequest.prototype.write = function (encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.cancel);
|
|
frameWriter.writeInt(1);
|
|
frameWriter.writeInt(this.operationId);
|
|
return frameWriter.write(encoder.protocolVersion, streamId);
|
|
};
|
|
|
|
class OptionsRequest extends Request {
|
|
|
|
write(encoder, streamId) {
|
|
const frameWriter = new FrameWriter(types.opcodes.options);
|
|
return frameWriter.write(encoder.protocolVersion, streamId, 0);
|
|
}
|
|
|
|
clone() {
|
|
// since options has no unique state, simply return self.
|
|
return this;
|
|
}
|
|
}
|
|
|
|
const options = new OptionsRequest();
|
|
|
|
exports.AuthResponseRequest = AuthResponseRequest;
|
|
exports.BatchRequest = BatchRequest;
|
|
exports.CancelRequest = CancelRequest;
|
|
exports.CredentialsRequest = CredentialsRequest;
|
|
exports.ExecuteRequest = ExecuteRequest;
|
|
exports.PrepareRequest = PrepareRequest;
|
|
exports.QueryRequest = QueryRequest;
|
|
exports.Request = Request;
|
|
exports.RegisterRequest = RegisterRequest;
|
|
exports.StartupRequest = StartupRequest;
|
|
exports.options = options;
|