blob: a77860327f7242ff5e894c65dd2ebee3956484c7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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('chai');
const sinon = require('sinon');
const util = require('util');
const utils = require('../../lib/utils');
const tokenizer = require('../../lib/tokenizer');
const token = require('../../lib/token');
const Vector = require('../../lib/types/vector');
const Encoder = require('../../lib/encoder');
const { types } = require('../../index.js');
const ExecutionOptions = require('../../lib/execution-options').ExecutionOptions;
const dataTypes = types.dataTypes;
const helper = require('../test-helper');
const zeroLengthTypesSupported = new Set([
dataTypes.text,
dataTypes.ascii,
dataTypes.varchar,
dataTypes.custom,
dataTypes.blob
]);
describe('encoder', function () {
describe('Encoder.guessDataType()', function () {
it('should guess the native types', function () {
assertGuessed(1, dataTypes.double, 'Guess type for an integer (double) number failed');
assertGuessed(1.01, dataTypes.double, 'Guess type for a double number failed');
assertGuessed(true, dataTypes.boolean, 'Guess type for a boolean value failed');
assertGuessed([1,2,3], dataTypes.list, 'Guess type for an Array value failed');
assertGuessed('a string', dataTypes.text, 'Guess type for an string value failed');
assertGuessed(utils.allocBufferFromString('bip bop'), dataTypes.blob, 'Guess type for a buffer value failed');
assertGuessed(new Date(), dataTypes.timestamp, 'Guess type for a Date value failed');
assertGuessed(new types.Long(10), dataTypes.bigint, 'Guess type for a Int 64 value failed');
assertGuessed(types.Uuid.random(), dataTypes.uuid, 'Guess type for a UUID value failed');
assertGuessed(types.TimeUuid.now(), dataTypes.uuid, 'Guess type for a TimeUuid value failed');
assertGuessed(types.TimeUuid.now().toString(), dataTypes.uuid, 'Guess type for a string uuid value failed');
assertGuessed(types.timeuuid(), dataTypes.uuid, 'Guess type for a Timeuuid value failed');
assertGuessed(types.Integer.fromNumber(1), dataTypes.varint, 'Guess type for a varint value failed');
assertGuessed(types.BigDecimal.fromString('1.01'), dataTypes.decimal, 'Guess type for a varint value failed');
assertGuessed(types.Integer.fromBuffer(utils.allocBufferFromArray([0xff])), dataTypes.varint, 'Guess type for a varint value failed');
assertGuessed(new types.InetAddress(utils.allocBufferFromArray([10, 10, 10, 2])), dataTypes.inet, 'Guess type for a inet value failed');
assertGuessed(new types.Tuple(1, 2, 3), dataTypes.tuple, 'Guess type for a tuple value failed');
assertGuessed(new types.LocalDate(2010, 4, 29), dataTypes.date, 'Guess type for a date value failed');
assertGuessed(new types.LocalTime(types.Long.fromString('6331999999911')), dataTypes.time, 'Guess type for a time value failed');
assertGuessed(new Float32Array([1.2, 3.4, 5.6]), dataTypes.custom, 'Guess type for a Float32 TypedArray value failed');
assertGuessed({}, null, 'Objects must not be guessed');
});
function assertGuessed(value, expectedType, message) {
const type = Encoder.guessDataType(value);
if (type === null) {
if (expectedType !== null) {
assert.ok(false, 'Type not guessed for value ' + value);
}
return;
}
assert.strictEqual(type.code, expectedType, message + ': ' + value);
}
});
describe('Encoder.isTypedArray()', function () {
it('should return true for TypedArray subclasses', function () {
assert.ok(Encoder.isTypedArray(new Float32Array([])));
assert.ok(Encoder.isTypedArray(new Float32Array([1.2, 3.4, 5.6])));
assert.ok(Encoder.isTypedArray(new Float64Array([])));
assert.ok(Encoder.isTypedArray(new Float64Array([1.2, 3.4, 5.6])));
assert.ok(Encoder.isTypedArray(new Int8Array([])));
assert.ok(Encoder.isTypedArray(new Int8Array([1, 2, 3])));
assert.ok(Encoder.isTypedArray(new Uint8Array([])));
assert.ok(Encoder.isTypedArray(new Uint8Array([1, 2, 3])));
});
it('should return false for other types', function () {
assert.notOk(Encoder.isTypedArray(100));
assert.notOk(Encoder.isTypedArray([]));
assert.notOk(Encoder.isTypedArray([1,2,3]));
assert.notOk(Encoder.isTypedArray([1.2, 3.4, 5.6]));
});
});
describe('#encode() and #decode()', function () {
const typeEncoder = new Encoder(2, {});
it('should encode and decode a guessed double', function () {
const value = 1111.1;
const encoded = typeEncoder.encode(value);
const decoded = typeEncoder.decode(encoded, {code: dataTypes.double});
assert.strictEqual(decoded, value);
});
it('should encode and decode a guessed string', function () {
const value = 'Pennsatucky';
const encoded = typeEncoder.encode(value);
const decoded = typeEncoder.decode(encoded, {code: dataTypes.text});
assert.strictEqual(decoded, value);
});
it('should encode stringified uuids for backward-compatibility', function () {
let uuid = types.Uuid.random();
let encoded = typeEncoder.encode(uuid.toString(), types.dataTypes.uuid);
assert.strictEqual(encoded.toString('hex'), uuid.getBuffer().toString('hex'));
uuid = types.TimeUuid.now();
encoded = typeEncoder.encode(uuid.toString(), types.dataTypes.uuid);
assert.strictEqual(encoded.toString('hex'), uuid.getBuffer().toString('hex'));
});
it('should throw when string is not an uuid', function () {
assert.throws(function () {
typeEncoder.encode('', types.dataTypes.uuid);
}, TypeError);
});
it('should encode undefined as null', function () {
const hinted = typeEncoder.encode(undefined, 'set<text>');
const unHinted = typeEncoder.encode();
assert.strictEqual(hinted, null);
assert.strictEqual(unHinted, null);
});
it('should throw on unknown types', function () {
assert.throws(function () {
typeEncoder.encode({});
}, TypeError);
});
it('should throw when the typeInfo and the value source type does not match', function () {
assert.throws(function () {
typeEncoder.encode('hello', 'int');
}, TypeError);
assert.throws(function () {
typeEncoder.encode(100, dataTypes.uuid);
}, TypeError);
assert.throws(function () {
typeEncoder.encode(200, dataTypes.timeuuid);
}, TypeError);
assert.throws(function () {
typeEncoder.encode('Its anybody in there? I know that you can hear me', dataTypes.blob);
}, TypeError);
assert.throws(function () {
typeEncoder.encode(100, dataTypes.blob);
}, TypeError);
assert.throws(function () {
typeEncoder.encode({}, dataTypes.list);
}, TypeError);
});
it('should encode Long/Date/Number/String as timestamps', function () {
const encoder = new Encoder(2, {});
let buffer = encoder.encode(types.Long.fromBits(0x00fafafa, 0x07090909), dataTypes.timestamp);
assert.strictEqual(buffer.toString('hex'), '0709090900fafafa');
buffer = encoder.encode(1421755130012, dataTypes.timestamp);
assert.strictEqual(buffer.toString('hex'), '0000014b0735a09c');
buffer = encoder.encode(new Date(1421755130012), dataTypes.timestamp);
assert.throws(function () {
encoder.encode(new Date('This is an invalid date string'), dataTypes.timestamp);
}, TypeError);
assert.strictEqual(buffer.toString('hex'), '0000014b0735a09c');
buffer = encoder.encode(new Date(1421755130012), dataTypes.timestamp);
assert.strictEqual(buffer.toString('hex'), '0000014b0735a09c');
buffer = encoder.encode('Tue Jan 20 2015 13:00:35 GMT+0100 (CET)', dataTypes.timestamp);
assert.strictEqual(buffer.toString('hex'), '0000014b07373ab8');
assert.throws(function () {
encoder.encode('This is an invalid date string', dataTypes.timestamp);
}, TypeError);
});
it('should encode String/Number (not NaN) as int', function () {
const encoder = new Encoder(2, {});
let buffer = encoder.encode(0x071272ab, dataTypes.int);
assert.strictEqual(buffer.toString('hex'), '071272ab');
buffer = encoder.encode('0x071272ab', dataTypes.int);
assert.strictEqual(buffer.toString('hex'), '071272ab');
buffer = encoder.encode(-1, 'int');
assert.strictEqual(buffer.toString('hex'), 'ffffffff');
buffer = encoder.encode('-1', 'int');
assert.strictEqual(buffer.toString('hex'), 'ffffffff');
buffer = encoder.encode(0, 'int');
assert.strictEqual(buffer.toString('hex'), '00000000');
buffer = encoder.encode('0', 'int');
assert.strictEqual(buffer.toString('hex'), '00000000');
assert.throws(function () {
encoder.encode(NaN, 'int');
}, TypeError);
});
it('should encode String/Long/Number as bigint', function () {
const encoder = new Encoder(2, {});
let buffer = encoder.encode(types.Long.fromString('506946367331695353'), dataTypes.bigint);
assert.strictEqual(buffer.toString('hex'), '0709090900fafaf9');
buffer = encoder.encode('506946367331695353', dataTypes.bigint);
assert.strictEqual(buffer.toString('hex'), '0709090900fafaf9');
buffer = encoder.encode(0, dataTypes.bigint);
assert.strictEqual(buffer.toString('hex'), '0000000000000000');
buffer = encoder.encode(255, dataTypes.bigint);
assert.strictEqual(buffer.toString('hex'), '00000000000000ff');
});
it('should encode String/Integer/Number as varint', function () {
const encoder = new Encoder(2, {});
let buffer = encoder.encode(types.Integer.fromString('33554433'), dataTypes.varint);
assert.strictEqual(buffer.toString('hex'), '02000001');
buffer = encoder.encode('-150012', dataTypes.varint);
assert.strictEqual(buffer.toString('hex'), 'fdb604');
buffer = encoder.encode('-1000000000012', dataTypes.varint);
assert.strictEqual(buffer.toString('hex'), 'ff172b5aeff4');
buffer = encoder.encode(-128, dataTypes.varint);
assert.strictEqual(buffer.toString('hex'), '80');
buffer = encoder.encode(-100, dataTypes.varint);
assert.strictEqual(buffer.toString('hex'), '9c');
});
it('should encode String/BigDecimal/Number as decimal', function () {
const encoder = new Encoder(2, {});
let buffer = encoder.encode(types.BigDecimal.fromString('0.00256'), dataTypes.decimal);
assert.strictEqual(buffer.toString('hex'), '000000050100');
buffer = encoder.encode('-0.01', dataTypes.decimal);
assert.strictEqual(buffer.toString('hex'), '00000002ff');
buffer = encoder.encode('-25.5', dataTypes.decimal);
assert.strictEqual(buffer.toString('hex'), '00000001ff01');
buffer = encoder.encode(0.004, dataTypes.decimal);
assert.strictEqual(buffer.toString('hex'), '0000000304');
buffer = encoder.encode(-25.5, dataTypes.decimal);
assert.strictEqual(buffer.toString('hex'), '00000001ff01');
});
it('should encode/decode InetAddress/Buffer as inet', function () {
const InetAddress = types.InetAddress;
const encoder = new Encoder(2, {});
let val1 = new InetAddress(utils.allocBufferFromArray([15, 15, 15, 1]));
let encoded = encoder.encode(val1, dataTypes.inet);
assert.strictEqual(encoded.toString('hex'), '0f0f0f01');
let val2 = encoder.decode(encoded, {code: dataTypes.inet});
assert.strictEqual(val2.toString(), '15.15.15.1');
assert.ok(val1.equals(val2));
val1 = new InetAddress(utils.allocBufferFromString('00000000000100112233445500aa00bb', 'hex'));
encoded = encoder.encode(val1, dataTypes.inet);
val2 = encoder.decode(encoded, {code: dataTypes.inet});
assert.ok(val1.equals(val2));
//Buffers are valid InetAddress
encoded = encoder.encode(val1.getBuffer(), dataTypes.inet);
assert.strictEqual(encoded.toString('hex'), val1.getBuffer().toString('hex'));
});
it('should decode uuids into Uuid', function () {
const uuid = types.Uuid.random();
const decoded = typeEncoder.decode(uuid.getBuffer(), {code: dataTypes.uuid});
helper.assertInstanceOf(decoded, types.Uuid);
assert.strictEqual(uuid.toString(), decoded.toString());
assert.ok(uuid.equals(decoded));
const decoded2 = typeEncoder.decode(types.Uuid.random().getBuffer(), {code: dataTypes.uuid});
assert.ok(!decoded.equals(decoded2));
});
it('should decode timeuuids into TimeUuid', function () {
const uuid = types.TimeUuid.now();
const decoded = typeEncoder.decode(uuid.getBuffer(), {code: dataTypes.timeuuid});
helper.assertInstanceOf(decoded, types.TimeUuid);
assert.strictEqual(uuid.toString(), decoded.toString());
assert.ok(uuid.equals(decoded));
const decoded2 = typeEncoder.decode(types.TimeUuid.now().getBuffer(), {code: dataTypes.timeuuid});
assert.ok(!decoded.equals(decoded2));
});
[2, 3].forEach(function (version) {
const encoder = new Encoder(version, {});
it(util.format('should encode and decode maps for protocol v%d', version), function () {
let value = {value1: 'Surprise', value2: 'Madafaka'};
//Minimum info, guessed
let encoded = encoder.encode(value, dataTypes.map);
let decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//Minimum info, guessed
value = {value1: 1.1, valueN: 1.2};
encoded = encoder.encode(value, dataTypes.map);
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.double}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//Minimum info string, guessed
value = {value1: new Date(9999999), valueN: new Date(5555555)};
encoded = encoder.encode(value, 'map');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.timestamp}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//Minimum info string, guessed
value = {};
value[types.uuid()] = 0;
value[types.uuid()] = 2;
encoded = encoder.encode(value, 'map');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.uuid}, {code: dataTypes.double}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//full info string
value = {value1: 1, valueN: -3};
encoded = encoder.encode(value, 'map<text,int>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.int}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//full info typeInfo
value = {value1: 1, valueN: -33892};
encoded = encoder.encode(value, {code: dataTypes.map, info: [dataTypes.string, dataTypes.int]});
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.int}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode maps with stringified keys for protocol v%d', version), function () {
let value = {};
value[new Date(1421756675488)] = 'date1';
value[new Date(1411756633461)] = 'date2';
let encoded = encoder.encode(value, {code: dataTypes.map, info: [{code: dataTypes.timestamp}, {code: dataTypes.text}]});
let decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.timestamp}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value[101] = 'number1';
value[102] = 'number2';
encoded = encoder.encode(value, 'map<int, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.int}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value[types.Long.fromBits(0x12002001, 0x7f999299)] = 'bigint1';
value[types.Long.fromBits(0x12002000, 0x7f999299)] = 'bigint2';
encoded = encoder.encode(value, 'map<bigint, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.bigint}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['201'] = 'bigint1_1';
value['202'] = 'bigint2_1';
encoded = encoder.encode(value, 'map<bigint, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.bigint}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['2d5db74c-c2da-4e59-b5ec-d8ad3d0aefb9'] = 'uuid1';
value['651b5c17-5357-4764-ae2d-21c409288822'] = 'uuid2';
encoded = encoder.encode(value, 'map<uuid, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.uuid}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['1ab50440-a0ab-11e4-9d01-1dc0e727b460'] = 'timeuuid1';
value['1820c4d0-a0ab-11e4-9d01-1dc0e727b460'] = 'timeuuid2';
encoded = encoder.encode(value, 'map<timeuuid, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.timeuuid}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['988229782938247303441911118'] = 'varint1';
value['988229782938247303441911119'] = 'varint2';
encoded = encoder.encode(value, 'map<varint, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.varint}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['12.1'] = 'decimal1';
value['12.90'] = 'decimal2';
encoded = encoder.encode(value, 'map<decimal, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.decimal}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['127.0.0.1'] = 'inet1';
value['12.10.10.2'] = 'inet2';
encoded = encoder.encode(value, 'map<inet, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.inet}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['::1'] = 'inet1';
value['::2233:0:0:b1'] = 'inet2';
value['aabb::11:2233:4455:6677:88ff'] = 'inet3';
encoded = encoder.encode(value, 'map<inet, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.inet}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['12:59:56'] = 'time1';
value['15:01:02.1234'] = 'time2';
value['06:01:02.000000213'] = 'time3';
encoded = encoder.encode(value, 'map<time, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.time}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
value = {};
value['2015-01-30'] = 'date1';
value['1999-11-12'] = 'date2';
value['-0001-11-12'] = 'date3';
encoded = encoder.encode(value, 'map<date, text>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.date}, {code: dataTypes.text}]});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode list<int> for protocol v%d', version), function () {
const value = [1, 2, 3, 4];
const encoded = encoder.encode(value, 'list<int>');
const decoded = encoder.decode(encoded, {code: dataTypes.list, info: {code: dataTypes.int}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode list<double> for protocol v%d', version), function () {
const value = [1, 2, 3, 100];
const encoded = encoder.encode(value, 'list<double>');
const decoded = encoder.decode(encoded, {code: dataTypes.list, info: {code: dataTypes.double}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode list<double> without hint for protocol v%d', version), function () {
const value = [1, 2, 3, 100.1];
const encoded = encoder.encode(value);
const decoded = encoder.decode(encoded, {code: dataTypes.list, info: {code: dataTypes.double}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode set<text> for protocol v%d', version), function () {
const value = ['Alex Vause', 'Piper Chapman', '3', '4'];
let encoded = encoder.encode(value, 'set<text>');
let decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.text}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
//with type info
encoded = encoder.encode(value, {code: dataTypes.set, info: {code: dataTypes.text}});
decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.text}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode and decode list<float> with typeInfo for protocol v%d', version), function () {
const value = [1.1122000217437744, 2.212209939956665, 3.3999900817871094, 4.412120819091797, -1000, 1];
const encoded = encoder.encode(value, {code: dataTypes.list, info: {code: dataTypes.float}});
const decoded = encoder.decode(encoded, {code: dataTypes.list, info: {code: dataTypes.float}});
assert.strictEqual(util.inspect(decoded), util.inspect(value));
});
it(util.format('should encode/decode ES6 Set as maps for protocol v%d', version), function () {
if (typeof Set !== 'function') {
//Set not supported in Node.js runtime
return;
}
// eslint-disable-next-line no-undef
const Es6Set = Set;
const encoder = new Encoder(version, { encoding: { set: Es6Set}});
let m = new Es6Set(['k1', 'k2', 'k3']);
let encoded = encoder.encode(m, 'set<text>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000300026b3100026b3200026b33');
}
let decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.text}});
helper.assertInstanceOf(decoded, Es6Set);
assert.strictEqual(decoded.toString(), m.toString());
m = new Es6Set([1, 2, 1000]);
encoded = encoder.encode(m, 'set<int>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '00030004000000010004000000020004000003e8');
}
decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.text}});
assert.strictEqual(decoded.toString(), m.toString());
});
it(util.format('should encode/decode Map polyfills as maps for protocol v%d', version), function () {
const encoder = new Encoder(version, { encoding: { map: helper.Map}});
let m = new helper.Map();
m.set('k1', 'v1');
m.set('k2', 'v2');
let encoded = encoder.encode(m, 'map<text,text>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000200026b310002763100026b3200027632');
}
let decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.text}]});
helper.assertInstanceOf(decoded, helper.Map);
assert.strictEqual(decoded.arr.toString(), m.arr.toString());
m = new helper.Map();
m.set('k1', 1);
m.set('k2', 2);
m.set('k3', 3);
encoded = encoder.encode(m, 'map<text,int>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000300026b3100040000000100026b3200040000000200026b33000400000003');
}
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.int}]});
assert.strictEqual(decoded.arr.toString(), m.arr.toString());
});
it(util.format('should encode/decode ES6 Map as maps for protocol v%d', version), function () {
function getValues(m) {
const arr = [];
m.forEach(function (val, key) {
arr.push([key, val]);
});
return arr.toString();
}
// eslint-disable-next-line no-undef
const Es6Map = Map;
const encoder = new Encoder(version, { encoding: { map: Es6Map}});
let m = new Es6Map();
m.set('k1', 'v1');
m.set('k2', 'v2');
let encoded = encoder.encode(m, 'map<text,text>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000200026b310002763100026b3200027632');
}
let decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.text}]});
helper.assertInstanceOf(decoded, Es6Map);
assert.strictEqual(getValues(decoded), getValues(m));
m = new Es6Map();
m.set('k1', 1);
m.set('k2', 2);
m.set('k3', 3);
encoded = encoder.encode(m, 'map<text,int>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000300026b3100040000000100026b3200040000000200026b33000400000003');
}
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.int}]});
assert.strictEqual(getValues(decoded), getValues(m));
m = new Es6Map();
m.set(new Date('2005-08-05'), 10);
m.set(new Date('2010-04-29'), 2);
encoded = encoder.encode(m, 'map<timestamp,int>');
decoded = encoder.decode(encoded, {code: dataTypes.map, info: [{code: dataTypes.timestamp}, {code: dataTypes.int}]});
assert.strictEqual(getValues(decoded), getValues(m));
});
it(util.format('should encode/decode Set polyfills as maps for protocol v%d', version), function () {
const encoder = new Encoder(version, { encoding: { set: helper.Set}});
let m = new helper.Set(['k1', 'k2', 'k3']);
let encoded = encoder.encode(m, 'set<text>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '000300026b3100026b3200026b33');
}
let decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.text}});
helper.assertInstanceOf(decoded, helper.Set);
assert.strictEqual(decoded.toString(), m.toString());
m = new helper.Set([1, 2, 1000]);
encoded = encoder.encode(m, 'set<int>');
if (version === 2) {
assert.strictEqual(encoded.toString('hex'), '00030004000000010004000000020004000003e8');
}
decoded = encoder.decode(encoded, {code: dataTypes.set, info: {code: dataTypes.int}});
assert.strictEqual(decoded.toString(), m.toString());
});
});
it('should encode/decode udts', function () {
const encoder = new Encoder(3, {});
const type = { code: dataTypes.udt, info: { fields:[
{name: 'alias', type:{code:dataTypes.text}},
{name: 'number', type:{code:dataTypes.text}}] }};
const encoded = encoder.encode({ alias: 'zeta'}, type);
const decoded = encoder.decode(encoded, type);
assert.strictEqual(decoded['alias'], 'zeta');
assert.strictEqual(decoded['number'], null);
});
it('should encode/decode nested collections', function () {
const encoder = new Encoder(3, {});
const type = { code: dataTypes.map, info: [{code: dataTypes.text}, {code: dataTypes.set, info: {code: dataTypes.text}}]};
const encoded = encoder.encode({ key1: ['first', 'second', 'third'], key2: ['2-first']}, type);
const decoded = encoder.decode(encoded, type);
assert.ok(decoded.key1);
assert.strictEqual(decoded.key1.length, 3);
assert.strictEqual(decoded.key1[0], 'first');
assert.strictEqual(decoded.key1[1], 'second');
assert.strictEqual(decoded.key1[2], 'third');
assert.ok(decoded.key2);
assert.strictEqual(decoded.key2.length, 1);
assert.strictEqual(decoded.key2[0], '2-first');
});
it('should encode/decode tuples', function () {
const encoder = new Encoder(3, {});
const type = { code: dataTypes.tuple, info: [ { code: dataTypes.text}, { code: dataTypes.timestamp }]};
const encoded = encoder.encode(new types.Tuple('one', new Date(1429259123607)), type);
const decoded = encoder.decode(encoded, type);
assert.strictEqual(decoded.length, 2);
assert.strictEqual(decoded.get(0), 'one');
assert.strictEqual(decoded.get(1).getTime(), 1429259123607);
});
it('should encode/decode LocalDate as date', function () {
const encoder = new Encoder(4, {});
const type = {code: dataTypes.date};
const year1day1 = new Date(Date.UTC(1970, 0, 1));
year1day1.setUTCFullYear(1);
const year0day1 = new Date(Date.UTC(1970, 0, 1));
year0day1.setUTCFullYear(0);
const dates = [
// At epoch.
{ldate: new types.LocalDate(1970, 1, 1), string: '1970-01-01', date: new Date(Date.UTC(1970, 0, 1))},
// 10 days after epoch.
{ldate: new types.LocalDate(1970, 1, 11), string: '1970-01-11', date: new Date(Date.UTC(1970, 0, 11))},
// -10 days from epoch.
{ldate: new types.LocalDate(1969, 12, 22), string: '1969-12-22', date: new Date(Date.UTC(1969, 11, 22))},
// Year after 0.
{ldate: new types.LocalDate(1, 1, 1), string: '0001-01-01', date: year1day1},
// 0th year.
{ldate: new types.LocalDate(0, 1, 1), string: '0000-01-01', date: year0day1},
// Year before 0.
{ldate: new types.LocalDate(-1, 1, 1), string: '-0001-01-01', date: new Date(Date.UTC(-1, 0, 1))},
// Minimum possible ES5 date.
{ldate: new types.LocalDate(-271821, 4, 20), string: '-271821-04-20', date: new Date(Date.UTC(-271821, 3, 20))},
// Maximum possible ES5 date.
{ldate: new types.LocalDate(275760, 9, 13), string: '275760-09-13', date: new Date(Date.UTC(275760, 8, 13))},
// Minimum possible C* date.
{ldate: new types.LocalDate(-2147483648), string: '-2147483648', date: new Date(NaN)},
// Maximum possible C* date.
{ldate: new types.LocalDate(2147483647), string: '2147483647', date: new Date(NaN)}
];
dates.forEach(function(item) {
const encoded = encoder.encode(item.ldate, type);
const decoded = encoder.decode(encoded, type);
helper.assertInstanceOf(decoded, types.LocalDate);
assert.ok(decoded.equals(item.ldate));
assert.strictEqual(decoded.toString(), item.string, "String mismatch for " + item.date);
if(isNaN(item.date.getTime())) {
assert.ok(isNaN(decoded.date.getTime()));
}
else {
assert.equal(decoded.date.getTime(), item.date.getTime(), decoded.date + " != " + item.date);
}
});
});
it('should refuse to encode invalid values as LocalDate.', function () {
const encoder = new Encoder(4, {});
const type = {code: dataTypes.date};
// Non Date/String/LocalDate
assert.throws(function () { encoder.encode(23.0, type);}, TypeError);
assert.throws(function () { encoder.encode('zzz', type);}, TypeError);
assert.throws(function () { encoder.encode('', type);}, TypeError);
});
it('should encode/decode LocalTime as time', function () {
const encoder = new Encoder(3, {});
const type = {code: dataTypes.time};
/* eslint-disable no-multi-spaces */
[
//Long value | string representation
['2000000501', '00:00:02.000000501'],
['0', '00:00:00'],
['3600000006001', '01:00:00.000006001'],
['61000000000', '00:01:01'],
['610000136000', '00:10:10.000136'],
['52171800000000', '14:29:31.8'],
['52171800600000', '14:29:31.8006']
].forEach(function (item) {
const encoded = encoder.encode(new types.LocalTime(types.Long.fromString(item[0])), type);
const decoded = encoder.decode(encoded, type);
helper.assertInstanceOf(decoded, types.LocalTime);
assert.strictEqual(decoded.toString(), item[1]);
});
/* eslint-enable no-multi-spaces */
});
it('should refuse to encode invalid values as LocalTime.', function () {
const encoder = new Encoder(4, {});
const type = {code: dataTypes.time};
// Negative value string.
assert.throws(function () { encoder.encode('-1:00:00', type);}, TypeError);
// Non string/LocalTime value.
assert.throws(function () { encoder.encode(23.0, type);}, TypeError);
});
it('should encode/decode FloatArray as vector, encoder guesses type', function () {
const encoder = new Encoder(4, {});
const refVal = new Float32Array([1.2, 3.4, 5.6]);
const guessedTypeObj = Encoder.guessDataType(refVal);
if (guessedTypeObj == null){
assert.fail();
}
const encoded = encoder.encode(refVal, guessedTypeObj);
const decoded = encoder.decode(encoded, guessedTypeObj);
helper.assertInstanceOf(decoded, Vector);
for (let i = 0; i < decoded.length; i++) {
assert.strictEqual(decoded[i],refVal[i]);
}
});
it('should encode/decode FloatArray as vector, encoder provided with full type', function () {
const encoder = new Encoder(4, {});
const refVal = new Float32Array([1.2, 3.4, 5.6]);
/** @type {import('../../lib/encoder').VectorColumnInfo} */
const typeObj = {code: dataTypes.custom, info: [{code : dataTypes.float},3], customTypeName : 'vector'};
const encoded = encoder.encode(refVal, typeObj);
const decoded = encoder.decode(encoded, typeObj);
helper.assertInstanceOf(decoded, Vector);
for (let i = 0; i < decoded.length; i++) {
assert.strictEqual(decoded[i],refVal[i]);
}
});
it('should encode/decode Vector of texts as vector, encoder provided with full type', function () {
const encoder = new Encoder(4, {});
const refVal = new Vector(['a', 'bc', 'de']);
/** @type {import('../../lib/encoder').VectorColumnInfo} */
const typeObj = {code: dataTypes.custom, info: [{code : dataTypes.ascii},3], customTypeName : 'vector'};
const encoded = encoder.encode(refVal, typeObj);
const decoded = encoder.decode(encoded, typeObj);
helper.assertInstanceOf(decoded, Vector);
for (let k = 0; k < decoded.length; k++) {
assert.strictEqual(decoded[k],refVal[k]);
}
});
it('should fail to encode if full type provided and input vector fails to match dimensions of type', function () {
const encoder = new Encoder(4, {});
const refVal = new Float32Array([1.2, 3.4, 5.6, 7.8]);
const typeName = 'org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType,3)';
assert.throws(function() { encoder.encode(refVal, {code: dataTypes.custom, info: typeName}); }, TypeError);
});
it('should fail to encode if input vector is not Float32Array, encoder guesses type', function () {
const encoder = new Encoder(4, {});
const refVal = new Int32Array([1, 2, 3]);
const guessedTypeObj = Encoder.guessDataType(refVal);
assert.throws(function() { encoder.encode(refVal, {code: dataTypes.custom, info: guessedTypeObj}); }, TypeError);
});
it('should fail to encode if input vector is not Float32Array, encoder provided with full type', function () {
const encoder = new Encoder(4, {});
const refVal = new Int32Array([1, 2, 3]);
const typeName = 'org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType,3)';
assert.throws(function() { encoder.encode(refVal, {code: dataTypes.custom, info: typeName}); }, TypeError);
});
it('should encode/decode nested type as vector, encoder guesses type', function () {
const encoder = new Encoder(4, {});
const refVal = new Vector([new Float32Array([1.2, 3.4, 5.6]), new Float32Array([7.8, 9.0, 11.2])]);
const guessedTypeObj = Encoder.guessDataType(refVal);
if (guessedTypeObj == null){
assert.fail();
return;
}
const encoded = encoder.encode(refVal, guessedTypeObj);
const decoded = encoder.decode(encoded, guessedTypeObj);
helper.assertInstanceOf(decoded, Vector);
for (let i = 0; i < decoded.length; i++) {
for (let j = 0; j < decoded[i].length; j++) {
assert.strictEqual(decoded[i][j],refVal[i][j]);
}
}
});
});
describe('#encode()', function () {
it('should return null when value is null', function () {
const encoder = new Encoder(2, {});
assert.strictEqual(encoder.encode(null), null);
});
it('should return unset when value is unset', function () {
const encoder = new Encoder(4, {});
assert.strictEqual(encoder.encode(types.unset), types.unset);
});
it('should return null when value is undefined', function () {
const encoder = new Encoder(2, {});
assert.strictEqual(encoder.encode(undefined), null);
});
it('should return unset when value is undefined and flag set', function () {
const encoder = new Encoder(4, { encoding: { useUndefinedAsUnset: true}});
assert.strictEqual(encoder.encode(undefined), types.unset);
});
it('should throw TypeError when value is unset with low protocol version', function () {
const encoder = new Encoder(2, {});
assert.throws(function () {
encoder.encode(types.unset);
}, TypeError);
});
it('should return null when value is undefined and flag set with low protocol version', function () {
const encoder = new Encoder(2, { encoding: { useUndefinedAsUnset: true}});
assert.strictEqual(encoder.encode(undefined), null);
});
it('should decode 0-length map values of supported types into 0-length values', function () {
const input = {'key1': Buffer.from([])};
[2, 3].forEach(v => {
const encoder = new Encoder(v, {});
const buffer = encoder.encode(input, dataTypes.map);
Array.from(zeroLengthTypesSupported).forEach(function(t){
const type = { code: types.dataTypes.map, info: [ { code: types.dataTypes.text }, { code: t } ]};
const value = encoder.decode(buffer, type);
assert.ok(value);
assert.deepEqual(Object.keys(value), Object.keys(input));
assert.strictEqual(value['key1'].length, 0);
});
});
});
it('should decode 0-length map values of unsupported types into null values', function () {
// For some unperceivable reason the server can give us an empty buffer as a map value for any type.
// We must be able to handle this behaviour.
const input = {'key1': Buffer.from([])};
[2, 3].forEach(v => {
const encoder = new Encoder(v, {});
const buffer = encoder.encode(input, dataTypes.map);
utils.objectValues(types.dataTypes)
.filter(t => typeof t === "number" && !zeroLengthTypesSupported.has(t))
.forEach(t => {
const type = { code: types.dataTypes.map, info: [ { code: types.dataTypes.text }, { code: t } ]};
const value = encoder.decode(buffer, type);
assert.ok(value);
assert.deepEqual(Object.keys(value), Object.keys(input));
assert.strictEqual(value['key1'], null);
});
});
});
it('should decode empty buffers as nulls for most types', () => {
const encoder = new Encoder(4, {});
const emptyBuffer = utils.allocBufferUnsafe(0);
utils.objectValues(types.dataTypes)
.filter(t => typeof t === "number" && !zeroLengthTypesSupported.has(t))
.forEach(code => assert.strictEqual(encoder.decode(emptyBuffer, { code }), null));
});
it('should decode empty buffers as empty strings', () => {
const encoder = new Encoder(4, {});
const emptyBuffer = utils.allocBufferUnsafe(0);
[
dataTypes.text,
dataTypes.ascii,
dataTypes.varchar
]
.forEach(code => assert.strictEqual(encoder.decode(emptyBuffer, { code }), ''));
});
it('should decode empty buffers for blobs and custom types', () => {
const encoder = new Encoder(4, {});
const emptyBuffer = utils.allocBufferUnsafe(0);
[
dataTypes.blob,
dataTypes.custom
]
.forEach(t => assert.deepStrictEqual(encoder.decode(emptyBuffer, {code: t}), utils.allocBufferUnsafe(0)));
});
it('should decode null map values', function () {
// technically this should not be possible as nulls are not allowed in collections.
// at a protocol level this is only possible with v3+ as v2 uses unsigned short for collection element size.
const encoder = new Encoder(3, {});
const buffer = utils.allocBufferFromString('00000001000000046b657931FFFFFFFF', 'hex');
const value = encoder.decode(buffer,
{ code: types.dataTypes.map, info: [ { code: types.dataTypes.text }, { code: types.dataTypes.text } ]});
assert.ok(value);
assert.deepEqual(Object.keys(value), ['key1']);
assert.strictEqual(value['key1'], null);
});
it('should return the raw buffer when a buffer is provided', () => {
const encoder = new Encoder(3, {});
const buffer = utils.allocBufferFromArray([ 0, 1, 2, 3 ]);
Object.keys(dataTypes).forEach(typeName => {
assert.strictEqual(encoder.encode(buffer, dataTypes[typeName]), buffer);
});
});
it('should encode string representations of numeric values', () => {
const encoder = newInstance();
assertBuffer(encoder.encode('3', getType(dataTypes.bigint)), 8);
assertBuffer(encoder.encode('3', getType(dataTypes.varint)), 1);
assertBuffer(encoder.encode('3', getType(dataTypes.int)), 4);
assertBuffer(encoder.encode('3.375', getType(dataTypes.float)), 4);
assertBuffer(encoder.encode('3.375', getType(dataTypes.double)), 8);
assertBuffer(encoder.encode('3.375', getType(dataTypes.decimal)), 6);
assertBuffer(encoder.encode(NaN, getType(dataTypes.double)), 8);
});
it('should encode NaN', () => {
const encoder = newInstance();
assertBuffer(encoder.encode(NaN, getType(dataTypes.double)), 8);
assertBuffer(encoder.encode(NaN, getType(dataTypes.float)), 4);
});
it('should throw an error for invalid string representations of numeric values', () => {
const encoder = newInstance();
assert.throws(() => encoder.encode('hello!', getType(dataTypes.int)), TypeError);
assert.throws(() => encoder.encode('hello!', getType(dataTypes.double)), TypeError);
assert.throws(() => encoder.encode('hello!', getType(dataTypes.float)), TypeError);
});
});
describe('#setRoutingKeyFromUser()', function () {
const encoder = new Encoder(2, {});
it('should concat Array of buffers in the correct format',function () {
let options = getExecOptions({
//Its an array of 3 values
/** @type {Array|Buffer} */
routingKey: [
utils.allocBufferFromArray([1]), utils.allocBufferFromArray([2]), utils.allocBufferFromArray([3, 3])]
});
encoder.setRoutingKeyFromUser([1, 'text'], options);
assert.ok(options.getRoutingKey());
//The routingKey should have the form: [2-byte-length] + key + [0]
// eslint-disable-next-line no-useless-concat
assert.strictEqual(options.getRoutingKey().toString('hex'), '00010100' + '00010200' + '0002030300');
options = getExecOptions({
//Its an array of 1 value
routingKey: [utils.allocBufferFromArray([1])]
});
encoder.setRoutingKeyFromUser([], options);
//the result should be a single value
assert.strictEqual(options.getRoutingKey().toString('hex'), '01');
});
it('should not affect Buffer routing keys', function () {
let options = getExecOptions({
routingKey: utils.allocBufferFromArray([1, 2, 3, 4])
});
const initialRoutingKey = options.getRoutingKey().toString('hex');
encoder.setRoutingKeyFromUser([1, 'text'], options);
assert.strictEqual(options.getRoutingKey().toString('hex'), initialRoutingKey);
options = getExecOptions({
routingIndexes: [1],
routingKey: utils.allocBufferFromArray([1, 2, 3, 4])
});
encoder.setRoutingKeyFromUser([1, 'text'], options);
//The routing key should take precedence over routingIndexes
assert.strictEqual(options.getRoutingKey().toString('hex'), initialRoutingKey);
});
it('should not affect Token routing keys', function () {
const token = new tokenizer.Murmur3Tokenizer().hash('4611686018427387904');
const options = getExecOptions({
routingIndexes: [1],
routingKey: token
});
encoder.setRoutingKeyFromUser([1, 'text'], options);
assert.strictEqual(options.getRoutingKey(), token);
});
it('should not affect TokenRange routing keys', function () {
const murmur3 = new tokenizer.Murmur3Tokenizer();
const start = murmur3.hash('-9223372036854775808');
const end = murmur3.hash('4611686018427387904');
const range = new token.TokenRange(start, end, murmur3);
const options = getExecOptions({
routingIndexes: [1],
routingKey: range
});
encoder.setRoutingKeyFromUser([1, 'text'], options);
assert.strictEqual(options.getRoutingKey(), range);
});
it('should build routing key based on routingIndexes', function () {
let options = getExecOptions({
hints: ['int'],
routingIndexes: [0]
});
encoder.setRoutingKeyFromUser([1], options);
assert.strictEqual(options.getRoutingKey().toString('hex'), '00000001');
options = getExecOptions({
hints: ['int', 'string', 'int'],
routingIndexes: [0, 2]
});
encoder.setRoutingKeyFromUser([1, 'yeah', 2], options);
//length1 + buffer1 + 0 + length2 + buffer2 + 0
// eslint-disable-next-line no-useless-concat
assert.strictEqual(options.getRoutingKey().toString('hex'), '0004' + '00000001' + '00' + '0004' + '00000002' + '00');
options = getExecOptions({
//less hints
hints: ['int'],
routingIndexes: [0, 2]
});
encoder.setRoutingKeyFromUser([1, 'yeah', utils.allocBufferFromArray([1, 1, 1, 1])], options);
//length1 + buffer1 + 0 + length2 + buffer2 + 0
// eslint-disable-next-line no-useless-concat
assert.strictEqual(options.getRoutingKey().toString('hex'), '0004' + '00000001' + '00' + '0004' + '01010101' + '00');
options = getExecOptions({
//no hints
routingIndexes: [1, 2]
});
encoder.setRoutingKeyFromUser([1, 'yeah', utils.allocBufferFromArray([1, 1, 1, 1])], options);
assert.strictEqual(options.getRoutingKey().toString('hex'),
//length1 + buffer1 + 0 + length2 + buffer2 + 0
// eslint-disable-next-line no-useless-concat
'0004' + utils.allocBufferFromString('yeah').toString('hex') + '00' + '0004' + '01010101' + '00');
});
it('should allow null/undefined routingIndexes', function () {
const execOptions = getExecOptions({
hints: ['int', 'text'],
routingIndexes: [0, null, 2]
});
encoder.setRoutingKeyFromUser([1], execOptions);
// It doesn't fail, it bypass routing logic
assert.strictEqual(execOptions.getRoutingKey(), undefined);
});
it('should allow null or undefined routingKey parts', function () {
let options = getExecOptions({
routingKey: [utils.allocBufferFromArray([0]), null, utils.allocBufferFromArray([1])]
});
encoder.setRoutingKeyFromUser([], options);
assert.strictEqual(options.getRoutingKey(), null);
options = getExecOptions({
routingKey: [utils.allocBufferFromArray([0]), undefined, utils.allocBufferFromArray([1])]
});
encoder.setRoutingKeyFromUser([], options);
assert.strictEqual(options.getRoutingKey(), null);
});
it('should throw TypeError if invalid routingKey type is provided', function () {
assert.throws(() => {
encoder.setRoutingKeyFromUser([1, 'text'], getExecOptions({ routingKey: 123 }));
}, TypeError);
});
});
describe('#parseFqTypeName()', function () {
it('should parse single type names', function () {
const encoder = new Encoder(2, {});
let type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.Int32Type');
assert.strictEqual(dataTypes.int, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.UUIDType');
assert.strictEqual(dataTypes.uuid, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.UTF8Type');
assert.strictEqual(dataTypes.varchar, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.BytesType');
assert.strictEqual(dataTypes.blob, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.FloatType');
assert.strictEqual(dataTypes.float, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.DoubleType');
assert.strictEqual(dataTypes.double, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.BooleanType');
assert.strictEqual(dataTypes.boolean, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.InetAddressType');
assert.strictEqual(dataTypes.inet, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.DateType');
assert.strictEqual(dataTypes.timestamp, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.TimestampType');
assert.strictEqual(dataTypes.timestamp, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.LongType');
assert.strictEqual(dataTypes.bigint, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.DecimalType');
assert.strictEqual(dataTypes.decimal, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.IntegerType');
assert.strictEqual(dataTypes.varint, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.CounterColumnType');
assert.strictEqual(dataTypes.counter, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.TimeUUIDType');
assert.strictEqual(dataTypes.timeuuid, type.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.AsciiType');
assert.strictEqual(dataTypes.ascii, type.code);
});
it('should parse complex type names', function () {
const encoder = new Encoder(2, {});
let type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.Int32Type)');
assert.strictEqual(dataTypes.list, type.code);
assert.ok(type.info);
assert.strictEqual(dataTypes.int, type.info.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UUIDType)');
assert.strictEqual(dataTypes.set, type.code);
assert.ok(type.info);
assert.strictEqual(dataTypes.uuid, type.info.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.TimeUUIDType)');
assert.strictEqual(dataTypes.set, type.code);
assert.ok(type.info);
assert.strictEqual(dataTypes.timeuuid, type.info.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.LongType)');
assert.strictEqual(dataTypes.map, type.code);
assert.ok(Array.isArray(type.info));
assert.strictEqual(dataTypes.varchar, type.info[0].code);
assert.strictEqual(dataTypes.bigint, type.info[1].code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.TupleType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.Int32Type)');
assert.strictEqual(dataTypes.tuple, type.code);
assert.ok(Array.isArray(type.info));
assert.strictEqual(dataTypes.varchar, type.info[0].code);
assert.strictEqual(dataTypes.int, type.info[1].code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType,10)');
assert.strictEqual(dataTypes.custom, type.code);
assert.ok(typeof type.info === 'object');
assert.strictEqual(dataTypes.float, type.info[0].code);
assert.strictEqual(10, type.info[1]);
});
it('should parse frozen types', function () {
const encoder = new Encoder(2, {});
let type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.TimeUUIDType))');
assert.strictEqual(dataTypes.list, type.code);
assert.ok(type.info);
assert.strictEqual(dataTypes.timeuuid, type.info.code);
type = encoder.parseFqTypeName('org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.Int32Type)))');
assert.strictEqual(dataTypes.map, type.code);
assert.ok(Array.isArray(type.info));
assert.strictEqual(dataTypes.varchar, type.info[0].code);
assert.strictEqual(dataTypes.list, type.info[1].code);
const subType = type.info[1].info;
assert.ok(subType);
assert.strictEqual(dataTypes.int, subType.code);
});
it('should parse udt types', function () {
const encoder = new Encoder(2, {});
const typeText =
'org.apache.cassandra.db.marshal.UserType(' +
'tester,70686f6e65,616c696173:org.apache.cassandra.db.marshal.UTF8Type,6e756d626572:org.apache.cassandra.db.marshal.UTF8Type' +
')';
const dataType = encoder.parseFqTypeName(typeText);
assert.strictEqual(dataTypes.udt, dataType.code);
//Udt name
assert.ok(dataType.info);
assert.strictEqual('phone', dataType.info.name);
assert.strictEqual(2, dataType.info.fields.length);
assert.strictEqual('alias', dataType.info.fields[0].name);
assert.strictEqual(dataTypes.varchar, dataType.info.fields[0].type.code);
assert.strictEqual('number', dataType.info.fields[1].name);
assert.strictEqual(dataTypes.varchar, dataType.info.fields[1].type.code);
});
it('should parse nested udt types', function () {
const encoder = new Encoder(2, {});
const typeText =
'org.apache.cassandra.db.marshal.UserType(' +
'tester,' +
'61646472657373,' +
'737472656574:org.apache.cassandra.db.marshal.UTF8Type,' +
'5a4950:org.apache.cassandra.db.marshal.Int32Type,' +
'70686f6e6573:org.apache.cassandra.db.marshal.SetType(' +
'org.apache.cassandra.db.marshal.UserType(' +
'tester,' +
'70686f6e65,' +
'616c696173:org.apache.cassandra.db.marshal.UTF8Type,' +
'6e756d626572:org.apache.cassandra.db.marshal.UTF8Type))' +
')';
const dataType = encoder.parseFqTypeName(typeText);
assert.strictEqual(dataTypes.udt, dataType.code);
assert.strictEqual('address', dataType.info.name);
assert.strictEqual('tester', dataType.info.keyspace);
const subTypes = dataType.info.fields;
assert.strictEqual(3, subTypes.length);
assert.strictEqual('street,ZIP,phones', subTypes.map(function (f) {return f.name;}).join(','));
assert.strictEqual(dataTypes.varchar, subTypes[0].type.code);
assert.strictEqual(dataTypes.set, subTypes[2].type.code);
//field name
assert.strictEqual('phones', subTypes[2].name);
const phonesSubType = subTypes[2].type.info;
assert.strictEqual(dataTypes.udt, phonesSubType.code);
assert.strictEqual('phone', phonesSubType.info.name);
assert.strictEqual('tester', phonesSubType.info.keyspace);
assert.strictEqual(2, phonesSubType.info.fields.length);
assert.strictEqual('alias', phonesSubType.info.fields[0].name);
assert.strictEqual('number', phonesSubType.info.fields[1].name);
});
});
describe('#parseTypeName()', function () {
it('should parse single type names', async () => {
const encoder = new Encoder(4, {});
/* eslint-disable no-multi-spaces */
const items = [
['int', dataTypes.int],
['uuid', dataTypes.uuid],
['text', dataTypes.text],
['varchar', dataTypes.varchar],
['blob', dataTypes.blob],
['float', dataTypes.float],
['double', dataTypes.double],
['boolean', dataTypes.boolean],
['inet', dataTypes.inet],
['timestamp', dataTypes.timestamp],
['bigint', dataTypes.bigint],
['decimal', dataTypes.decimal],
['varint', dataTypes.varint],
['counter', dataTypes.counter],
['timeuuid', dataTypes.timeuuid],
['ascii', dataTypes.ascii]
];
/* eslint-enable no-multi-spaces */
for (const item of items) {
const dataType = await encoder.parseTypeName('ks1', item[0], 0, null, helper.failop);
assert.ok(dataType);
assert.strictEqual(dataType.code, item[1]);
}
});
it('should parse complex type names', async () => {
const encoder = new Encoder(4, {});
const items = [
['list<int>', dataTypes.list, dataTypes.int],
['set<uuid>', dataTypes.set, dataTypes.uuid],
['set<timeuuid>', dataTypes.set, dataTypes.timeuuid],
['map<varchar,bigint>', dataTypes.map, [dataTypes.varchar, dataTypes.bigint]],
['tuple<varchar,int>', dataTypes.tuple, [dataTypes.varchar, dataTypes.int]],
['frozen<list<timeuuid>>', dataTypes.list, dataTypes.timeuuid],
['map<text,frozen<list<int>>>', dataTypes.map, [dataTypes.text, dataTypes.list]],
['vector<float,20>', dataTypes.custom, {code: dataTypes.float, dimension: 20}]
];
for (const item of items) {
const dataType = await encoder.parseTypeName('ks1', item[0], 0, null, helper.failop);
assert.ok(dataType, `Type not parsed for ${item[0]}`);
assert.strictEqual(dataType.code, item[1]);
assert.notEqual(dataType.info, null);
if (Array.isArray(item[2])) {
assert.strictEqual(dataType.info.length, item[2].length);
dataType.info.forEach(function (childType, i) {
// If it's an object use the code property, otherwise just use the value itself
assert.strictEqual((childType.code || childType), item[2][i]);
});
}
else if (typeof item[2] === 'object') {
assert.strictEqual(dataType.info[0].code, item[2].code);
assert.strictEqual(dataType.info[1], item[2].dimension);
}
else {
assert.strictEqual(dataType.info.code, item[2]);
}
}
});
it('should parse nested subtypes', async () => {
const encoder = new Encoder(4, {});
const name = 'map<text,frozen<list<frozen<map<text,frozen<list<int>>>>>>>';
const type = await encoder.parseTypeName('ks1', name, 0, null, helper.failop);
assert.ok(type);
assert.strictEqual(type.code, dataTypes.map);
assert.strictEqual(type.info[0].code, dataTypes.text);
assert.strictEqual(type.info[1].code, dataTypes.list);
const subType1 = type.info[1];
assert.strictEqual(subType1.info.code, dataTypes.map);
const subType2 = subType1.info.info[1];
assert.strictEqual(subType2.code, dataTypes.list);
assert.strictEqual(subType2.info.code, dataTypes.int);
});
[
true, false
].forEach(frozen => {
it(`should parse udts with frozen ${frozen}`, async () => {
const encoder = new Encoder(4, {});
const udtResolver = sinon.spy(() => Promise.resolve('udt info dummy'));
const name = frozen ? 'frozen<address>' : 'address';
const type = await encoder.parseTypeName('ks_sample', name, 0, null, udtResolver);
assert.ok(type);
assert.strictEqual(type.code, dataTypes.udt);
assert.strictEqual(type.options.frozen, frozen);
assert.strictEqual(udtResolver.callCount, 1);
assert.deepStrictEqual(udtResolver.firstCall.args, ['ks_sample', 'address']);
});
});
[
{ name: 'PHONE', frozen: true },
{ name: 'PhoNe', frozen: false }
].forEach(({ name, frozen }) => {
it(`should parse quoted udts with frozen ${frozen}`, async () => {
const encoder = new Encoder(4, {});
const udtResolver = sinon.spy(() => Promise.resolve('udt info dummy'));
const nameParameter = frozen ? `frozen<"${name}">` : `"${name}"`;
const type = await encoder.parseTypeName('ks_sample2', nameParameter, 0, null, udtResolver);
assert.strictEqual(type.code, dataTypes.udt);
assert.strictEqual(type.options.frozen, frozen);
assert.strictEqual(udtResolver.callCount, 1);
assert.deepStrictEqual(udtResolver.firstCall.args, ['ks_sample2', name]);
});
});
it('should callback with TypeError when not found', async () => {
const encoder = new Encoder(4, {});
const udtResolver = sinon.spy(() => Promise.resolve(null));
await helper.assertThrowsAsync(encoder.parseTypeName('ks2', 'WHATEVER', 0, null, udtResolver), TypeError);
assert.ok(udtResolver.calledOnce);
});
it('should callback with the same error if udtResolver fails', async () => {
const encoder = new Encoder(4, {});
const testError = new Error('Test error');
const udtResolver = sinon.spy(() => Promise.reject(testError));
const err = await helper.assertThrowsAsync(encoder.parseTypeName('ks2', 'WHATEVER', 0, null, udtResolver));
assert.strictEqual(err, testError);
});
});
describe('#parseKeyTypes', function () {
const encoder = new Encoder(1, {});
it('should parse single type', function () {
let value = 'org.apache.cassandra.db.marshal.UTF8Type';
let result = encoder.parseKeyTypes(value);
assert.strictEqual(result.types.length, 1);
assert.strictEqual(result.types[0].code, types.dataTypes.varchar);
value = 'org.apache.cassandra.db.marshal.TimeUUIDType';
result = encoder.parseKeyTypes(value);
assert.strictEqual(result.types.length, 1);
assert.strictEqual(result.types[0].code, types.dataTypes.timeuuid);
assert.strictEqual(result.isComposite, false);
assert.strictEqual(result.hasCollections, false);
});
it('should parse composites', function () {
const value = 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)';
const result = encoder.parseKeyTypes(value);
assert.strictEqual(result.types.length, 2);
assert.strictEqual(result.types[0].code, types.dataTypes.int);
assert.strictEqual(result.types[1].code, types.dataTypes.varchar);
assert.strictEqual(result.isComposite, true);
assert.strictEqual(result.hasCollections, false);
});
it('should parse composites with collection types', function () {
const value = 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.ColumnToCollectionType(6c6973745f73616d706c65:org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.Int32Type),6d61705f73616d706c65:org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type)))';
const result = encoder.parseKeyTypes(value);
assert.strictEqual(result.types.length, 4);
assert.strictEqual(result.types[0].code, types.dataTypes.varchar);
assert.strictEqual(result.types[1].code, types.dataTypes.int);
assert.strictEqual(result.types[2].code, types.dataTypes.list);
assert.strictEqual(result.types[3].code, types.dataTypes.map);
});
});
describe('constructor', function () {
it('should define instance members', function () {
const encoder = new Encoder(4, {});
assert.strictEqual(typeof encoder.encodeBlob, 'function');
assert.strictEqual(typeof encoder.decodeBlob, 'function');
assert.strictEqual(typeof encoder.protocolVersion, 'number');
});
});
describe('prototype', function () {
it('should only expose encode() and decode() functions', function () {
const keys = Object.keys(Encoder.prototype);
assert.deepStrictEqual(keys, ['decode', 'encode']);
keys.forEach(function (k) {
assert.strictEqual(typeof Encoder.prototype[k], 'function');
});
});
});
});
function getExecOptions(options) {
const result = ExecutionOptions.empty();
let routingKey = options.routingKey;
result.setRoutingKey = v => routingKey = v;
result.getRoutingKey = () => routingKey;
result.getHints = () => options.hints;
result.getRoutingIndexes = () => options.routingIndexes;
return result;
}
function newInstance(protocolVersion, clientOptions) {
return new Encoder(protocolVersion || types.protocolVersion.maxSupported, clientOptions || {});
}
function getType(typeCode) {
return { code: typeCode };
}
function assertBuffer(buffer, length) {
helper.assertInstanceOf(buffer, Buffer);
assert.strictEqual(buffer.length, length);
}