} an iterator over the elements of the vector
+ */
+ [Symbol.iterator]() {
+ return this.elements[Symbol.iterator]();
+ }
+
+ static get [Symbol.species]() {
+ return Vector;
+ }
+
+ /**
+ *
+ * @param {(value: any, index: number, array: any[]) => void} callback
+ */
+ forEach(callback) {
+ return this.elements.forEach(callback);
+ }
+
+ /**
+ * @returns {string | undefined} get the subtype string, e.g., "float", but it's optional so it can return null
+ */
+ getSubtype(){
+ return this.subtype;
+ }
+}
+
+Object.defineProperty(Vector, Symbol.hasInstance, {
+ value: function (i) {
+ return (util.types.isProxy(i) && i.IDENTITY === 'Vector') || i instanceof Float32Array; }
+});
+
+module.exports = Vector;
diff --git a/lib/utils.js b/lib/utils.js
index 395f122c..dfc217a7 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -15,6 +15,7 @@
*/
'use strict';
+const Long = require('long');
const util = require('util');
const net = require('net');
const { EventEmitter } = require('events');
@@ -1040,6 +1041,335 @@ function whilst(condition, fn, callback) {
}
}
+
+/**
+ * Contains the methods for reading and writing vints into binary format.
+ * Exposes only 2 internal methods, the rest are hidden.
+ */
+const VIntCoding = (function () {
+ /** @param {Long} n */
+ function encodeZigZag64(n) {
+ // (n << 1) ^ (n >> 63);
+ return n.toUnsigned().shiftLeft(1).xor(n.shiftRight(63));
+ }
+
+ /** @param {Long} n */
+ function decodeZigZag64(n) {
+ // (n >>> 1) ^ -(n & 1);
+ return n.shiftRightUnsigned(1).xor(n.and(Long.ONE).negate());
+ }
+
+ /**
+ * @param {Long} value
+ * @param {Buffer} buffer
+ * @returns {Number}
+ */
+ function writeVInt(value, buffer) {
+ return writeUnsignedVInt(encodeZigZag64(value), buffer);
+ }
+
+ /**
+ * @param {Long} value
+ * @param {Buffer} buffer
+ * @returns {number}
+ */
+ function writeUnsignedVInt(value, buffer) {
+ const size = computeUnsignedVIntSize(value);
+ if (size === 1) {
+ buffer[0] = value.getLowBits();
+ return 1;
+ }
+ encodeVInt(value, size, buffer);
+ return size;
+ }
+
+ /**
+ * @param {Long} value
+ * @returns {number}
+ */
+ function computeUnsignedVIntSize(value) {
+ const magnitude = numberOfLeadingZeros(value.or(Long.ONE));
+ return (639 - magnitude * 9) >> 6;
+ }
+
+ /**
+ * @param {Long} value
+ * @param {Number} size
+ * @param {Buffer} buffer
+ */
+ function encodeVInt(value, size, buffer) {
+ const extraBytes = size - 1;
+ let intValue = value.getLowBits();
+ let i;
+ let intBytes = 4;
+ for (i = extraBytes; i >= 0 && (intBytes--) > 0; i--) {
+ buffer[i] = 0xFF & intValue;
+ intValue >>= 8;
+ }
+ intValue = value.getHighBits();
+ for (; i >= 0; i--) {
+ buffer[i] = 0xFF & intValue;
+ intValue >>= 8;
+ }
+ buffer[0] |= encodeExtraBytesToRead(extraBytes);
+ }
+ /**
+ * Returns the number of zero bits preceding the highest-order one-bit in the binary representation of the value.
+ * @param {Long} value
+ * @returns {Number}
+ */
+ function numberOfLeadingZeros(value) {
+ if (value.equals(Long.ZERO)) {
+ return 64;
+ }
+ let n = 1;
+ let x = value.getHighBits();
+ if (x === 0) {
+ n += 32;
+ x = value.getLowBits();
+ }
+ if (x >>> 16 === 0) {
+ n += 16;
+ x <<= 16;
+ }
+ if (x >>> 24 === 0) {
+ n += 8;
+ x <<= 8;
+ }
+ if (x >>> 28 === 0) {
+ n += 4;
+ x <<= 4;
+ }
+ if (x >>> 30 === 0) {
+ n += 2;
+ x <<= 2;
+ }
+ n -= x >>> 31;
+ return n;
+ }
+
+
+ function encodeExtraBytesToRead(extraBytesToRead) {
+ return ~(0xff >> extraBytesToRead);
+ }
+
+ /**
+ * @param {Buffer} buffer
+ * @param {{value: number}} offset
+ * @returns {Long}
+ */
+ function readVInt(buffer, offset) {
+ return decodeZigZag64(readUnsignedVInt(buffer, offset));
+ }
+
+ /**
+ * uvint_unpack
+ * @param {Buffer} input
+ * @param {{ value: number}} offset
+ * @returns {Long}
+ */
+ function readUnsignedVInt(input, offset) {
+ const firstByte = input[offset.value++];
+ if ((firstByte & 0x80) === 0) {
+ return Long.fromInt(firstByte);
+ }
+ const sByteInt = fromSignedByteToInt(firstByte);
+ const size = numberOfExtraBytesToRead(sByteInt);
+ let result = Long.fromInt(sByteInt & firstByteValueMask(size));
+ for (let ii = 0; ii < size; ii++) {
+ const b = Long.fromInt(input[offset.value++]);
+ // (result << 8) | b
+ result = result.shiftLeft(8).or(b);
+ }
+ return result;
+ }
+
+ function fromSignedByteToInt(value) {
+ if (value > 0x7f) {
+ return value - 0x0100;
+ }
+ return value;
+ }
+
+ function numberOfLeadingZerosInt32(i) {
+ if (i === 0) {
+ return 32;
+ }
+ let n = 1;
+ if (i >>> 16 === 0) {
+ n += 16;
+ i <<= 16;
+ }
+ if (i >>> 24 === 0) {
+ n += 8;
+ i <<= 8;
+ }
+ if (i >>> 28 === 0) {
+ n += 4;
+ i <<= 4;
+ }
+ if (i >>> 30 === 0) {
+ n += 2;
+ i <<= 2;
+ }
+ n -= i >>> 31;
+ return n;
+ }
+
+ /**
+ * @param {Number} firstByte
+ * @returns {Number}
+ */
+ function numberOfExtraBytesToRead(firstByte) {
+ // Instead of counting 1s of the byte, we negate and count 0 of the byte
+ return numberOfLeadingZerosInt32(~firstByte) - 24;
+ }
+
+ /**
+ * @param {Number} extraBytesToRead
+ * @returns {Number}
+ */
+ function firstByteValueMask(extraBytesToRead) {
+ return 0xff >> extraBytesToRead;
+ }
+
+ /**
+ * @param {Number} value
+ * @param {Buffer} output
+ * @returns {void}
+ */
+ // eslint-disable-next-line no-unused-vars
+ function writeUnsignedVInt32(value, output) {
+ writeUnsignedVInt(Long.fromNumber(value), output);
+ }
+
+ /**
+ * Read up to a 32-bit integer back, using the unsigned (no zigzag) encoding.
+ *
+ * Note this method is the same as {@link #readUnsignedVInt(DataInput)}, except that we do
+ * *not* block if there are not enough bytes in the buffer to reconstruct the value.
+ *
+ * @param {Buffer} input
+ * @param {Number} readerIndex
+ * @returns {Number}
+ * @throws VIntOutOfRangeException If the vint doesn't fit into a 32-bit integer
+ */
+ // eslint-disable-next-line no-unused-vars
+ function getUnsignedVInt32(input, readerIndex) {
+ return checkedCast(getUnsignedVInt(input, readerIndex, input.length));
+ }
+
+ /**
+ *
+ * @param {Buffer} input
+ * @param {Number} readerIndex
+ * @param {Number} readerLimit
+ * @returns {Long}
+ */
+ function getUnsignedVInt(input, readerIndex, readerLimit) {
+ if (readerIndex < 0)
+ {throw new errors.ArgumentError(
+ "Reader index should be non-negative, but was " + readerIndex);}
+
+ if (readerIndex >= readerLimit) {return Long.fromNumber(-1);}
+
+ const firstByte = /** @type {Number} */ (input.at(readerIndex++));
+
+ // Bail out early if this is one byte, necessary or it fails later
+ if (firstByte >= 0) {return Long.fromNumber(firstByte);}
+
+ const size = numberOfExtraBytesToRead(firstByte);
+ if (readerIndex + size > readerLimit) {return Long.fromNumber(-1);}
+
+ const retval = Long.fromNumber(firstByte & firstByteValueMask(size));
+ for (let ii = 0; ii < size; ii++) {
+ const b = /** @type {Number} */ (input.at(readerIndex++));
+ retval.shiftLeft(8);
+ retval.or(b & 0xff);
+ }
+
+ return retval;
+ }
+
+ /**
+ *
+ * @param {Long} value
+ * @returns {Number}
+ */
+ function checkedCast(value) {
+ const result = value.toInt();
+ if (value.notEquals(result)) {throw new errors.VIntOutOfRangeException(value);}
+ return result;
+ }
+
+ /**
+ *
+ * @param {Buffer} bytes
+ * @returns {[number, number]} [size, bytes read]
+ */
+ function uvintUnpack(bytes) {
+ const firstByte = bytes[0];
+
+ if ((firstByte & 128) === 0) {
+ return [firstByte, 1];
+ }
+
+ const numExtraBytes = 8 - (~firstByte & 0xff).toString(2).length;
+ let rv = firstByte & (0xff >> numExtraBytes);
+
+ for (let idx = 1; idx <= numExtraBytes; idx++) {
+ const newByte = bytes[idx];
+ rv <<= 8;
+ rv |= newByte & 0xff;
+ }
+
+ return [rv, numExtraBytes + 1];
+ }
+
+ /**
+ *
+ * @param {Number} val
+ * @returns {Buffer}
+ */
+ function uvintPack(val) {
+ const rv = [];
+ if (val < 128) {
+ rv.push(val);
+ } else {
+ let v = val;
+ let numExtraBytes = 0;
+ let numBits = v.toString(2).length;
+ let reservedBits = numExtraBytes + 1;
+
+ while (numBits > (8 - reservedBits)) {
+ numExtraBytes += 1;
+ numBits -= 8;
+ reservedBits = Math.min(numExtraBytes + 1, 8);
+ rv.push(v & 0xff);
+ v >>= 8;
+ }
+
+ if (numExtraBytes > 8) {
+ throw new Error(`Value ${val} is too big and cannot be encoded as vint`);
+ }
+
+ const n = 8 - numExtraBytes;
+ v |= (0xff >> n) << n;
+ rv.push(Math.abs(v));
+ }
+
+ rv.reverse();
+ return Buffer.from(rv);
+ }
+
+ return {
+ readVInt: readVInt,
+ writeVInt: writeVInt,
+ uvintPack: uvintPack,
+ uvintUnpack: uvintUnpack
+ };
+})();
+
exports.adaptNamedParamsPrepared = adaptNamedParamsPrepared;
exports.adaptNamedParamsWithHints = adaptNamedParamsWithHints;
exports.AddressResolver = AddressResolver;
@@ -1084,4 +1414,5 @@ exports.timesSeries = timesSeries;
exports.totalLength = totalLength;
exports.validateFn = validateFn;
exports.whilst = whilst;
-exports.HashSet = HashSet;
\ No newline at end of file
+exports.HashSet = HashSet;
+exports.VIntCoding = VIntCoding;
\ No newline at end of file
diff --git a/test/integration/short/vector-tests.js b/test/integration/short/vector-tests.js
new file mode 100644
index 00000000..77a3b522
--- /dev/null
+++ b/test/integration/short/vector-tests.js
@@ -0,0 +1,102 @@
+/*
+ * 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 assert = require('assert');
+const helper = require('../../test-helper.js');
+
+const { types } = require('../../../index.js');
+const Vector = require('../../../lib/types/vector.js');
+const util = require('node:util');
+const vdescribe = helper.vdescribe;
+vdescribe('5.0.0', 'Vector tests', function () {
+ this.timeout(120000);
+
+ describe('#execute with vectors', function () {
+ const keyspace = helper.getRandomName('ks');
+ const table = keyspace + '.' + helper.getRandomName('table');
+ let createTableCql = `CREATE TABLE ${table} (id uuid PRIMARY KEY`;
+ helper.dataProviderWithCollections.forEach(data => {
+ // skip set because it's not allowed in C* 5.0
+ if (data.subtypeString === 'set') {
+ return;
+ }
+ createTableCql += `, ${subtypeStringToColumnName(data.subtypeString)} vector<${data.subtypeString}, 3>`;
+ });
+ createTableCql += ');';
+ const createUdtCql = `CREATE TYPE ${keyspace}.my_udt (f1 text);`;
+ const setupInfo = helper.setup(1, {
+ keyspace: keyspace,
+ queries: [createUdtCql, createTableCql]
+ });
+
+ const client = setupInfo.client;
+ if (!client) { throw new Error('client setup failed'); }
+
+ helper.dataProviderWithCollections.forEach(data => {
+ // skip set because it's not allowed in C* 5.0
+ if (data.subtypeString === 'set') {
+ return;
+ }
+ it('should insert, select, and update vector of subtype ' + data.subtypeString, function (done) {
+ const id = types.Uuid.random();
+ const vector = new Vector(data.value, data.subtypeString);
+ const query = `INSERT INTO ${table} (id, ${subtypeStringToColumnName(data.subtypeString)}) VALUES (?, ?)`;
+ client.execute(query, [id, vector], { prepare: true }, function (err) {
+ if (err) { return done(err); }
+ client.execute(`SELECT ${subtypeStringToColumnName(data.subtypeString)} FROM ${table} WHERE id = ?`, [id], { prepare: true }, function (err, result) {
+ if (err) { return done(err); }
+ assert.strictEqual(result.rows.length, 1);
+ assert.strictEqual(util.inspect(result.rows[0][subtypeStringToColumnName(data.subtypeString)]), util.inspect(vector));
+
+ const updatedValues = data.value.slice();
+ updatedValues[0] = updatedValues[1];
+ client.execute(`UPDATE ${table} SET ${subtypeStringToColumnName(data.subtypeString)} = ? WHERE id = ?`, [new Vector(updatedValues, data.subtypeString), id], { prepare: true }, function (err, result) {
+ if (err) { return done(err); }
+ done();
+ });
+ });
+ });
+ });
+
+ it('should insert and select vector of subtype ' + data.subtypeString + ' with prepare to be false', function (done) {
+ if (data.subtypeString === 'my_udt') {
+ this.skip();
+ }
+ const id = types.Uuid.random();
+ const vector = new Vector(data.value, data.subtypeString);
+ const query = `INSERT INTO ${table} (id, ${subtypeStringToColumnName(data.subtypeString)}) VALUES (?, ?)`;
+ client.execute(query, [id, vector], { prepare: false }, function (err) {
+ if (err) { return done(err); }
+ client.execute(`SELECT ${subtypeStringToColumnName(data.subtypeString)} FROM ${table} WHERE id = ?`, [id], { prepare: false }, function (err, result) {
+ if (err) { return done(err); }
+ assert.strictEqual(result.rows.length, 1);
+ assert.strictEqual(util.inspect(result.rows[0][subtypeStringToColumnName(data.subtypeString)]), util.inspect(vector));
+ done();
+ });
+ });
+ });
+ });
+ });
+});
+
+/**
+ *
+ * @param {string} subtypeString
+ * @returns
+ */
+function subtypeStringToColumnName(subtypeString) {
+ return "v" + subtypeString.replace(/<|>|,| /g, '_');
+}
\ No newline at end of file
diff --git a/test/test-helper.js b/test/test-helper.js
index c342d1e3..2a01576b 100644
--- a/test/test-helper.js
+++ b/test/test-helper.js
@@ -22,6 +22,7 @@ const util = require('util');
const path = require('path');
const policies = require('../lib/policies');
const types = require('../lib/types');
+// const { types } = require('../lib/types');
const utils = require('../lib/utils');
const spawn = require('child_process').spawn;
const childProcessExec = require('child_process').exec;
@@ -32,6 +33,7 @@ const defaultOptions = require('../lib/client-options').defaultOptions;
const { Host, HostMap } = require('../lib/host');
const OperationState = require('../lib/operation-state');
const promiseUtils = require('../lib/promise-utils');
+const Vector = types.Vector;
util.inherits(RetryMultipleTimes, policies.retry.RetryPolicy);
@@ -1616,6 +1618,282 @@ helper.ads.getKrb5ConfigPath = function() {
return path.join(this.dir, 'krb5.conf');
};
+/**
+ * @type {Array<{subtypeString : string, typeInfo: import('../lib/encoder').VectorColumnInfo, value: Array}>}
+ */
+const dataProvider = [
+ {
+ subtypeString: 'float',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.float }, 3]
+ },
+ value: [1.1122000217437744, 2.212209939956665, 3.3999900817871094]
+ },
+ {
+ subtypeString: 'double',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.double }, 3]
+ },
+ value: [1.1, 2.2, 3.3]
+ },
+ {
+ subtypeString: 'varchar',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.text }, 3]
+ },
+ value: ['ab', 'b', 'cde']
+ },
+ {
+ subtypeString: 'bigint',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.bigint }, 3]
+ },
+ value: [new types.Long(1), new types.Long(2), new types.Long(3)]
+ },
+ {
+ subtypeString: 'blob',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.blob }, 3]
+ },
+ value: [Buffer.from([1, 2, 3]), Buffer.from([4, 5, 6]), Buffer.from([7, 8, 9])]
+ },
+ {
+ subtypeString: 'boolean',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.boolean }, 3]
+ },
+ value: [true, false, true]
+ },
+ {
+ subtypeString: 'decimal',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.decimal }, 3]
+ },
+ value: [types.BigDecimal.fromString('1.1'), types.BigDecimal.fromString('2.2'), types.BigDecimal.fromString('3.3')]
+ },
+ {
+ subtypeString: 'inet',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.inet }, 3]
+ },
+ value: [types.InetAddress.fromString('127.0.0.1'), types.InetAddress.fromString('0.0.0.0'), types.InetAddress.fromString('34.12.10.19')]
+ },
+ {
+ subtypeString: 'tinyint',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.tinyint }, 3]
+ },
+ value: [1, 2, 3]
+ },
+ {
+ subtypeString: 'smallint',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.smallint }, 3]
+ },
+ value: [1, 2, 3]
+ },
+ {
+ subtypeString: 'int',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.int }, 3]
+ },
+ value: [-1, 0, -3]
+ },
+ {
+ subtypeString: 'duration',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ info: [{
+ code: types.dataTypes.custom,
+ info: 'org.apache.cassandra.db.marshal.DurationType'
+ },3],
+ customTypeName: 'vector',
+ },
+ value: [new types.Duration(1, 2, 3), new types.Duration(4, 5, 6), new types.Duration(7, 8, 9)]
+ },
+ {
+ subtypeString: 'date',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.date }, 3]
+ },
+ value: [new types.LocalDate(2020, 1, 1), new types.LocalDate(2020, 2, 1), new types.LocalDate(2020, 3, 1)]
+ },
+ {
+ subtypeString: 'time',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.time }, 3]
+ },
+ value: [new types.LocalTime(types.Long.fromString('6331999999911')), new types.LocalTime(types.Long.fromString('6331999999911')), new types.LocalTime(types.Long.fromString('6331999999911'))]
+ },
+ {
+ subtypeString: 'timestamp',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.timestamp }, 3]
+ },
+ value: [new Date(2020, 1, 1, 1, 1, 1, 1), new Date(2020, 2, 1, 1, 1, 1, 1), new Date(2020, 3, 1, 1, 1, 1, 1)]
+ },
+ {
+ subtypeString: 'uuid',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.uuid }, 3]
+ },
+ value: [types.Uuid.random(), types.Uuid.random(), types.Uuid.random()]
+ },
+ {
+ subtypeString: 'timeuuid',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ customTypeName: 'vector',
+ info: [{ code: types.dataTypes.timeuuid }, 3]
+ },
+ value: [types.TimeUuid.now(), types.TimeUuid.now(), types.TimeUuid.now()]
+ }
+];
+
+helper.dataProvider = dataProvider;
+
+const dataProviderWithCollections = dataProvider.flatMap(data => [
+ data,
+ // vector, 3>
+ {
+ subtypeString: 'list<' + data.subtypeString + '>',
+ typeInfo: {
+ code: types.dataTypes.custom,
+ info: [{
+ code: types.dataTypes.list,
+ info: {
+ code: data.typeInfo.info[0].code,
+ info: data.typeInfo.info[0]["info"]
+ }
+ },3],
+ customTypeName: 'vector',
+ },
+ value: data.value.map(value => [value, value, value])
+ },
+ // vector