Source: types/index.js

"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 name
     * @returns {{code: number, info: *|Object}}
     */
    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 typeInfo = { code: this[name], info: null };
        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
 */
const _dataTypesByCode = (function () {
    const result = {};
    for (const key in dataTypes) {
        if (!Object.prototype.hasOwnProperty.call(dataTypes, 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.
 * 
 * Use this field if you want to set a parameter to `unset`. Valid for Cassandra 2.2 and above.
 */
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;

/**
 * <strong>Backward compatibility only, use [TimeUuid]{@link module:types~TimeUuid} instead</strong>.
 * 
 * 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();
}

/**
 * <strong>Backward compatibility only, use [Uuid]{@link module:types~Uuid} class instead</strong>.
 * 
 * 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
 * @internal
 * @ignore
 * @throws {ArgumentError}
 */
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 (!item.info) {
        return typeName;
    }
    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) + ">";
    }
    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;
    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;
    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.Uuid = Uuid;
exports.unset = unset;
exports.generateTimestamp = generateTimestamp;