diff --git a/doc/features/datatypes/README.md b/doc/features/datatypes/README.md index da554ac0..fec28e5d 100644 --- a/doc/features/datatypes/README.md +++ b/doc/features/datatypes/README.md @@ -29,7 +29,7 @@ tuple|[Tuple](tuples) uuid|[Uuid](uuids) varchar|String varint|[Integer](numerical) -vector|[Float32Array](collections) +vector|[Vector](collections) ## Encoding data diff --git a/doc/features/datatypes/collections/README.md b/doc/features/datatypes/collections/README.md index 879d7dc2..a7a18134 100644 --- a/doc/features/datatypes/collections/README.md +++ b/doc/features/datatypes/collections/README.md @@ -73,8 +73,7 @@ client.execute('SELECT map_val FROM tbl') ### Vector -As of version 4.7.0 the driver also includes support for the vector type available in Cassandra 5.0. Vectors are represented as instances of -the [Float32Array] class. For example, to create and write to a vector with three dimensions you can do the following: +The driver supports vectors of arbitrary subtypes available in Apache Cassandra 5.0. Vectors are represented as instances of `cassandra.types.Vector` class. For example, to create and write to a vector with three dimensions you can do the following: ```javascript await c.connect() @@ -84,8 +83,10 @@ await c.connect() .then(() => c.execute("create custom index ann_index on test.foo(j) using 'StorageAttachedIndex'")) // Base inserts using simple and prepared statements - .then(() => c.execute(`insert into test.foo (i, j) values (?, ?)`, [cassandra.types.Integer.fromInt(1), new Float32Array([8, 2.3, 58])])) - .then(() => c.execute(`insert into test.foo (i, j) values (?, ?)`, [cassandra.types.Integer.fromInt(5), new Float32Array([23, 18, 3.9])], {prepare: true})); + .then(() => c.execute(`insert into test.foo (i, j) values (?, ?)`, [cassandra.types.Integer.fromInt(1), new cassandra.types.Vector([8, 2.3, 58], 'float')])) + .then(() => c.execute(`insert into test.foo (i, j) values (?, ?)`, [cassandra.types.Integer.fromInt(5), new cassandra.types.Vector([23, 18, 3.9])], {prepare: true})); ``` +In driver versions 4.7.0 - 4.7.2, the vector of floats was represented as a [Float32Array]. This is still supported for backward compatibility, but it is deprecated, and it is recommended to use the `cassandra.types.Vector` class instead. + [Float32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array diff --git a/index.d.ts b/index.d.ts index cf44d76e..90f2b6c3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -349,6 +349,10 @@ export namespace errors { constructor(address: string, maxRequestsPerConnection: number, connectionLength: number); } + class VIntOutOfRangeException extends DriverError { + constructor(long: Long); + } + abstract class DriverError extends Error { info: string; diff --git a/lib/encoder.js b/lib/encoder.js index 74219f77..7a78754d 100644 --- a/lib/encoder.js +++ b/lib/encoder.js @@ -26,6 +26,7 @@ const utils = require('./utils'); const token = require('./token'); const { DateRange } = require('./datastax/search'); const geo = require('./geometry'); +const Vector = require('./types/vector'); const Geometry = geo.Geometry; const LineString = geo.LineString; const Point = geo.Point; @@ -94,6 +95,8 @@ const singleTypeNames = Object.freeze({ 'org.apache.cassandra.db.marshal.IntegerType': dataTypes.varint, 'org.apache.cassandra.db.marshal.CounterColumnType': dataTypes.counter }); + +// eslint-disable-next-line no-unused-vars const singleTypeNamesByDataType = invertObject(singleTypeNames); const singleFqTypeNamesLength = Object.keys(singleTypeNames).reduce(function (previous, current) { return current.length > previous ? current.length : previous; @@ -125,10 +128,26 @@ const zeroLengthTypesSupported = new Set([ dataTypes.blob ]); +/** + * @typedef {(singleTypeNames[keyof singleTypeNames] | types.dataTypes.duration | types.dataTypes.text)} SingleTypeCodes + * @typedef {('point' | 'polygon' | 'duration' | 'lineString' | 'dateRange')} CustomSimpleTypeCodes + * @typedef {(customTypeNames[CustomSimpleTypeCodes]) | CustomSimpleTypeCodes | 'empty'} CustomSimpleTypeNames + * @typedef {{code : SingleTypeCodes, info?: null, options? : {frozen?:boolean, reversed?:boolean} }} SingleColumnInfo + * @typedef {{code : (types.dataTypes.custom), info : CustomSimpleTypeNames, options? : {frozen?:boolean, reversed?:boolean}}} CustomSimpleColumnInfo + * @typedef {{code : (types.dataTypes.map), info : [ColumnInfo, ColumnInfo], options?: {frozen?: Boolean, reversed?: Boolean}}} MapColumnInfo + * @typedef {{code : (types.dataTypes.tuple), info : Array, options?: {frozen?: Boolean, reversed?: Boolean}}} TupleColumnInfo + * @typedef {{code : (types.dataTypes.tuple | types.dataTypes.list)}} TupleListColumnInfoWithoutSubtype TODO: guessDataType can return null on tuple/list info, why? + * @typedef {{code : (types.dataTypes.list | types.dataTypes.set), info : ColumnInfo, options?: {frozen?: Boolean, reversed?: Boolean}}} ListSetColumnInfo + * @typedef {{code : (types.dataTypes.udt), info : {name : string, fields : Array<{name : string, type : ColumnInfo}>}, options? : {frozen?: Boolean, reversed?: Boolean}}} UdtColumnInfo + * @typedef {{code : (types.dataTypes.custom), customTypeName : ('vector'), info : [ColumnInfo, number], options? : {frozen?:boolean, reversed?:boolean}}} VectorColumnInfo + * @typedef {{code : (types.dataTypes.custom), info : string, options? : {frozen?:boolean, reversed?:boolean}}} OtherCustomColumnInfo + * @typedef {SingleColumnInfo | CustomSimpleColumnInfo | MapColumnInfo | TupleColumnInfo | ListSetColumnInfo | VectorColumnInfo | OtherCustomColumnInfo | UdtColumnInfo | TupleListColumnInfoWithoutSubtype} ColumnInfo If this is a simple type, info is null; if this is a collection type with a simple subtype, info is a string, if this is a nested collection type, info is a ColumnInfo object + */ + /** * Serializes and deserializes to and from a CQL type and a Javascript Type. * @param {Number} protocolVersion - * @param {ClientOptions} options + * @param {import('./client.js').ClientOptions} options * @constructor */ function Encoder(protocolVersion, options) { @@ -188,23 +207,37 @@ function defineInstanceMembers() { this.decodeBlob = function (bytes) { return this.handleBuffer(bytes); }; - this.decodeCustom = function (bytes, typeName) { + + /** + * + * @param {Buffer} bytes + * @param {OtherCustomColumnInfo | VectorColumnInfo} columnInfo + * @returns + */ + this.decodeCustom = function (bytes, columnInfo) { // Make sure we actually have something to process in typeName before we go any further - if (!typeName || typeName.length === 0) { + if (!columnInfo) { return this.handleBuffer(bytes); } // Special handling for vector custom types (since they have args) - if (typeName.startsWith(customTypeNames.vector)) { - return this.decodeVector(bytes, this.parseVectorTypeArgs(typeName, customTypeNames.vector, this.parseFqTypeName)); + if ('customTypeName' in columnInfo && columnInfo.customTypeName === 'vector') { + return this.decodeVector(bytes, columnInfo); + } + + if(typeof columnInfo.info === 'string' && columnInfo.info.startsWith(customTypeNames.vector)) { + const vectorColumnInfo = /** @type {VectorColumnInfo} */ (this.parseFqTypeName(columnInfo.info)); + return this.decodeVector(bytes, vectorColumnInfo); } - const handler = customDecoders[typeName]; + + const handler = customDecoders[columnInfo.info]; if (handler) { return handler.call(this, bytes); } return this.handleBuffer(bytes); }; + this.decodeUtf8String = function (bytes) { return bytes.toString('utf8'); }; @@ -283,7 +316,8 @@ function defineInstanceMembers() { /* * Reads a list from bytes */ - this.decodeList = function (bytes, subtype) { + this.decodeList = function (bytes, columnInfo) { + const subtype = columnInfo.info; const totalItems = this.decodeCollectionLength(bytes, 0); let offset = this.collectionLengthSize; const list = new Array(totalItems); @@ -300,8 +334,8 @@ function defineInstanceMembers() { /* * Reads a Set from bytes */ - this.decodeSet = function (bytes, subtype) { - const arr = this.decodeList(bytes, subtype); + this.decodeSet = function (bytes, columnInfo) { + const arr = this.decodeList(bytes, columnInfo); if (this.encodingOptions.set) { const setConstructor = this.encodingOptions.set; return new setConstructor(arr); @@ -311,7 +345,8 @@ function defineInstanceMembers() { /* * Reads a map (key / value) from bytes */ - this.decodeMap = function (bytes, subtypes) { + this.decodeMap = function (bytes, columnInfo) { + const subtypes = columnInfo.info; let map; const totalItems = this.decodeCollectionLength(bytes, 0); let offset = this.collectionLengthSize; @@ -358,10 +393,11 @@ function defineInstanceMembers() { /** * Decodes a user defined type into an object * @param {Buffer} bytes - * @param {{fields: Array}} udtInfo + * @param {UdtColumnInfo} columnInfo * @private */ - this.decodeUdt = function (bytes, udtInfo) { + this.decodeUdt = function (bytes, columnInfo) { + const udtInfo = columnInfo.info; const result = {}; let offset = 0; for (let i = 0; i < udtInfo.fields.length && offset < bytes.length; i++) { @@ -380,7 +416,8 @@ function defineInstanceMembers() { return result; }; - this.decodeTuple = function (bytes, tupleInfo) { + this.decodeTuple = function (bytes, columnInfo) { + const tupleInfo = columnInfo.info; const elements = new Array(tupleInfo.length); let offset = 0; @@ -708,17 +745,29 @@ function defineInstanceMembers() { } return value; }; - this.encodeCustom = function (value, customTypeName) { - // Special handling for vector custom types (since they have args) - if (customTypeName.startsWith(customTypeNames.vector)) { - return this.encodeVector(value, this.parseVectorTypeArgs(customTypeName, customTypeNames.vector, this.parseFqTypeName)); + /** + * + * @param {any} value + * @param {OtherCustomColumnInfo | VectorColumnInfo} columnInfo + * @returns + */ + this.encodeCustom = function (value, columnInfo) { + + if ('customTypeName' in columnInfo && columnInfo.customTypeName === 'vector') { + return this.encodeVector(value, columnInfo); + } + + if(typeof columnInfo.info === 'string' && columnInfo.info.startsWith(customTypeNames.vector)) { + const vectorColumnInfo = /** @type {VectorColumnInfo} */ (this.parseFqTypeName(columnInfo.info)); + return this.encodeVector(value, vectorColumnInfo); } - const handler = customEncoders[customTypeName]; + + const handler = customEncoders[columnInfo.info]; if (handler) { return handler.call(this, value); } - throw new TypeError('No encoding handler found for type ' + customTypeName); + throw new TypeError('No encoding handler found for type ' + columnInfo); }; /** * @param {Boolean} value @@ -753,7 +802,7 @@ function defineInstanceMembers() { return buf; }; /** - * @param {Number|String} value + * @param {Number} value * @private */ this.encodeTinyint = function (value) { @@ -764,7 +813,8 @@ function defineInstanceMembers() { buf.writeInt8(value, 0); return buf; }; - this.encodeList = function (value, subtype) { + this.encodeList = function (value, columnInfo) { + const subtype = columnInfo.info; if (!Array.isArray(value)) { throw new TypeError('Not a valid list value, expected Array obtained ' + util.inspect(value)); } @@ -786,24 +836,25 @@ function defineInstanceMembers() { } return Buffer.concat(parts); }; - this.encodeSet = function (value, subtype) { + this.encodeSet = function (value, columnInfo) { if (this.encodingOptions.set && value instanceof this.encodingOptions.set) { const arr = []; value.forEach(function (x) { arr.push(x); }); - return this.encodeList(arr, subtype); + return this.encodeList(arr, columnInfo); } - return this.encodeList(value, subtype); + return this.encodeList(value, columnInfo); }; /** * Serializes a map into a Buffer * @param value - * @param {Array} [subtypes] + * @param {MapColumnInfo} columnInfo * @returns {Buffer} * @private */ - this.encodeMap = function (value, subtypes) { + this.encodeMap = function (value, columnInfo) { + const subtypes = columnInfo.info; const parts = []; let propCounter = 0; let keySubtype = null; @@ -853,7 +904,14 @@ function defineInstanceMembers() { parts.unshift(this.getLengthBuffer(propCounter)); return Buffer.concat(parts); }; - this.encodeUdt = function (value, udtInfo) { + /** + * + * @param {any} value + * @param {UdtColumnInfo} columnInfo + * @returns + */ + this.encodeUdt = function (value, columnInfo) { + const udtInfo = columnInfo.info; const parts = []; let totalLength = 0; for (let i = 0; i < udtInfo.fields.length; i++) { @@ -877,7 +935,14 @@ function defineInstanceMembers() { } return Buffer.concat(parts, totalLength); }; - this.encodeTuple = function (value, tupleInfo) { + /** + * + * @param {any} value + * @param {TupleColumnInfo} columnInfo + * @returns + */ + this.encodeTuple = function (value, columnInfo) { + const tupleInfo = columnInfo.info; const parts = []; let totalLength = 0; const length = Math.min(tupleInfo.length, value.length); @@ -908,70 +973,110 @@ function defineInstanceMembers() { return Buffer.concat(parts, totalLength); }; + /** + * + * @param {Buffer} buffer + * @param {VectorColumnInfo} params + * @returns {Vector} + */ this.decodeVector = function(buffer, params) { - const subtype = params["subtype"]; - const dimensions = params["dimensions"]; - const elemLength = 4; // TODO: figure this out based on the subtype - const expectedLength = buffer.length / elemLength; - if ((elemLength * dimensions) !== buffer.length) { - throw new TypeError(`Expected buffer of subtype ${subtype} with dimensions ${dimensions} to be of size ${expectedLength}, observed size ${buffer.length}`); - } + const subtype = params.info[0]; + const dimension = params.info[1]; + const elemLength = this.serializationSizeIfFixed(subtype); + const rv = []; let offset = 0; - for (let i = 0; i < dimensions; i++) { - offset = i * elemLength; - rv[i] = this.decode(buffer.slice(offset, offset + elemLength), subtype); + for (let i = 0; i < dimension; i++) { + if (elemLength === -1) { + // var sized + const [size, bytesRead] = utils.VIntCoding.uvintUnpack(buffer.subarray(offset)); + offset += bytesRead; + if (offset + size > buffer.length) { + throw new TypeError('Not enough bytes to decode the vector'); + } + rv[i] = this.decode(buffer.subarray(offset, offset + size), subtype); + offset += size; + }else{ + if (offset + elemLength > buffer.length) { + throw new TypeError('Not enough bytes to decode the vector'); + } + rv[i] = this.decode(buffer.subarray(offset, offset + elemLength), subtype); + offset += elemLength; + } + } + if (offset !== buffer.length) { + throw new TypeError('Extra bytes found after decoding the vector'); } - return new Float32Array(rv); + const typeInfo = (types.getDataTypeNameByCode(subtype)); + return new Vector(rv, typeInfo); }; /** - * @param {CqlVector} value - * @param {Object} params + * @param {ColumnInfo} cqlType + * @returns {Number} */ - this.encodeVector = function(value, params) { - - // Evaluate params to encodeVector(), returning the computed subtype - function evalParams() { - - if (!(value instanceof Float32Array)) { - throw new TypeError("Driver only supports vectors of 4 byte floating point values"); - } - - // Perform client-side validation iff we were actually supplied with meaningful type info. In practice - // this will only occur when using prepared statements. - if (params.hasOwnProperty("subtype") && params.hasOwnProperty("dimensions")) { - - const subtype = params["subtype"]; - const dimensions = params["dimensions"]; - if (value.length !== dimensions) { - throw new TypeError(`Expected vector with ${dimensions} dimensions, observed size of ${value.length}`); - } - if (subtype.code !== dataTypes.float) { - throw new TypeError("Driver only supports vectors of 4 byte floating point values"); + this.serializationSizeIfFixed = function (cqlType) { + switch (cqlType.code) { + case dataTypes.bigint: + return 8; + case dataTypes.boolean: + return 1; + case dataTypes.timestamp: + return 8; + case dataTypes.double: + return 8; + case dataTypes.float: + return 4; + case dataTypes.int: + return 4; + case dataTypes.timeuuid: + return 16; + case dataTypes.uuid: + return 16; + case dataTypes.custom: + if ('customTypeName' in cqlType && cqlType.customTypeName === 'vector'){ + const subtypeSerialSize = this.serializationSizeIfFixed(cqlType.info[0]); + if (subtypeSerialSize === -1){ + return -1; + } + return subtypeSerialSize * cqlType.info[1]; + } - return subtype; - } + return -1; + default: + return -1; + } + }; + /** + * @param {Vector} value + * @param {VectorColumnInfo} params + * @returns {Buffer} + */ + this.encodeVector = function(value, params) { - return { code: dataTypes.float }; + if (!(value instanceof Vector)) { + throw new TypeError("Driver only supports Vector type when encoding a vector"); } - if (!Encoder.isTypedArray(value)) { - throw new TypeError('Expected TypedArray subclass, obtained ' + util.inspect(value)); + const dimension = params.info[1]; + if (value.length !== dimension) { + throw new TypeError(`Expected vector with ${dimension} dimensions, observed size of ${value.length}`); } + if (value.length === 0) { throw new TypeError("Cannot encode empty array as vector"); } - const subtype = evalParams(); - - // TypedArrays are _not_ JS arrays so explicitly convert them here before trying to write them - // into a buffer - const elems = []; + const serializationSize = this.serializationSizeIfFixed(params.info[0]); + const encoded = []; for (const elem of value) { - elems.push(this.encode(elem, subtype)); + const elemBuffer = this.encode(elem, params.info[0]); + if (serializationSize === -1) { + encoded.push(utils.VIntCoding.uvintPack(elemBuffer.length)); + } + encoded.push(elemBuffer); } - return Buffer.concat(elems); + return Buffer.concat(encoded); }; /** @@ -980,7 +1085,7 @@ function defineInstanceMembers() { * @param {String} typeName * @param {String} stringToExclude Leading string indicating this is a vector type (to be excluded when eval'ing args) * @param {Function} subtypeResolveFn Function used to resolve subtype type; varies depending on type naming convention - * @returns {Object} + * @returns {VectorColumnInfo} * @internal */ this.parseVectorTypeArgs = function(typeName, stringToExclude, subtypeResolveFn) { @@ -989,15 +1094,18 @@ function defineInstanceMembers() { const argsLength = typeName.length - (stringToExclude.length + 2); const params = parseParams(typeName, argsStartIndex, argsLength); if (params.length === 2) { - return {subtype: subtypeResolveFn(params[0]), dimensions: parseInt(params[1], 10)}; + /** @type {VectorColumnInfo} */ + const columnInfo = { code: dataTypes.custom, info: [subtypeResolveFn.bind(this)(params[0].trim()), parseInt(params[1].trim(), 10 )], customTypeName : 'vector'}; + return columnInfo; } + throw new TypeError('Not a valid type ' + typeName); }; /** * If not provided, it uses the array of buffers or the parameters and hints to build the routingKey * @param {Array} params - * @param {ExecutionOptions} execOptions + * @param {import('..').ExecutionOptions} execOptions * @param [keys] parameter keys and positions in the params array * @throws TypeError * @internal @@ -1145,8 +1253,10 @@ function defineInstanceMembers() { * @param {Number} startIndex * @param {Number|null} length * @param {Function} udtResolver - * @returns {Promise<{err, info, options}>} callback Callback invoked with err and {{code: number, info: Object|Array|null, options: {frozen: Boolean}}} + * @async + * @returns {Promise.} callback Callback invoked with err and {{code: number, info: Object|Array|null, options: {frozen: Boolean}}} * @internal + * @throws {Error} * @ignore */ this.parseTypeName = async function (keyspace, typeName, startIndex, length, udtResolver) { @@ -1155,20 +1265,16 @@ function defineInstanceMembers() { length = typeName.length; } - const dataType = { - code: 0, - info: null, - options: { - frozen: false - } - }; - let innerTypes; + let frozen = false; if (typeName.indexOf("'", startIndex) === startIndex) { //If quoted, this is a custom type. - dataType.info = typeName.substr(startIndex+1, length-2); - return dataType; + const info = typeName.substr(startIndex+1, length-2); + return { + code: dataTypes.custom, + info: info, + }; } if (!length) { @@ -1179,7 +1285,7 @@ function defineInstanceMembers() { //Remove the frozen token startIndex += cqlNames.frozen.length + 1; length -= cqlNames.frozen.length + 2; - dataType.options.frozen = true; + frozen = true; } if (typeName.indexOf(cqlNames.list, startIndex) === startIndex) { @@ -1192,9 +1298,14 @@ function defineInstanceMembers() { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.list; - dataType.info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver); - return dataType; + const info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver); + return { + code: dataTypes.list, + info: info, + options: { + frozen: frozen + } + }; } if (typeName.indexOf(cqlNames.set, startIndex) === startIndex) { @@ -1207,9 +1318,14 @@ function defineInstanceMembers() { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.set; - dataType.info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver); - return dataType; + const info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver); + return { + code: dataTypes.set, + info: info, + options: { + frozen: frozen + } + }; } if (typeName.indexOf(cqlNames.map, startIndex) === startIndex) { @@ -1223,9 +1339,14 @@ function defineInstanceMembers() { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.map; - dataType.info = await this._parseChildTypes(keyspace, innerTypes, udtResolver); - return dataType; + const info = await this._parseChildTypes(keyspace, innerTypes, udtResolver); + return { + code: dataTypes.map, + info: info, + options: { + frozen: frozen + } + }; } if (typeName.indexOf(cqlNames.tuple, startIndex) === startIndex) { @@ -1238,22 +1359,23 @@ function defineInstanceMembers() { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.tuple; - dataType.info = await this._parseChildTypes(keyspace, innerTypes, udtResolver); - return dataType; + const info = await this._parseChildTypes(keyspace, innerTypes, udtResolver); + return { + code: dataTypes.tuple, + info: info, + options: {frozen: frozen} + }; } if (typeName.indexOf(cqlNames.vector, startIndex) === startIndex) { // It's a vector, so record the subtype and dimension. - dataType.code = dataTypes.custom; // parseVectorTypeArgs is not an async function but we are. To keep things simple let's ask the // function to just return whatever it finds for an arg and we'll eval it after the fact - const params = this.parseVectorTypeArgs(typeName, cqlNames.vector, (arg) => arg ); - params["subtype"] = await this.parseTypeName(keyspace, params["subtype"]); - dataType.info = params; + const params = this.parseVectorTypeArgs.bind(this)(typeName, cqlNames.vector, (arg) => arg ); + params["info"][0] = await this.parseTypeName(keyspace, params["info"][0]); - return dataType; + return params; } const quoted = typeName.indexOf('"', startIndex) === startIndex; @@ -1275,26 +1397,27 @@ function defineInstanceMembers() { const typeCode = dataTypes[typeName]; if (typeof typeCode === 'number') { - dataType.code = typeCode; - return dataType; + return {code : typeCode, info: null}; } if (typeName === cqlNames.duration) { - dataType.info = customTypeNames.duration; - return dataType; + return {code : dataTypes.custom, info : customTypeNames.duration}; } if (typeName === cqlNames.empty) { // Set as custom - dataType.info = 'empty'; - return dataType; + return {code : dataTypes.custom, info : 'empty'}; } const udtInfo = await udtResolver(keyspace, typeName); if (udtInfo) { - dataType.code = dataTypes.udt; - dataType.info = udtInfo; - return dataType; + return { + code: dataTypes.udt, + info: udtInfo, + options: { + frozen: frozen + } + }; } throw new TypeError('Not a valid type "' + typeName + '"'); @@ -1316,20 +1439,14 @@ function defineInstanceMembers() { * @param {String} typeName * @param {Number} [startIndex] * @param {Number} [length] - * @throws TypeError - * @returns {{code: number, info: Object|Array|null, options: {frozen: Boolean, reversed: Boolean}}} + * @throws {TypeError} + * @returns {ColumnInfo} * @internal * @ignore */ this.parseFqTypeName = function (typeName, startIndex, length) { - const dataType = { - code: 0, - info: null, - options: { - reversed: false, - frozen: false - } - }; + let frozen = false; + let reversed = false; startIndex = startIndex || 0; let params; if (!length) { @@ -1339,19 +1456,26 @@ function defineInstanceMembers() { //Remove the reversed token startIndex += complexTypeNames.reversed.length + 1; length -= complexTypeNames.reversed.length + 2; - dataType.options.reversed = true; + reversed = true; } if (length > complexTypeNames.frozen.length && typeName.indexOf(complexTypeNames.frozen, startIndex) === startIndex) { //Remove the frozen token startIndex += complexTypeNames.frozen.length + 1; length -= complexTypeNames.frozen.length + 2; - dataType.options.frozen = true; + frozen = true; } + const options = { + frozen: frozen, + reversed: reversed + }; if (typeName === complexTypeNames.empty) { //set as custom - dataType.info = 'empty'; - return dataType; + return { + code: dataTypes.custom, + info: 'empty', + options: options + }; } //Quick check if its a single type if (length <= singleFqTypeNamesLength) { @@ -1360,8 +1484,11 @@ function defineInstanceMembers() { } const typeCode = singleTypeNames[typeName]; if (typeof typeCode === 'number') { - dataType.code = typeCode; - return dataType; + return {code : typeCode, info: null, options : options}; + } + // special handling for duration + if (typeName === customTypeNames.duration) { + return {code: dataTypes.duration, options: options}; } throw new TypeError('Not a valid type "' + typeName + '"'); } @@ -1375,9 +1502,12 @@ function defineInstanceMembers() { if (params.length !== 1) { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.list; - dataType.info = this.parseFqTypeName(params[0]); - return dataType; + const info = this.parseFqTypeName(params[0]); + return { + code: dataTypes.list, + info: info, + options: options + }; } if (typeName.indexOf(complexTypeNames.set, startIndex) === startIndex) { //Its a set @@ -1390,9 +1520,12 @@ function defineInstanceMembers() { { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.set; - dataType.info = this.parseFqTypeName(params[0]); - return dataType; + const info = this.parseFqTypeName(params[0]); + return { + code : dataTypes.set, + info : info, + options: options + }; } if (typeName.indexOf(complexTypeNames.map, startIndex) === startIndex) { //org.apache.cassandra.db.marshal.MapType(keyType,valueType) @@ -1404,15 +1537,21 @@ function defineInstanceMembers() { if (params.length !== 2) { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.map; - dataType.info = [this.parseFqTypeName(params[0]), this.parseFqTypeName(params[1])]; - return dataType; + const info1 = this.parseFqTypeName(params[0]); + const info2 = this.parseFqTypeName(params[1]); + return { + code : dataTypes.map, + info : [info1, info2], + options: options + }; } if (typeName.indexOf(complexTypeNames.udt, startIndex) === startIndex) { //move cursor across the name and bypass the parenthesis startIndex += complexTypeNames.udt.length + 1; length -= complexTypeNames.udt.length + 2; - return this._parseUdtName(typeName, startIndex, length); + const udtType = this._parseUdtName(typeName, startIndex, length); + udtType.options = options; + return udtType; } if (typeName.indexOf(complexTypeNames.tuple, startIndex) === startIndex) { //move cursor across the name and bypass the parenthesis @@ -1422,21 +1561,28 @@ function defineInstanceMembers() { if (params.length < 1) { throw new TypeError('Not a valid type ' + typeName); } - dataType.code = dataTypes.tuple; - dataType.info = params.map(x => this.parseFqTypeName(x)); - return dataType; + const info = params.map(x => this.parseFqTypeName(x)); + return { + code : dataTypes.tuple, + info : info, + options: options + }; } if (typeName.indexOf(customTypeNames.vector, startIndex) === startIndex) { // It's a vector, so record the subtype and dimension. - dataType.code = dataTypes.custom; - dataType.info = this.parseVectorTypeArgs(typeName, customTypeNames.vector, this.parseFqTypeName); - return dataType; + const params = this.parseVectorTypeArgs.bind(this)(typeName, customTypeNames.vector, this.parseFqTypeName); + params.options = options; + return params; } // Assume custom type if cannot be parsed up to this point. - dataType.info = typeName.substr(startIndex, length); - return dataType; + const info = typeName.substr(startIndex, length); + return { + code: dataTypes.custom, + info: info, + options: options + }; }; /** * Parses type names with composites @@ -1502,16 +1648,22 @@ function defineInstanceMembers() { isComposite: isComposite }; }; + /** + * + * @param {string} typeName + * @param {number} startIndex + * @param {number} length + * @returns {UdtColumnInfo} + */ this._parseUdtName = function (typeName, startIndex, length) { const udtParams = parseParams(typeName, startIndex, length); if (udtParams.length < 2) { //It should contain at least the keyspace, name of the udt and a type throw new TypeError('Not a valid type ' + typeName); } - const dataType = { - code: dataTypes.udt, - info: null - }; + /** + * @type {{keyspace: String, name: String, fields: Array}} + */ const udtInfo = { keyspace: udtParams[0], name: utils.allocBufferFromString(udtParams[1], 'hex').toString(), @@ -1526,8 +1678,10 @@ function defineInstanceMembers() { type: fieldType }); } - dataType.info = udtInfo; - return dataType; + return { + code : dataTypes.udt, + info : udtInfo + }; }; } @@ -1603,9 +1757,7 @@ function setEncoders() { * This is part of an experimental API, this can be changed future releases. *

* @param {Buffer} buffer Raw buffer to be decoded. - * @param {Object} type An object containing the data type code and info. - * @param {Number} type.code Type code. - * @param {Object} [type.info] Additional information on the type for complex / nested types. + * @param {ColumnInfo} type */ Encoder.prototype.decode = function (buffer, type) { if (buffer === null || (buffer.length === 0 && !zeroLengthTypesSupported.has(type.code))) { @@ -1618,7 +1770,7 @@ Encoder.prototype.decode = function (buffer, type) { throw new Error('Unknown data type: ' + type.code); } - return decoder.call(this, buffer, type.info); + return decoder.call(this, buffer, type); }; /** @@ -1627,7 +1779,7 @@ Encoder.prototype.decode = function (buffer, type) { * This is part of an experimental API, this can be changed future releases. *

* @param {*} value The value to be converted. - * @param {{code: number, info: *|Object}|String|Number} [typeInfo] The type information. + * @param {ColumnInfo | Number | String} typeInfo The type information. *

It can be either a:

*