UNPKG

cassandra-driver

Version:

DataStax Node.js Driver for Apache Cassandra

649 lines (615 loc) 22.1 kB
/* * 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 errors = require('../errors'); const TimeUuid = require('./time-uuid'); const Uuid = require('./uuid'); const protocolVersion = require('./protocol-version'); const utils = require('../utils'); /** @module types */ /** * Long constructor, wrapper of the internal library used: {@link https://github.com/dcodeIO/long.js Long.js}. * @constructor */ const Long = require('long'); /** * Consistency levels * @type {Object} * @property {Number} any Writing: A write must be written to at least one node. If all replica nodes for the given row key are down, the write can still succeed after a hinted handoff has been written. If all replica nodes are down at write time, an ANY write is not readable until the replica nodes for that row have recovered. * @property {Number} one Returns a response from the closest replica, as determined by the snitch. * @property {Number} two Returns the most recent data from two of the closest replicas. * @property {Number} three Returns the most recent data from three of the closest replicas. * @property {Number} quorum Reading: Returns the record with the most recent timestamp after a quorum of replicas has responded regardless of data center. Writing: A write must be written to the commit log and memory table on a quorum of replica nodes. * @property {Number} all Reading: Returns the record with the most recent timestamp after all replicas have responded. The read operation will fail if a replica does not respond. Writing: A write must be written to the commit log and memory table on all replica nodes in the cluster for that row. * @property {Number} localQuorum Reading: Returns the record with the most recent timestamp once a quorum of replicas in the current data center as the coordinator node has reported. Writing: A write must be written to the commit log and memory table on a quorum of replica nodes in the same data center as the coordinator node. Avoids latency of inter-data center communication. * @property {Number} eachQuorum Reading: Returns the record once a quorum of replicas in each data center of the cluster has responded. Writing: Strong consistency. A write must be written to the commit log and memtable on a quorum of replica nodes in all data centers. * @property {Number} serial Achieves linearizable consistency for lightweight transactions by preventing unconditional updates. * @property {Number} localSerial Same as serial but confined to the data center. A write must be written conditionally to the commit log and memtable on a quorum of replica nodes in the same data center. * @property {Number} localOne Similar to One but only within the DC the coordinator is in. */ const consistencies = { any: 0x00, one: 0x01, two: 0x02, three: 0x03, quorum: 0x04, all: 0x05, localQuorum: 0x06, eachQuorum: 0x07, serial: 0x08, localSerial: 0x09, localOne: 0x0a }; /** * Mapping of consistency level codes to their string representation. * @type {Object} */ const consistencyToString = {}; consistencyToString[consistencies.any] = 'ANY'; consistencyToString[consistencies.one] = 'ONE'; consistencyToString[consistencies.two] = 'TWO'; consistencyToString[consistencies.three] = 'THREE'; consistencyToString[consistencies.quorum] = 'QUORUM'; consistencyToString[consistencies.all] = 'ALL'; consistencyToString[consistencies.localQuorum] = 'LOCAL_QUORUM'; consistencyToString[consistencies.eachQuorum] = 'EACH_QUORUM'; consistencyToString[consistencies.serial] = 'SERIAL'; consistencyToString[consistencies.localSerial] = 'LOCAL_SERIAL'; consistencyToString[consistencies.localOne] = 'LOCAL_ONE'; /** * CQL data types * @type {Object} * @property {Number} custom A custom type. * @property {Number} ascii ASCII character string. * @property {Number} bigint 64-bit signed long. * @property {Number} blob Arbitrary bytes (no validation). * @property {Number} boolean true or false. * @property {Number} counter Counter column (64-bit signed value). * @property {Number} decimal Variable-precision decimal. * @property {Number} double 64-bit IEEE-754 floating point. * @property {Number} float 32-bit IEEE-754 floating point. * @property {Number} int 32-bit signed integer. * @property {Number} text UTF8 encoded string. * @property {Number} timestamp A timestamp. * @property {Number} uuid Type 1 or type 4 UUID. * @property {Number} varchar UTF8 encoded string. * @property {Number} varint Arbitrary-precision integer. * @property {Number} timeuuid Type 1 UUID. * @property {Number} inet An IP address. It can be either 4 bytes long (IPv4) or 16 bytes long (IPv6). * @property {Number} date A date without a time-zone in the ISO-8601 calendar system. * @property {Number} time A value representing the time portion of the day. * @property {Number} smallint 16-bit two's complement integer. * @property {Number} tinyint 8-bit two's complement integer. * @property {Number} list A collection of elements. * @property {Number} map Key/value pairs. * @property {Number} set A collection that contains no duplicate elements. * @property {Number} udt User-defined type. * @property {Number} tuple A sequence of values. */ const dataTypes = { custom: 0x0000, ascii: 0x0001, bigint: 0x0002, blob: 0x0003, boolean: 0x0004, counter: 0x0005, decimal: 0x0006, double: 0x0007, float: 0x0008, int: 0x0009, text: 0x000a, timestamp: 0x000b, uuid: 0x000c, varchar: 0x000d, varint: 0x000e, timeuuid: 0x000f, inet: 0x0010, date: 0x0011, time: 0x0012, smallint: 0x0013, tinyint: 0x0014, duration: 0x0015, list: 0x0020, map: 0x0021, set: 0x0022, udt: 0x0030, tuple: 0x0031, /** * Returns the typeInfo of a given type name * @param {string} name * @returns {import('../encoder').ColumnInfo} */ getByName: function(name) { name = name.toLowerCase(); if (name.indexOf('<') > 0) { const listMatches = /^(list|set)<(.+)>$/.exec(name); if (listMatches) { return { code: this[listMatches[1]], info: this.getByName(listMatches[2])}; } const mapMatches = /^(map)< *(.+) *, *(.+)>$/.exec(name); if (mapMatches) { return { code: this[mapMatches[1]], info: [this.getByName(mapMatches[2]), this.getByName(mapMatches[3])]}; } const udtMatches = /^(udt)<(.+)>$/.exec(name); if (udtMatches) { //udt name as raw string return { code: this[udtMatches[1]], info: udtMatches[2]}; } const tupleMatches = /^(tuple)<(.+)>$/.exec(name); if (tupleMatches) { //tuple info as an array of types return { code: this[tupleMatches[1]], info: tupleMatches[2].split(',').map(function (x) { return this.getByName(x.trim()); }, this)}; } const vectorMatches = /^vector<\s*(.+)\s*,\s*(\d+)\s*>$/.exec(name); if(vectorMatches){ return { code: this.custom, customTypeName: 'vector', info: [this.getByName(vectorMatches[1]), parseInt(vectorMatches[2], 10)] }; } } const typeInfo = { code: this[name]}; if (typeof typeInfo.code !== 'number') { throw new TypeError('Data type with name ' + name + ' not valid'); } return typeInfo; } }; /** * Map of Data types by code * @internal * @private * @type {Record<number, string>} */ const _dataTypesByCode = (function () { /**@type {Record<number, string>} */ const result = {}; for (const key in dataTypes) { if (!dataTypes.hasOwnProperty(key)) { continue; } const val = dataTypes[key]; if (typeof val !== 'number') { continue; } result[val] = key; } return result; })(); /** * Represents the distance of Cassandra node as assigned by a LoadBalancingPolicy relatively to the driver instance. * @type {Object} * @property {Number} local A local node. * @property {Number} remote A remote node. * @property {Number} ignored A node that is meant to be ignored. */ const distance = { local: 0, remote: 1, ignored: 2 }; /** * An integer byte that distinguish the actual message from and to Cassandra * @internal * @ignore */ const opcodes = { error: 0x00, startup: 0x01, ready: 0x02, authenticate: 0x03, credentials: 0x04, options: 0x05, supported: 0x06, query: 0x07, result: 0x08, prepare: 0x09, execute: 0x0a, register: 0x0b, event: 0x0c, batch: 0x0d, authChallenge: 0x0e, authResponse: 0x0f, authSuccess: 0x10, cancel: 0xff, /** * Determines if the code is a valid opcode */ isInRange: function (code) { return code > this.error && code > this.event; } }; /** * Event types from Cassandra * @type {{topologyChange: string, statusChange: string, schemaChange: string}} * @internal * @ignore */ const protocolEvents = { topologyChange: 'TOPOLOGY_CHANGE', statusChange: 'STATUS_CHANGE', schemaChange: 'SCHEMA_CHANGE' }; /** * Server error codes returned by Cassandra * @type {Object} * @property {Number} serverError Something unexpected happened. * @property {Number} protocolError Some client message triggered a protocol violation. * @property {Number} badCredentials Authentication was required and failed. * @property {Number} unavailableException Raised when coordinator knows there is not enough replicas alive to perform a query with the requested consistency level. * @property {Number} overloaded The request cannot be processed because the coordinator is overloaded. * @property {Number} isBootstrapping The request was a read request but the coordinator node is bootstrapping. * @property {Number} truncateError Error encountered during a truncate request. * @property {Number} writeTimeout Timeout encountered on write query on coordinator waiting for response(s) from replicas. * @property {Number} readTimeout Timeout encountered on read query on coordinator waitign for response(s) from replicas. * @property {Number} readFailure A non-timeout error encountered during a read request. * @property {Number} functionFailure A (user defined) function encountered during execution. * @property {Number} writeFailure A non-timeout error encountered during a write request. * @property {Number} syntaxError The submitted query has a syntax error. * @property {Number} unauthorized The logged user doesn't have the right to perform the query. * @property {Number} invalid The query is syntactically correct but invalid. * @property {Number} configError The query is invalid because of some configuration issue. * @property {Number} alreadyExists The query attempted to create a schema element (i.e. keyspace, table) that already exists. * @property {Number} unprepared Can be thrown while a prepared statement tries to be executed if the provided statement is not known by the coordinator. */ const responseErrorCodes = { serverError: 0x0000, protocolError: 0x000A, badCredentials: 0x0100, unavailableException: 0x1000, overloaded: 0x1001, isBootstrapping: 0x1002, truncateError: 0x1003, writeTimeout: 0x1100, readTimeout: 0x1200, readFailure: 0x1300, functionFailure: 0x1400, writeFailure: 0x1500, syntaxError: 0x2000, unauthorized: 0x2100, invalid: 0x2200, configError: 0x2300, alreadyExists: 0x2400, unprepared: 0x2500, clientWriteFailure: 0x8000, }; /** * Type of result included in a response * @internal * @ignore */ const resultKind = { voidResult: 0x0001, rows: 0x0002, setKeyspace: 0x0003, prepared: 0x0004, schemaChange: 0x0005 }; /** * Message frame flags * @internal * @ignore */ const frameFlags = { compression: 0x01, tracing: 0x02, customPayload: 0x04, warning: 0x08 }; /** * Unset representation. * <p> * Use this field if you want to set a parameter to <code>unset</code>. Valid for Cassandra 2.2 and above. * </p> */ const unset = Object.freeze({'unset': true}); /** * A long representing the value 1000 * @const * @private */ const _longOneThousand = Long.fromInt(1000); /** * Counter used to generate up to 1000 different timestamp values with the same Date * @private */ let _timestampTicks = 0; /** * <p><strong>Backward compatibility only, use [TimeUuid]{@link module:types~TimeUuid} instead</strong>.</p> * Generates and returns a RFC4122 v1 (timestamp based) UUID in a string representation. * @param {{msecs, node, clockseq, nsecs}} [options] * @param {Buffer} [buffer] * @param {Number} [offset] * @deprecated Use [TimeUuid]{@link module:types~TimeUuid} instead */ function timeuuid(options, buffer, offset) { let date; let ticks; let nodeId; let clockId; if (options) { if (typeof options.msecs === 'number') { date = new Date(options.msecs); } if (options.msecs instanceof Date) { date = options.msecs; } if (Array.isArray(options.node)) { nodeId = utils.allocBufferFromArray(options.node); } if (typeof options.clockseq === 'number') { clockId = utils.allocBufferUnsafe(2); clockId.writeUInt16BE(options.clockseq, 0); } if (typeof options.nsecs === 'number') { ticks = options.nsecs; } } const uuid = new TimeUuid(date, ticks, nodeId, clockId); if (buffer instanceof Buffer) { //copy the values into the buffer uuid.getBuffer().copy(buffer, offset || 0); return buffer; } return uuid.toString(); } /** * <p><strong>Backward compatibility only, use [Uuid]{@link module:types~Uuid} class instead</strong>.</p> * Generate and return a RFC4122 v4 UUID in a string representation. * @deprecated Use [Uuid]{@link module:types~Uuid} class instead */ function uuid(options, buffer, offset) { let uuid; if (options) { if (Array.isArray(options.random)) { uuid = new Uuid(utils.allocBufferFromArray(options.random)); } } if (!uuid) { uuid = Uuid.random(); } if (buffer instanceof Buffer) { //copy the values into the buffer uuid.getBuffer().copy(buffer, offset || 0); return buffer; } return uuid.toString(); } /** * Gets the data type name for a given type definition, it may not work for udt or custom type * @internal * @ignore * @throws {ArgumentError} * @param {import('../encoder').ColumnInfo} item */ function getDataTypeNameByCode(item) { if (!item || typeof item.code !== 'number') { throw new errors.ArgumentError('Invalid signature type definition'); } const typeName = _dataTypesByCode[item.code]; if (!typeName) { throw new errors.ArgumentError(util.format('Type with code %d not found', item.code)); } if (!('info' in item) || !item.info) { return typeName; } // special case for vector if (item.code === dataTypes.custom && 'customTypeName' in item && item.customTypeName === 'vector') { return 'vector<' + getDataTypeNameByCode(item.info[0]) + ', ' + item.info[1] + '>'; } if (Array.isArray(item.info)) { return (typeName + '<' + item.info.map(function (t) { return getDataTypeNameByCode(t); }).join(', ') + '>'); } if (typeof item.info.code === 'number') { return typeName + '<' + getDataTypeNameByCode(item.info) + '>'; } if (item.code === dataTypes.udt) { return (/**@type {UdtColumnInfo}*/item).info.name; } return typeName; } //classes /** * Represents a frame header that could be used to read from a Buffer or to write to a Buffer * @ignore * @param {Number} version Protocol version * @param {Number} flags * @param {Number} streamId * @param {Number} opcode * @param {Number} bodyLength * @constructor */ function FrameHeader(version, flags, streamId, opcode, bodyLength) { this.version = version; this.flags = flags; this.streamId = streamId; this.opcode = opcode; this.bodyLength = bodyLength; } /** * The length of the header of the frame based on the protocol version * @returns {Number} */ FrameHeader.size = function (version) { if (protocolVersion.uses2BytesStreamIds(version)) { return 9; } return 8; }; /** * Gets the protocol version based on the first byte of the header * @param {Buffer} buffer * @returns {Number} */ FrameHeader.getProtocolVersion = function (buffer) { return buffer[0] & 0x7F; }; /** * @param {Buffer} buf * @param {Number} [offset] * @returns {FrameHeader} */ FrameHeader.fromBuffer = function (buf, offset) { let streamId = 0; if (!offset) { offset = 0; } const version = buf[offset++] & 0x7F; const flags = buf.readUInt8(offset++); if (!protocolVersion.uses2BytesStreamIds(version)) { streamId = buf.readInt8(offset++); } else { streamId = buf.readInt16BE(offset); offset += 2; } return new FrameHeader(version, flags, streamId, buf.readUInt8(offset++), buf.readUInt32BE(offset)); }; /** @returns {Buffer} */ FrameHeader.prototype.toBuffer = function () { const buf = utils.allocBufferUnsafe(FrameHeader.size(this.version)); buf.writeUInt8(this.version, 0); buf.writeUInt8(this.flags, 1); let offset = 3; if (!protocolVersion.uses2BytesStreamIds(this.version)) { buf.writeInt8(this.streamId, 2); } else { buf.writeInt16BE(this.streamId, 2); offset = 4; } buf.writeUInt8(this.opcode, offset++); buf.writeUInt32BE(this.bodyLength, offset); return buf; }; /** * Returns a long representation. * Used internally for deserialization */ Long.fromBuffer = function (value) { if (!(value instanceof Buffer)) { throw new TypeError('Expected Buffer, obtained ' + util.inspect(value)); } return new Long(value.readInt32BE(4), value.readInt32BE(0)); }; /** * Returns a big-endian buffer representation of the Long instance * @param {Long} value */ Long.toBuffer = function (value) { if (!(value instanceof Long)) { throw new TypeError('Expected Long, obtained ' + util.inspect(value)); } const buffer = utils.allocBufferUnsafe(8); buffer.writeUInt32BE(value.getHighBitsUnsigned(), 0); buffer.writeUInt32BE(value.getLowBitsUnsigned(), 4); return buffer; }; /** * Provide the name of the constructor and the string representation * @returns {string} */ Long.prototype.inspect = function () { return 'Long: ' + this.toString(); }; /** * Returns the string representation. * Method used by the native JSON.stringify() to serialize this instance */ Long.prototype.toJSON = function () { return this.toString(); }; /** * Generates a value representing the timestamp for the query in microseconds based on the date and the microseconds provided * @param {Date} [date] The date to generate the value, if not provided it will use the current date. * @param {Number} [microseconds] A number from 0 to 999 used to build the microseconds part of the date. * @returns {Long} */ function generateTimestamp(date, microseconds) { if (!date) { date = new Date(); } let longMicro = Long.ZERO; if (typeof microseconds === 'number' && microseconds >= 0 && microseconds < 1000) { longMicro = Long.fromInt(microseconds); } else { if (_timestampTicks > 999) { _timestampTicks = 0; } longMicro = Long.fromInt(_timestampTicks); _timestampTicks++; } return Long .fromNumber(date.getTime()) .multiply(_longOneThousand) .add(longMicro); } //error classes /** @private */ function QueryParserError(e) { QueryParserError.super_.call(this, e.message, this.constructor); this.internalError = e; } util.inherits(QueryParserError, errors.DriverError); /** @private */ function TimeoutError (message) { TimeoutError.super_.call(this, message, this.constructor); this.info = 'Represents an error that happens when the maximum amount of time for an operation passed.'; } util.inherits(TimeoutError, errors.DriverError); exports.opcodes = opcodes; exports.consistencies = consistencies; exports.consistencyToString = consistencyToString; exports.dataTypes = dataTypes; exports.getDataTypeNameByCode = getDataTypeNameByCode; exports.distance = distance; exports.frameFlags = frameFlags; exports.protocolEvents = protocolEvents; exports.protocolVersion = protocolVersion; exports.responseErrorCodes = responseErrorCodes; exports.resultKind = resultKind; exports.timeuuid = timeuuid; exports.uuid = uuid; exports.BigDecimal = require('./big-decimal'); exports.Duration = require('./duration'); exports.FrameHeader = FrameHeader; exports.InetAddress = require('./inet-address'); exports.Integer = require('./integer'); exports.LocalDate = require('./local-date'); exports.LocalTime = require('./local-time'); exports.Long = Long; exports.ResultSet = require('./result-set'); exports.ResultStream = require('./result-stream'); exports.Row = require('./row'); //export DriverError for backward-compatibility exports.DriverError = errors.DriverError; exports.TimeoutError = TimeoutError; exports.TimeUuid = TimeUuid; exports.Tuple = require('./tuple'); exports.Vector = require('./vector'); exports.Uuid = Uuid; exports.unset = unset; exports.generateTimestamp = generateTimestamp;