UNPKG

56.3 kBJavaScriptView Raw
1/*
2 * Copyright DataStax, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16'use strict';
17const util = require('util');
18
19const types = require('./types');
20const dataTypes = types.dataTypes;
21const Long = types.Long;
22const Integer = types.Integer;
23const BigDecimal = types.BigDecimal;
24const MutableLong = require('./types/mutable-long');
25const utils = require('./utils');
26const token = require('./token');
27const { DateRange } = require('./datastax/search');
28const geo = require('./geometry');
29const Geometry = geo.Geometry;
30const LineString = geo.LineString;
31const Point = geo.Point;
32const Polygon = geo.Polygon;
33
34const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
35
36const buffers = {
37 int16Zero: utils.allocBufferFromArray([0, 0]),
38 int32Zero: utils.allocBufferFromArray([0, 0, 0, 0]),
39 int8Zero: utils.allocBufferFromArray([0]),
40 int8One: utils.allocBufferFromArray([1]),
41 int8MaxValue: utils.allocBufferFromArray([0xff])
42};
43
44// BigInt: Avoid using literals (e.g., 32n) as we must be able to compile with older engines
45const isBigIntSupported = typeof BigInt !== 'undefined';
46const bigInt32 = isBigIntSupported ? BigInt(32) : null;
47const bigInt8 = isBigIntSupported ? BigInt(8) : null;
48const bigInt0 = isBigIntSupported ? BigInt(0) : null;
49const bigIntMinus1 = isBigIntSupported ? BigInt(-1) : null;
50const bigInt32BitsOn = isBigIntSupported ? BigInt(0xffffffff) : null;
51const bigInt8BitsOn = isBigIntSupported ? BigInt(0xff) : null;
52
53const complexTypeNames = Object.freeze({
54 list : 'org.apache.cassandra.db.marshal.ListType',
55 set : 'org.apache.cassandra.db.marshal.SetType',
56 map : 'org.apache.cassandra.db.marshal.MapType',
57 udt : 'org.apache.cassandra.db.marshal.UserType',
58 tuple : 'org.apache.cassandra.db.marshal.TupleType',
59 frozen : 'org.apache.cassandra.db.marshal.FrozenType',
60 reversed : 'org.apache.cassandra.db.marshal.ReversedType',
61 composite : 'org.apache.cassandra.db.marshal.CompositeType',
62 empty : 'org.apache.cassandra.db.marshal.EmptyType',
63 collection: 'org.apache.cassandra.db.marshal.ColumnToCollectionType'
64});
65const cqlNames = Object.freeze({
66 frozen: 'frozen',
67 list: 'list',
68 'set': 'set',
69 map: 'map',
70 tuple: 'tuple',
71 empty: 'empty',
72 duration: 'duration'
73});
74const singleTypeNames = Object.freeze({
75 'org.apache.cassandra.db.marshal.UTF8Type': dataTypes.varchar,
76 'org.apache.cassandra.db.marshal.AsciiType': dataTypes.ascii,
77 'org.apache.cassandra.db.marshal.UUIDType': dataTypes.uuid,
78 'org.apache.cassandra.db.marshal.TimeUUIDType': dataTypes.timeuuid,
79 'org.apache.cassandra.db.marshal.Int32Type': dataTypes.int,
80 'org.apache.cassandra.db.marshal.BytesType': dataTypes.blob,
81 'org.apache.cassandra.db.marshal.FloatType': dataTypes.float,
82 'org.apache.cassandra.db.marshal.DoubleType': dataTypes.double,
83 'org.apache.cassandra.db.marshal.BooleanType': dataTypes.boolean,
84 'org.apache.cassandra.db.marshal.InetAddressType': dataTypes.inet,
85 'org.apache.cassandra.db.marshal.SimpleDateType': dataTypes.date,
86 'org.apache.cassandra.db.marshal.TimeType': dataTypes.time,
87 'org.apache.cassandra.db.marshal.ShortType': dataTypes.smallint,
88 'org.apache.cassandra.db.marshal.ByteType': dataTypes.tinyint,
89 'org.apache.cassandra.db.marshal.DateType': dataTypes.timestamp,
90 'org.apache.cassandra.db.marshal.TimestampType': dataTypes.timestamp,
91 'org.apache.cassandra.db.marshal.LongType': dataTypes.bigint,
92 'org.apache.cassandra.db.marshal.DecimalType': dataTypes.decimal,
93 'org.apache.cassandra.db.marshal.IntegerType': dataTypes.varint,
94 'org.apache.cassandra.db.marshal.CounterColumnType': dataTypes.counter
95});
96const singleFqTypeNamesLength = Object.keys(singleTypeNames).reduce(function (previous, current) {
97 return current.length > previous ? current.length : previous;
98}, 0);
99
100const customTypeNames = Object.freeze({
101 duration: 'org.apache.cassandra.db.marshal.DurationType',
102 lineString: 'org.apache.cassandra.db.marshal.LineStringType',
103 point: 'org.apache.cassandra.db.marshal.PointType',
104 polygon: 'org.apache.cassandra.db.marshal.PolygonType',
105 dateRange: 'org.apache.cassandra.db.marshal.DateRangeType'
106});
107
108const nullValueBuffer = utils.allocBufferFromArray([255, 255, 255, 255]);
109const unsetValueBuffer = utils.allocBufferFromArray([255, 255, 255, 254]);
110
111/**
112 * For backwards compatibility, empty buffers as text/blob/custom values are supported.
113 * In the case of other types, they are going to be decoded as a <code>null</code> value.
114 * @private
115 * @type {Set}
116 */
117const zeroLengthTypesSupported = new Set([
118 dataTypes.text,
119 dataTypes.ascii,
120 dataTypes.varchar,
121 dataTypes.custom,
122 dataTypes.blob
123]);
124
125/**
126 * Serializes and deserializes to and from a CQL type and a Javascript Type.
127 * @param {Number} protocolVersion
128 * @param {ClientOptions} options
129 * @constructor
130 */
131function Encoder(protocolVersion, options) {
132 this.encodingOptions = options.encoding || utils.emptyObject;
133 defineInstanceMembers.call(this);
134 this.setProtocolVersion(protocolVersion);
135 setEncoders.call(this);
136 if (this.encodingOptions.copyBuffer) {
137 this.handleBuffer = handleBufferCopy;
138 }
139 else {
140 this.handleBuffer = handleBufferRef;
141 }
142}
143
144/**
145 * Declares the privileged instance members.
146 * @private
147 */
148function defineInstanceMembers() {
149 /**
150 * Sets the protocol version and the encoding/decoding methods depending on the protocol version
151 * @param {Number} value
152 * @ignore
153 * @internal
154 */
155 this.setProtocolVersion = function (value) {
156 this.protocolVersion = value;
157 //Set the collection serialization based on the protocol version
158 this.decodeCollectionLength = decodeCollectionLengthV3;
159 this.getLengthBuffer = getLengthBufferV3;
160 this.collectionLengthSize = 4;
161 if (!types.protocolVersion.uses4BytesCollectionLength(this.protocolVersion)) {
162 this.decodeCollectionLength = decodeCollectionLengthV2;
163 this.getLengthBuffer = getLengthBufferV2;
164 this.collectionLengthSize = 2;
165 }
166 };
167
168 const customDecoders = {
169 [customTypeNames.duration]: decodeDuration,
170 [customTypeNames.lineString]: decodeLineString,
171 [customTypeNames.point]: decodePoint,
172 [customTypeNames.polygon]: decodePolygon,
173 [customTypeNames.dateRange]: decodeDateRange
174 };
175
176 const customEncoders = {
177 [customTypeNames.duration]: encodeDuration,
178 [customTypeNames.lineString]: encodeLineString,
179 [customTypeNames.point]: encodePoint,
180 [customTypeNames.polygon]: encodePolygon,
181 [customTypeNames.dateRange]: encodeDateRange
182 };
183
184 // Decoding methods
185 this.decodeBlob = function (bytes) {
186 return this.handleBuffer(bytes);
187 };
188 this.decodeCustom = function (bytes, typeName) {
189 const handler = customDecoders[typeName];
190 if (handler) {
191 return handler.call(this, bytes);
192 }
193 return this.handleBuffer(bytes);
194 };
195 this.decodeUtf8String = function (bytes) {
196 return bytes.toString('utf8');
197 };
198 this.decodeAsciiString = function (bytes) {
199 return bytes.toString('ascii');
200 };
201 this.decodeBoolean = function (bytes) {
202 return !!bytes.readUInt8(0);
203 };
204 this.decodeDouble = function (bytes) {
205 return bytes.readDoubleBE(0);
206 };
207 this.decodeFloat = function (bytes) {
208 return bytes.readFloatBE(0);
209 };
210 this.decodeInt = function (bytes) {
211 return bytes.readInt32BE(0);
212 };
213 this.decodeSmallint = function (bytes) {
214 return bytes.readInt16BE(0);
215 };
216 this.decodeTinyint = function (bytes) {
217 return bytes.readInt8(0);
218 };
219
220 this._decodeCqlLongAsLong = function (bytes) {
221 return Long.fromBuffer(bytes);
222 };
223
224 this._decodeCqlLongAsBigInt = function (bytes) {
225 return BigInt.asIntN(64, (BigInt(bytes.readUInt32BE(0)) << bigInt32) | BigInt(bytes.readUInt32BE(4)));
226 };
227
228 this.decodeLong = this.encodingOptions.useBigIntAsLong
229 ? this._decodeCqlLongAsBigInt
230 : this._decodeCqlLongAsLong;
231
232 this._decodeVarintAsInteger = function (bytes) {
233 return Integer.fromBuffer(bytes);
234 };
235
236 this._decodeVarintAsBigInt = function decodeVarintAsBigInt(bytes) {
237 let result = bigInt0;
238 if (bytes[0] <= 0x7f) {
239 for (let i = 0; i < bytes.length; i++) {
240 const b = BigInt(bytes[bytes.length - 1 - i]);
241 result = result | (b << BigInt(i * 8));
242 }
243 } else {
244 for (let i = 0; i < bytes.length; i++) {
245 const b = BigInt(bytes[bytes.length - 1 - i]);
246 result = result | ((~b & bigInt8BitsOn) << BigInt(i * 8));
247 }
248 result = ~result;
249 }
250
251 return result;
252 };
253
254 this.decodeVarint = this.encodingOptions.useBigIntAsVarint
255 ? this._decodeVarintAsBigInt
256 : this._decodeVarintAsInteger;
257
258 this.decodeDecimal = function(bytes) {
259 return BigDecimal.fromBuffer(bytes);
260 };
261 this.decodeTimestamp = function(bytes) {
262 return new Date(this._decodeCqlLongAsLong(bytes).toNumber());
263 };
264 this.decodeDate = function (bytes) {
265 return types.LocalDate.fromBuffer(bytes);
266 };
267 this.decodeTime = function (bytes) {
268 return types.LocalTime.fromBuffer(bytes);
269 };
270 /*
271 * Reads a list from bytes
272 */
273 this.decodeList = function (bytes, subtype) {
274 const totalItems = this.decodeCollectionLength(bytes, 0);
275 let offset = this.collectionLengthSize;
276 const list = new Array(totalItems);
277 for (let i = 0; i < totalItems; i++) {
278 //bytes length of the item
279 const length = this.decodeCollectionLength(bytes, offset);
280 offset += this.collectionLengthSize;
281 //slice it
282 list[i] = this.decode(bytes.slice(offset, offset+length), subtype);
283 offset += length;
284 }
285 return list;
286 };
287 /*
288 * Reads a Set from bytes
289 */
290 this.decodeSet = function (bytes, subtype) {
291 const arr = this.decodeList(bytes, subtype);
292 if (this.encodingOptions.set) {
293 const setConstructor = this.encodingOptions.set;
294 return new setConstructor(arr);
295 }
296 return arr;
297 };
298 /*
299 * Reads a map (key / value) from bytes
300 */
301 this.decodeMap = function (bytes, subtypes) {
302 let map;
303 const totalItems = this.decodeCollectionLength(bytes, 0);
304 let offset = this.collectionLengthSize;
305 const self = this;
306 function readValues(callback, thisArg) {
307 for (let i = 0; i < totalItems; i++) {
308 const keyLength = self.decodeCollectionLength(bytes, offset);
309 offset += self.collectionLengthSize;
310 const key = self.decode(bytes.slice(offset, offset + keyLength), subtypes[0]);
311 offset += keyLength;
312 const valueLength = self.decodeCollectionLength(bytes, offset);
313 offset += self.collectionLengthSize;
314 if (valueLength < 0) {
315 callback.call(thisArg, key, null);
316 continue;
317 }
318 const value = self.decode(bytes.slice(offset, offset + valueLength), subtypes[1]);
319 offset += valueLength;
320 callback.call(thisArg, key, value);
321 }
322 }
323 if (this.encodingOptions.map) {
324 const mapConstructor = this.encodingOptions.map;
325 map = new mapConstructor();
326 readValues(map.set, map);
327 }
328 else {
329 map = {};
330 readValues(function (key, value) {
331 map[key] = value;
332 });
333 }
334 return map;
335 };
336 this.decodeUuid = function (bytes) {
337 return new types.Uuid(this.handleBuffer(bytes));
338 };
339 this.decodeTimeUuid = function (bytes) {
340 return new types.TimeUuid(this.handleBuffer(bytes));
341 };
342 this.decodeInet = function (bytes) {
343 return new types.InetAddress(this.handleBuffer(bytes));
344 };
345 /**
346 * Decodes a user defined type into an object
347 * @param {Buffer} bytes
348 * @param {{fields: Array}} udtInfo
349 * @private
350 */
351 this.decodeUdt = function (bytes, udtInfo) {
352 const result = {};
353 let offset = 0;
354 for (let i = 0; i < udtInfo.fields.length && offset < bytes.length; i++) {
355 //bytes length of the field value
356 const length = bytes.readInt32BE(offset);
357 offset += 4;
358 //slice it
359 const field = udtInfo.fields[i];
360 if (length < 0) {
361 result[field.name] = null;
362 continue;
363 }
364 result[field.name] = this.decode(bytes.slice(offset, offset+length), field.type);
365 offset += length;
366 }
367 return result;
368 };
369
370 this.decodeTuple = function (bytes, tupleInfo) {
371 const elements = new Array(tupleInfo.length);
372 let offset = 0;
373
374 for (let i = 0; i < tupleInfo.length && offset < bytes.length; i++) {
375 const length = bytes.readInt32BE(offset);
376 offset += 4;
377
378 if (length < 0) {
379 elements[i] = null;
380 continue;
381 }
382
383 elements[i] = this.decode(bytes.slice(offset, offset+length), tupleInfo[i]);
384 offset += length;
385 }
386
387 return types.Tuple.fromArray(elements);
388 };
389
390 //Encoding methods
391 this.encodeFloat = function (value) {
392 if (typeof value === 'string') {
393 // All numeric types are supported as strings for historical reasons
394 value = parseFloat(value);
395
396 if (Number.isNaN(value)) {
397 throw new TypeError(`Expected string representation of a number, obtained ${util.inspect(value)}`);
398 }
399 }
400
401 if (typeof value !== 'number') {
402 throw new TypeError('Expected Number, obtained ' + util.inspect(value));
403 }
404
405 const buf = utils.allocBufferUnsafe(4);
406 buf.writeFloatBE(value, 0);
407 return buf;
408 };
409
410 this.encodeDouble = function (value) {
411 if (typeof value === 'string') {
412 // All numeric types are supported as strings for historical reasons
413 value = parseFloat(value);
414
415 if (Number.isNaN(value)) {
416 throw new TypeError(`Expected string representation of a number, obtained ${util.inspect(value)}`);
417 }
418 }
419
420 if (typeof value !== 'number') {
421 throw new TypeError('Expected Number, obtained ' + util.inspect(value));
422 }
423
424 const buf = utils.allocBufferUnsafe(8);
425 buf.writeDoubleBE(value, 0);
426 return buf;
427 };
428
429 /**
430 * @param {Date|String|Long|Number} value
431 * @private
432 */
433 this.encodeTimestamp = function (value) {
434 const originalValue = value;
435 if (typeof value === 'string') {
436 value = new Date(value);
437 }
438 if (value instanceof Date) {
439 //milliseconds since epoch
440 value = value.getTime();
441 if (isNaN(value)) {
442 throw new TypeError('Invalid date: ' + originalValue);
443 }
444 }
445 if (this.encodingOptions.useBigIntAsLong) {
446 value = BigInt(value);
447 }
448 return this.encodeLong(value);
449 };
450 /**
451 * @param {Date|String|LocalDate} value
452 * @returns {Buffer}
453 * @throws {TypeError}
454 * @private
455 */
456 this.encodeDate = function (value) {
457 const originalValue = value;
458 try {
459 if (typeof value === 'string') {
460 value = types.LocalDate.fromString(value);
461 }
462 if (value instanceof Date) {
463 value = types.LocalDate.fromDate(value);
464 }
465 }
466 catch (err) {
467 //Wrap into a TypeError
468 throw new TypeError('LocalDate could not be parsed ' + err);
469 }
470 if (!(value instanceof types.LocalDate)) {
471 throw new TypeError('Expected Date/String/LocalDate, obtained ' + util.inspect(originalValue));
472 }
473 return value.toBuffer();
474 };
475 /**
476 * @param {String|LocalDate} value
477 * @returns {Buffer}
478 * @throws {TypeError}
479 * @private
480 */
481 this.encodeTime = function (value) {
482 const originalValue = value;
483 try {
484 if (typeof value === 'string') {
485 value = types.LocalTime.fromString(value);
486 }
487 }
488 catch (err) {
489 //Wrap into a TypeError
490 throw new TypeError('LocalTime could not be parsed ' + err);
491 }
492 if (!(value instanceof types.LocalTime)) {
493 throw new TypeError('Expected String/LocalTime, obtained ' + util.inspect(originalValue));
494 }
495 return value.toBuffer();
496 };
497 /**
498 * @param {Uuid|String|Buffer} value
499 * @private
500 */
501 this.encodeUuid = function (value) {
502 if (typeof value === 'string') {
503 try {
504 value = types.Uuid.fromString(value).getBuffer();
505 }
506 catch (err) {
507 throw new TypeError(err.message);
508 }
509 } else if (value instanceof types.Uuid) {
510 value = value.getBuffer();
511 } else {
512 throw new TypeError('Not a valid Uuid, expected Uuid/String/Buffer, obtained ' + util.inspect(value));
513 }
514
515 return value;
516 };
517 /**
518 * @param {String|InetAddress|Buffer} value
519 * @returns {Buffer}
520 * @private
521 */
522 this.encodeInet = function (value) {
523 if (typeof value === 'string') {
524 value = types.InetAddress.fromString(value);
525 }
526 if (value instanceof types.InetAddress) {
527 value = value.getBuffer();
528 }
529 if (!(value instanceof Buffer)) {
530 throw new TypeError('Not a valid Inet, expected InetAddress/Buffer, obtained ' + util.inspect(value));
531 }
532 return value;
533 };
534
535 /**
536 * @param {Long|Buffer|String|Number} value
537 * @private
538 */
539 this._encodeBigIntFromLong = function (value) {
540 if (typeof value === 'number') {
541 value = Long.fromNumber(value);
542 } else if (typeof value === 'string') {
543 value = Long.fromString(value);
544 }
545
546 let buf = null;
547
548 if (value instanceof Long) {
549 buf = Long.toBuffer(value);
550 } else if (value instanceof MutableLong) {
551 buf = Long.toBuffer(value.toImmutable());
552 }
553
554 if (buf === null) {
555 throw new TypeError('Not a valid bigint, expected Long/Number/String/Buffer, obtained ' + util.inspect(value));
556 }
557
558 return buf;
559 };
560
561 this._encodeBigIntFromBigInt = function (value) {
562 if (typeof value === 'string') {
563 // All numeric types are supported as strings for historical reasons
564 value = BigInt(value);
565 }
566
567 // eslint-disable-next-line valid-typeof
568 if (typeof value !== 'bigint') {
569 // Only BigInt values are supported
570 throw new TypeError('Not a valid BigInt value, obtained ' + util.inspect(value));
571 }
572
573 const buffer = utils.allocBufferUnsafe(8);
574 buffer.writeUInt32BE(Number(value >> bigInt32) >>> 0, 0);
575 buffer.writeUInt32BE(Number(value & bigInt32BitsOn), 4);
576 return buffer;
577 };
578
579 this.encodeLong = this.encodingOptions.useBigIntAsLong
580 ? this._encodeBigIntFromBigInt
581 : this._encodeBigIntFromLong;
582
583 /**
584 * @param {Integer|Buffer|String|Number} value
585 * @returns {Buffer}
586 * @private
587 */
588 this._encodeVarintFromInteger = function (value) {
589 if (typeof value === 'number') {
590 value = Integer.fromNumber(value);
591 }
592 if (typeof value === 'string') {
593 value = Integer.fromString(value);
594 }
595 let buf = null;
596 if (value instanceof Buffer) {
597 buf = value;
598 }
599 if (value instanceof Integer) {
600 buf = Integer.toBuffer(value);
601 }
602 if (buf === null) {
603 throw new TypeError('Not a valid varint, expected Integer/Number/String/Buffer, obtained ' + util.inspect(value));
604 }
605 return buf;
606 };
607
608 this._encodeVarintFromBigInt = function (value) {
609 if (typeof value === 'string') {
610 // All numeric types are supported as strings for historical reasons
611 value = BigInt(value);
612 }
613
614 // eslint-disable-next-line valid-typeof
615 if (typeof value !== 'bigint') {
616 throw new TypeError('Not a valid varint, expected BigInt, obtained ' + util.inspect(value));
617 }
618
619 if (value === bigInt0) {
620 return buffers.int8Zero;
621
622 }
623 else if (value === bigIntMinus1) {
624 return buffers.int8MaxValue;
625 }
626
627 const parts = [];
628
629 if (value > bigInt0){
630 while (value !== bigInt0) {
631 parts.unshift(Number(value & bigInt8BitsOn));
632 value = value >> bigInt8;
633 }
634
635 if (parts[0] > 0x7f) {
636 // Positive value needs a padding
637 parts.unshift(0);
638 }
639 } else {
640 while (value !== bigIntMinus1) {
641 parts.unshift(Number(value & bigInt8BitsOn));
642 value = value >> bigInt8;
643 }
644
645 if (parts[0] <= 0x7f) {
646 // Negative value needs a padding
647 parts.unshift(0xff);
648 }
649 }
650
651 return utils.allocBufferFromArray(parts);
652 };
653
654 this.encodeVarint = this.encodingOptions.useBigIntAsVarint
655 ? this._encodeVarintFromBigInt
656 : this._encodeVarintFromInteger;
657
658 /**
659 * @param {BigDecimal|Buffer|String|Number} value
660 * @returns {Buffer}
661 * @private
662 */
663 this.encodeDecimal = function (value) {
664 if (typeof value === 'number') {
665 value = BigDecimal.fromNumber(value);
666 } else if (typeof value === 'string') {
667 value = BigDecimal.fromString(value);
668 }
669
670 let buf = null;
671
672 if (value instanceof BigDecimal) {
673 buf = BigDecimal.toBuffer(value);
674 } else {
675 throw new TypeError('Not a valid varint, expected BigDecimal/Number/String/Buffer, obtained ' + util.inspect(value));
676 }
677
678 return buf;
679 };
680 this.encodeString = function (value, encoding) {
681 if (typeof value !== 'string') {
682 throw new TypeError('Not a valid text value, expected String obtained ' + util.inspect(value));
683 }
684 return utils.allocBufferFromString(value, encoding);
685 };
686 this.encodeUtf8String = function (value) {
687 return this.encodeString(value, 'utf8');
688 };
689 this.encodeAsciiString = function (value) {
690 return this.encodeString(value, 'ascii');
691 };
692 this.encodeBlob = function (value) {
693 if (!(value instanceof Buffer)) {
694 throw new TypeError('Not a valid blob, expected Buffer obtained ' + util.inspect(value));
695 }
696 return value;
697 };
698 this.encodeCustom = function (value, name) {
699 const handler = customEncoders[name];
700 if (handler) {
701 return handler.call(this, value);
702 }
703 throw new TypeError('No encoding handler found for type ' + name);
704 };
705 /**
706 * @param {Boolean} value
707 * @returns {Buffer}
708 * @private
709 */
710 this.encodeBoolean = function (value) {
711 return value ? buffers.int8One : buffers.int8Zero;
712 };
713 /**
714 * @param {Number|String} value
715 * @private
716 */
717 this.encodeInt = function (value) {
718 if (isNaN(value)) {
719 throw new TypeError('Expected Number, obtained ' + util.inspect(value));
720 }
721 const buf = utils.allocBufferUnsafe(4);
722 buf.writeInt32BE(value, 0);
723 return buf;
724 };
725 /**
726 * @param {Number|String} value
727 * @private
728 */
729 this.encodeSmallint = function (value) {
730 if (isNaN(value)) {
731 throw new TypeError('Expected Number, obtained ' + util.inspect(value));
732 }
733 const buf = utils.allocBufferUnsafe(2);
734 buf.writeInt16BE(value, 0);
735 return buf;
736 };
737 /**
738 * @param {Number|String} value
739 * @private
740 */
741 this.encodeTinyint = function (value) {
742 if (isNaN(value)) {
743 throw new TypeError('Expected Number, obtained ' + util.inspect(value));
744 }
745 const buf = utils.allocBufferUnsafe(1);
746 buf.writeInt8(value, 0);
747 return buf;
748 };
749 this.encodeList = function (value, subtype) {
750 if (!Array.isArray(value)) {
751 throw new TypeError('Not a valid list value, expected Array obtained ' + util.inspect(value));
752 }
753 if (value.length === 0) {
754 return null;
755 }
756 const parts = [];
757 parts.push(this.getLengthBuffer(value));
758 for (let i = 0;i < value.length;i++) {
759 const val = value[i];
760 if (val === null || typeof val === 'undefined' || val === types.unset) {
761 throw new TypeError('A collection can\'t contain null or unset values');
762 }
763 const bytes = this.encode(val, subtype);
764 //include item byte length
765 parts.push(this.getLengthBuffer(bytes));
766 //include item
767 parts.push(bytes);
768 }
769 return Buffer.concat(parts);
770 };
771 this.encodeSet = function (value, subtype) {
772 if (this.encodingOptions.set && value instanceof this.encodingOptions.set) {
773 const arr = [];
774 value.forEach(function (x) {
775 arr.push(x);
776 });
777 return this.encodeList(arr, subtype);
778 }
779 return this.encodeList(value, subtype);
780 };
781 /**
782 * Serializes a map into a Buffer
783 * @param value
784 * @param {Array} [subtypes]
785 * @returns {Buffer}
786 * @private
787 */
788 this.encodeMap = function (value, subtypes) {
789 const parts = [];
790 let propCounter = 0;
791 let keySubtype = null;
792 let valueSubtype = null;
793 const self = this;
794 if (subtypes) {
795 keySubtype = subtypes[0];
796 valueSubtype = subtypes[1];
797 }
798 function addItem(val, key) {
799 if (key === null || typeof key === 'undefined' || key === types.unset) {
800 throw new TypeError('A map can\'t contain null or unset keys');
801 }
802 if (val === null || typeof val === 'undefined' || val === types.unset) {
803 throw new TypeError('A map can\'t contain null or unset values');
804 }
805 const keyBuffer = self.encode(key, keySubtype);
806 //include item byte length
807 parts.push(self.getLengthBuffer(keyBuffer));
808 //include item
809 parts.push(keyBuffer);
810 //value
811 const valueBuffer = self.encode(val, valueSubtype);
812 //include item byte length
813 parts.push(self.getLengthBuffer(valueBuffer));
814 //include item
815 if (valueBuffer !== null) {
816 parts.push(valueBuffer);
817 }
818 propCounter++;
819 }
820 if (this.encodingOptions.map && value instanceof this.encodingOptions.map) {
821 //Use Map#forEach() method to iterate
822 value.forEach(addItem);
823 }
824 else {
825 //Use object
826 for (const key in value) {
827 if (!value.hasOwnProperty(key)) {
828 continue;
829 }
830 const val = value[key];
831 addItem(val, key);
832 }
833 }
834
835 parts.unshift(this.getLengthBuffer(propCounter));
836 return Buffer.concat(parts);
837 };
838 this.encodeUdt = function (value, udtInfo) {
839 const parts = [];
840 let totalLength = 0;
841 for (let i = 0; i < udtInfo.fields.length; i++) {
842 const field = udtInfo.fields[i];
843 const item = this.encode(value[field.name], field.type);
844 if (!item) {
845 parts.push(nullValueBuffer);
846 totalLength += 4;
847 continue;
848 }
849 if (item === types.unset) {
850 parts.push(unsetValueBuffer);
851 totalLength += 4;
852 continue;
853 }
854 const lengthBuffer = utils.allocBufferUnsafe(4);
855 lengthBuffer.writeInt32BE(item.length, 0);
856 parts.push(lengthBuffer);
857 parts.push(item);
858 totalLength += item.length + 4;
859 }
860 return Buffer.concat(parts, totalLength);
861 };
862 this.encodeTuple = function (value, tupleInfo) {
863 const parts = [];
864 let totalLength = 0;
865 const length = Math.min(tupleInfo.length, value.length);
866
867 for (let i = 0; i < length; i++) {
868 const type = tupleInfo[i];
869 const item = this.encode(value.get(i), type);
870
871 if (!item) {
872 parts.push(nullValueBuffer);
873 totalLength += 4;
874 continue;
875 }
876
877 if (item === types.unset) {
878 parts.push(unsetValueBuffer);
879 totalLength += 4;
880 continue;
881 }
882
883 const lengthBuffer = utils.allocBufferUnsafe(4);
884 lengthBuffer.writeInt32BE(item.length, 0);
885 parts.push(lengthBuffer);
886 parts.push(item);
887 totalLength += item.length + 4;
888 }
889
890 return Buffer.concat(parts, totalLength);
891 };
892
893 /**
894 * If not provided, it uses the array of buffers or the parameters and hints to build the routingKey
895 * @param {Array} params
896 * @param {ExecutionOptions} execOptions
897 * @param [keys] parameter keys and positions in the params array
898 * @throws TypeError
899 * @internal
900 * @ignore
901 */
902 this.setRoutingKeyFromUser = function (params, execOptions, keys) {
903 let totalLength = 0;
904 const userRoutingKey = execOptions.getRoutingKey();
905 if (Array.isArray(userRoutingKey)) {
906 if (userRoutingKey.length === 1) {
907 execOptions.setRoutingKey(userRoutingKey[0]);
908 return;
909 }
910
911 // Its a composite routing key
912 totalLength = 0;
913 for (let i = 0; i < userRoutingKey.length; i++) {
914 const item = userRoutingKey[i];
915 if (!item) {
916 // Invalid routing key part provided by the user, clear the value
917 execOptions.setRoutingKey(null);
918 return;
919 }
920 totalLength += item.length + 3;
921 }
922
923 execOptions.setRoutingKey(concatRoutingKey(userRoutingKey, totalLength));
924 return;
925 }
926 // If routingKey is present, ensure it is a Buffer, Token, or TokenRange. Otherwise throw an error.
927 if (userRoutingKey) {
928 if (userRoutingKey instanceof Buffer || userRoutingKey instanceof token.Token
929 || userRoutingKey instanceof token.TokenRange) {
930 return;
931 }
932
933 throw new TypeError(`Unexpected routingKey '${util.inspect(userRoutingKey)}' provided. ` +
934 `Expected Buffer, Array<Buffer>, Token, or TokenRange.`);
935 }
936
937 // If no params are present, return as routing key cannot be determined.
938 if (!params || params.length === 0) {
939 return;
940 }
941
942 let routingIndexes = execOptions.getRoutingIndexes();
943 if (execOptions.getRoutingNames()) {
944 routingIndexes = execOptions.getRoutingNames().map(k => keys[k]);
945 }
946 if (!routingIndexes) {
947 return;
948 }
949
950 const parts = [];
951 const hints = execOptions.getHints() || utils.emptyArray;
952
953 const encodeParam = !keys ?
954 (i => this.encode(params[i], hints[i])) :
955 (i => this.encode(params[i].value, hints[i]));
956
957 try {
958 totalLength = this._encodeRoutingKeyParts(parts, routingIndexes, encodeParam);
959 } catch (e) {
960 // There was an error encoding a parameter that is part of the routing key,
961 // ignore now to fail afterwards
962 }
963
964 if (totalLength === 0) {
965 return;
966 }
967
968 execOptions.setRoutingKey(concatRoutingKey(parts, totalLength));
969 };
970
971 /**
972 * Sets the routing key in the options based on the prepared statement metadata.
973 * @param {Object} meta Prepared metadata
974 * @param {Array} params Array of parameters
975 * @param {ExecutionOptions} execOptions
976 * @throws TypeError
977 * @internal
978 * @ignore
979 */
980 this.setRoutingKeyFromMeta = function (meta, params, execOptions) {
981 const routingIndexes = execOptions.getRoutingIndexes();
982 if (!routingIndexes) {
983 return;
984 }
985 const parts = new Array(routingIndexes.length);
986 const encodeParam = i => {
987 const columnInfo = meta.columns[i];
988 return this.encode(params[i], columnInfo ? columnInfo.type : null);
989 };
990
991 let totalLength = 0;
992
993 try {
994 totalLength = this._encodeRoutingKeyParts(parts, routingIndexes, encodeParam);
995 } catch (e) {
996 // There was an error encoding a parameter that is part of the routing key,
997 // ignore now to fail afterwards
998 }
999
1000 if (totalLength === 0) {
1001 return;
1002 }
1003
1004 execOptions.setRoutingKey(concatRoutingKey(parts, totalLength));
1005 };
1006
1007 /**
1008 * @param {Array} parts
1009 * @param {Array} routingIndexes
1010 * @param {Function} encodeParam
1011 * @returns {Number} The total length
1012 * @private
1013 */
1014 this._encodeRoutingKeyParts = function (parts, routingIndexes, encodeParam) {
1015 let totalLength = 0;
1016 for (let i = 0; i < routingIndexes.length; i++) {
1017 const paramIndex = routingIndexes[i];
1018 if (paramIndex === undefined) {
1019 // Bad input from the user, ignore
1020 return 0;
1021 }
1022
1023 const item = encodeParam(paramIndex);
1024 if (item === null || item === undefined || item === types.unset) {
1025 // The encoded partition key should an instance of Buffer
1026 // Let it fail later in the pipeline for null/undefined parameter values
1027 return 0;
1028 }
1029
1030 // Per each part of the routing key, 3 extra bytes are needed
1031 totalLength += item.length + 3;
1032 parts[i] = item;
1033 }
1034 return totalLength;
1035 };
1036
1037 /**
1038 * Parses a CQL name string into data type information
1039 * @param {String} keyspace
1040 * @param {String} typeName
1041 * @param {Number} startIndex
1042 * @param {Number|null} length
1043 * @param {Function} udtResolver
1044 * @returns {Promise<{err, info, options}>} callback Callback invoked with err and {{code: number, info: Object|Array|null, options: {frozen: Boolean}}}
1045 * @internal
1046 * @ignore
1047 */
1048 this.parseTypeName = async function (keyspace, typeName, startIndex, length, udtResolver) {
1049 startIndex = startIndex || 0;
1050 if (!length) {
1051 length = typeName.length;
1052 }
1053
1054 const dataType = {
1055 code: 0,
1056 info: null,
1057 options: {
1058 frozen: false
1059 }
1060 };
1061
1062 let innerTypes;
1063
1064 if (typeName.indexOf("'", startIndex) === startIndex) {
1065 //If quoted, this is a custom type.
1066 dataType.info = typeName.substr(startIndex+1, length-2);
1067 return dataType;
1068 }
1069
1070 if (!length) {
1071 length = typeName.length;
1072 }
1073
1074 if (typeName.indexOf(cqlNames.frozen, startIndex) === startIndex) {
1075 //Remove the frozen token
1076 startIndex += cqlNames.frozen.length + 1;
1077 length -= cqlNames.frozen.length + 2;
1078 dataType.options.frozen = true;
1079 }
1080
1081 if (typeName.indexOf(cqlNames.list, startIndex) === startIndex) {
1082 //move cursor across the name and bypass the angle brackets
1083 startIndex += cqlNames.list.length + 1;
1084 length -= cqlNames.list.length + 2;
1085 innerTypes = parseParams(typeName, startIndex, length, '<', '>');
1086
1087 if (innerTypes.length !== 1) {
1088 throw new TypeError('Not a valid type ' + typeName);
1089 }
1090
1091 dataType.code = dataTypes.list;
1092 dataType.info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver);
1093 return dataType;
1094 }
1095
1096 if (typeName.indexOf(cqlNames.set, startIndex) === startIndex) {
1097 //move cursor across the name and bypass the angle brackets
1098 startIndex += cqlNames.set.length + 1;
1099 length -= cqlNames.set.length + 2;
1100 innerTypes = parseParams(typeName, startIndex, length, '<', '>');
1101
1102 if (innerTypes.length !== 1) {
1103 throw new TypeError('Not a valid type ' + typeName);
1104 }
1105
1106 dataType.code = dataTypes.set;
1107 dataType.info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver);
1108 return dataType;
1109 }
1110
1111 if (typeName.indexOf(cqlNames.map, startIndex) === startIndex) {
1112 //move cursor across the name and bypass the angle brackets
1113 startIndex += cqlNames.map.length + 1;
1114 length -= cqlNames.map.length + 2;
1115 innerTypes = parseParams(typeName, startIndex, length, '<', '>');
1116
1117 //It should contain the key and value types
1118 if (innerTypes.length !== 2) {
1119 throw new TypeError('Not a valid type ' + typeName);
1120 }
1121
1122 dataType.code = dataTypes.map;
1123 dataType.info = await this._parseChildTypes(keyspace, innerTypes, udtResolver);
1124 return dataType;
1125 }
1126
1127 if (typeName.indexOf(cqlNames.tuple, startIndex) === startIndex) {
1128 //move cursor across the name and bypass the angle brackets
1129 startIndex += cqlNames.tuple.length + 1;
1130 length -= cqlNames.tuple.length + 2;
1131 innerTypes = parseParams(typeName, startIndex, length, '<', '>');
1132
1133 if (innerTypes.length < 1) {
1134 throw new TypeError('Not a valid type ' + typeName);
1135 }
1136
1137 dataType.code = dataTypes.tuple;
1138 dataType.info = await this._parseChildTypes(keyspace, innerTypes, udtResolver);
1139 return dataType;
1140 }
1141
1142 const quoted = typeName.indexOf('"', startIndex) === startIndex;
1143 if (quoted) {
1144 // Remove quotes
1145 startIndex++;
1146 length -= 2;
1147 }
1148
1149 // Quick check if its a single type
1150 if (startIndex > 0) {
1151 typeName = typeName.substr(startIndex, length);
1152 }
1153
1154 // Un-escape double quotes if quoted.
1155 if (quoted) {
1156 typeName = typeName.replace('""', '"');
1157 }
1158
1159 const typeCode = dataTypes[typeName];
1160 if (typeof typeCode === 'number') {
1161 dataType.code = typeCode;
1162 return dataType;
1163 }
1164
1165 if (typeName === cqlNames.duration) {
1166 dataType.info = customTypeNames.duration;
1167 return dataType;
1168 }
1169
1170 if (typeName === cqlNames.empty) {
1171 // Set as custom
1172 dataType.info = 'empty';
1173 return dataType;
1174 }
1175
1176 const udtInfo = await udtResolver(keyspace, typeName);
1177 if (udtInfo) {
1178 dataType.code = dataTypes.udt;
1179 dataType.info = udtInfo;
1180 return dataType;
1181 }
1182
1183 throw new TypeError('Not a valid type "' + typeName + '"');
1184 };
1185
1186 /**
1187 * @param {String} keyspace
1188 * @param {Array} typeNames
1189 * @param {Function} udtResolver
1190 * @returns {Promise}
1191 * @private
1192 */
1193 this._parseChildTypes = function (keyspace, typeNames, udtResolver) {
1194 return Promise.all(typeNames.map(name => this.parseTypeName(keyspace, name.trim(), 0, null, udtResolver)));
1195 };
1196
1197 /**
1198 * Parses a Cassandra fully-qualified class name string into data type information
1199 * @param {String} typeName
1200 * @param {Number} [startIndex]
1201 * @param {Number} [length]
1202 * @throws TypeError
1203 * @returns {{code: number, info: Object|Array|null, options: {frozen: Boolean, reversed: Boolean}}}
1204 * @internal
1205 * @ignore
1206 */
1207 this.parseFqTypeName = function (typeName, startIndex, length) {
1208 const dataType = {
1209 code: 0,
1210 info: null,
1211 options: {
1212 reversed: false,
1213 frozen: false
1214 }
1215 };
1216 startIndex = startIndex || 0;
1217 let innerTypes;
1218 if (!length) {
1219 length = typeName.length;
1220 }
1221 if (length > complexTypeNames.reversed.length && typeName.indexOf(complexTypeNames.reversed) === startIndex) {
1222 //Remove the reversed token
1223 startIndex += complexTypeNames.reversed.length + 1;
1224 length -= complexTypeNames.reversed.length + 2;
1225 dataType.options.reversed = true;
1226 }
1227 if (length > complexTypeNames.frozen.length &&
1228 typeName.indexOf(complexTypeNames.frozen, startIndex) === startIndex) {
1229 //Remove the frozen token
1230 startIndex += complexTypeNames.frozen.length + 1;
1231 length -= complexTypeNames.frozen.length + 2;
1232 dataType.options.frozen = true;
1233 }
1234 if (typeName === complexTypeNames.empty) {
1235 //set as custom
1236 dataType.info = 'empty';
1237 return dataType;
1238 }
1239 //Quick check if its a single type
1240 if (length <= singleFqTypeNamesLength) {
1241 if (startIndex > 0) {
1242 typeName = typeName.substr(startIndex, length);
1243 }
1244 const typeCode = singleTypeNames[typeName];
1245 if (typeof typeCode === 'number') {
1246 dataType.code = typeCode;
1247 return dataType;
1248 }
1249 throw new TypeError('Not a valid type "' + typeName + '"');
1250 }
1251 if (typeName.indexOf(complexTypeNames.list, startIndex) === startIndex) {
1252 //Its a list
1253 //org.apache.cassandra.db.marshal.ListType(innerType)
1254 //move cursor across the name and bypass the parenthesis
1255 startIndex += complexTypeNames.list.length + 1;
1256 length -= complexTypeNames.list.length + 2;
1257 innerTypes = parseParams(typeName, startIndex, length);
1258 if (innerTypes.length !== 1) {
1259 throw new TypeError('Not a valid type ' + typeName);
1260 }
1261 dataType.code = dataTypes.list;
1262 dataType.info = this.parseFqTypeName(innerTypes[0]);
1263 return dataType;
1264 }
1265 if (typeName.indexOf(complexTypeNames.set, startIndex) === startIndex) {
1266 //Its a set
1267 //org.apache.cassandra.db.marshal.SetType(innerType)
1268 //move cursor across the name and bypass the parenthesis
1269 startIndex += complexTypeNames.set.length + 1;
1270 length -= complexTypeNames.set.length + 2;
1271 innerTypes = parseParams(typeName, startIndex, length);
1272 if (innerTypes.length !== 1)
1273 {
1274 throw new TypeError('Not a valid type ' + typeName);
1275 }
1276 dataType.code = dataTypes.set;
1277 dataType.info = this.parseFqTypeName(innerTypes[0]);
1278 return dataType;
1279 }
1280 if (typeName.indexOf(complexTypeNames.map, startIndex) === startIndex) {
1281 //org.apache.cassandra.db.marshal.MapType(keyType,valueType)
1282 //move cursor across the name and bypass the parenthesis
1283 startIndex += complexTypeNames.map.length + 1;
1284 length -= complexTypeNames.map.length + 2;
1285 innerTypes = parseParams(typeName, startIndex, length);
1286 //It should contain the key and value types
1287 if (innerTypes.length !== 2) {
1288 throw new TypeError('Not a valid type ' + typeName);
1289 }
1290 dataType.code = dataTypes.map;
1291 dataType.info = [this.parseFqTypeName(innerTypes[0]), this.parseFqTypeName(innerTypes[1])];
1292 return dataType;
1293 }
1294 if (typeName.indexOf(complexTypeNames.udt, startIndex) === startIndex) {
1295 //move cursor across the name and bypass the parenthesis
1296 startIndex += complexTypeNames.udt.length + 1;
1297 length -= complexTypeNames.udt.length + 2;
1298 return this._parseUdtName(typeName, startIndex, length);
1299 }
1300 if (typeName.indexOf(complexTypeNames.tuple, startIndex) === startIndex) {
1301 //move cursor across the name and bypass the parenthesis
1302 startIndex += complexTypeNames.tuple.length + 1;
1303 length -= complexTypeNames.tuple.length + 2;
1304 innerTypes = parseParams(typeName, startIndex, length);
1305 if (innerTypes.length < 1) {
1306 throw new TypeError('Not a valid type ' + typeName);
1307 }
1308 dataType.code = dataTypes.tuple;
1309 dataType.info = innerTypes.map(x => this.parseFqTypeName(x));
1310 return dataType;
1311 }
1312
1313 // Assume custom type if cannot be parsed up to this point.
1314 dataType.info = typeName.substr(startIndex, length);
1315 return dataType;
1316 };
1317 /**
1318 * Parses type names with composites
1319 * @param {String} typesString
1320 * @returns {{types: Array, isComposite: Boolean, hasCollections: Boolean}}
1321 * @internal
1322 * @ignore
1323 */
1324 this.parseKeyTypes = function (typesString) {
1325 let i = 0;
1326 let length = typesString.length;
1327 const isComposite = typesString.indexOf(complexTypeNames.composite) === 0;
1328 if (isComposite) {
1329 i = complexTypeNames.composite.length + 1;
1330 length--;
1331 }
1332 const types = [];
1333 let startIndex = i;
1334 let nested = 0;
1335 let inCollectionType = false;
1336 let hasCollections = false;
1337 //as collection types are not allowed, it is safe to split by ,
1338 while (++i < length) {
1339 switch (typesString[i]) {
1340 case ',':
1341 if (nested > 0) {
1342 break;
1343 }
1344 if (inCollectionType) {
1345 //remove type id
1346 startIndex = typesString.indexOf(':', startIndex) + 1;
1347 }
1348 types.push(typesString.substring(startIndex, i));
1349 startIndex = i + 1;
1350 break;
1351 case '(':
1352 if (nested === 0 && typesString.indexOf(complexTypeNames.collection, startIndex) === startIndex) {
1353 inCollectionType = true;
1354 hasCollections = true;
1355 //skip collection type
1356 i++;
1357 startIndex = i;
1358 break;
1359 }
1360 nested++;
1361 break;
1362 case ')':
1363 if (inCollectionType && nested === 0){
1364 types.push(typesString.substring(typesString.indexOf(':', startIndex) + 1, i));
1365 startIndex = i + 1;
1366 break;
1367 }
1368 nested--;
1369 break;
1370 }
1371 }
1372 if (startIndex < length) {
1373 types.push(typesString.substring(startIndex, length));
1374 }
1375 return {
1376 types: types.map(name => this.parseFqTypeName(name)),
1377 hasCollections: hasCollections,
1378 isComposite: isComposite
1379 };
1380 };
1381 this._parseUdtName = function (typeName, startIndex, length) {
1382 const udtParams = parseParams(typeName, startIndex, length);
1383 if (udtParams.length < 2) {
1384 //It should contain at least the keyspace, name of the udt and a type
1385 throw new TypeError('Not a valid type ' + typeName);
1386 }
1387 const dataType = {
1388 code: dataTypes.udt,
1389 info: null
1390 };
1391 const udtInfo = {
1392 keyspace: udtParams[0],
1393 name: utils.allocBufferFromString(udtParams[1], 'hex').toString(),
1394 fields: []
1395 };
1396 for (let i = 2; i < udtParams.length; i++) {
1397 const p = udtParams[i];
1398 const separatorIndex = p.indexOf(':');
1399 const fieldType = this.parseFqTypeName(p, separatorIndex + 1, p.length - (separatorIndex + 1));
1400 udtInfo.fields.push({
1401 name: utils.allocBufferFromString(p.substr(0, separatorIndex), 'hex').toString(),
1402 type: fieldType
1403 });
1404 }
1405 dataType.info = udtInfo;
1406 return dataType;
1407 };
1408}
1409
1410/**
1411 * Sets the encoder and decoder methods for this instance
1412 * @private
1413 */
1414function setEncoders() {
1415 this.decoders = {
1416 [dataTypes.custom]: this.decodeCustom,
1417 [dataTypes.ascii]: this.decodeAsciiString,
1418 [dataTypes.bigint]: this.decodeLong,
1419 [dataTypes.blob]: this.decodeBlob,
1420 [dataTypes.boolean]: this.decodeBoolean,
1421 [dataTypes.counter]: this.decodeLong,
1422 [dataTypes.decimal]: this.decodeDecimal,
1423 [dataTypes.double]: this.decodeDouble,
1424 [dataTypes.float]: this.decodeFloat,
1425 [dataTypes.int]: this.decodeInt,
1426 [dataTypes.text]: this.decodeUtf8String,
1427 [dataTypes.timestamp]: this.decodeTimestamp,
1428 [dataTypes.uuid]: this.decodeUuid,
1429 [dataTypes.varchar]: this.decodeUtf8String,
1430 [dataTypes.varint]: this.decodeVarint,
1431 [dataTypes.timeuuid]: this.decodeTimeUuid,
1432 [dataTypes.inet]: this.decodeInet,
1433 [dataTypes.date]: this.decodeDate,
1434 [dataTypes.time]: this.decodeTime,
1435 [dataTypes.smallint]: this.decodeSmallint,
1436 [dataTypes.tinyint]: this.decodeTinyint,
1437 [dataTypes.duration]: decodeDuration,
1438 [dataTypes.list]: this.decodeList,
1439 [dataTypes.map]: this.decodeMap,
1440 [dataTypes.set]: this.decodeSet,
1441 [dataTypes.udt]: this.decodeUdt,
1442 [dataTypes.tuple]: this.decodeTuple
1443 };
1444
1445 this.encoders = {
1446 [dataTypes.custom]: this.encodeCustom,
1447 [dataTypes.ascii]: this.encodeAsciiString,
1448 [dataTypes.bigint]: this.encodeLong,
1449 [dataTypes.blob]: this.encodeBlob,
1450 [dataTypes.boolean]: this.encodeBoolean,
1451 [dataTypes.counter]: this.encodeLong,
1452 [dataTypes.decimal]: this.encodeDecimal,
1453 [dataTypes.double]: this.encodeDouble,
1454 [dataTypes.float]: this.encodeFloat,
1455 [dataTypes.int]: this.encodeInt,
1456 [dataTypes.text]: this.encodeUtf8String,
1457 [dataTypes.timestamp]: this.encodeTimestamp,
1458 [dataTypes.uuid]: this.encodeUuid,
1459 [dataTypes.varchar]: this.encodeUtf8String,
1460 [dataTypes.varint]: this.encodeVarint,
1461 [dataTypes.timeuuid]: this.encodeUuid,
1462 [dataTypes.inet]: this.encodeInet,
1463 [dataTypes.date]: this.encodeDate,
1464 [dataTypes.time]: this.encodeTime,
1465 [dataTypes.smallint]: this.encodeSmallint,
1466 [dataTypes.tinyint]: this.encodeTinyint,
1467 [dataTypes.duration]: encodeDuration,
1468 [dataTypes.list]: this.encodeList,
1469 [dataTypes.map]: this.encodeMap,
1470 [dataTypes.set]: this.encodeSet,
1471 [dataTypes.udt]: this.encodeUdt,
1472 [dataTypes.tuple]: this.encodeTuple
1473 };
1474}
1475
1476/**
1477 * Decodes Cassandra bytes into Javascript values.
1478 * <p>
1479 * This is part of an <b>experimental</b> API, this can be changed future releases.
1480 * </p>
1481 * @param {Buffer} buffer Raw buffer to be decoded.
1482 * @param {Object} type An object containing the data type <code>code</code> and <code>info</code>.
1483 * @param {Number} type.code Type code.
1484 * @param {Object} [type.info] Additional information on the type for complex / nested types.
1485 */
1486Encoder.prototype.decode = function (buffer, type) {
1487 if (buffer === null || (buffer.length === 0 && !zeroLengthTypesSupported.has(type.code))) {
1488 return null;
1489 }
1490
1491 const decoder = this.decoders[type.code];
1492
1493 if (!decoder) {
1494 throw new Error('Unknown data type: ' + type.code);
1495 }
1496
1497 return decoder.call(this, buffer, type.info);
1498};
1499
1500/**
1501 * Encodes Javascript types into Buffer according to the Cassandra protocol.
1502 * <p>
1503 * This is part of an <b>experimental</b> API, this can be changed future releases.
1504 * </p>
1505 * @param {*} value The value to be converted.
1506 * @param {{code: number, info: *|Object}|String|Number} [typeInfo] The type information.
1507 * <p>It can be either a:</p>
1508 * <ul>
1509 * <li>A <code>String</code> representing the data type.</li>
1510 * <li>A <code>Number</code> with one of the values of {@link module:types~dataTypes dataTypes}.</li>
1511 * <li>An <code>Object</code> containing the <code>type.code</code> as one of the values of
1512 * {@link module:types~dataTypes dataTypes} and <code>type.info</code>.
1513 * </li>
1514 * </ul>
1515 * @returns {Buffer}
1516 * @throws {TypeError} When there is an encoding error
1517 */
1518Encoder.prototype.encode = function (value, typeInfo) {
1519 if (value === undefined) {
1520 value = this.encodingOptions.useUndefinedAsUnset && this.protocolVersion >= 4 ? types.unset : null;
1521 }
1522
1523 if (value === types.unset) {
1524 if (!types.protocolVersion.supportsUnset(this.protocolVersion)) {
1525 throw new TypeError('Unset value can not be used for this version of Cassandra, protocol version: ' +
1526 this.protocolVersion);
1527 }
1528
1529 return value;
1530 }
1531
1532 if (value === null || value instanceof Buffer) {
1533 return value;
1534 }
1535
1536 /** @type {{code: Number, info: object}} */
1537 let type = {
1538 code: null,
1539 info: null
1540 };
1541
1542 if (typeInfo) {
1543 if (typeof typeInfo === 'number') {
1544 type.code = typeInfo;
1545 }
1546 else if (typeof typeInfo === 'string') {
1547 type = dataTypes.getByName(typeInfo);
1548 }
1549 if (typeof typeInfo.code === 'number') {
1550 type.code = typeInfo.code;
1551 type.info = typeInfo.info;
1552 }
1553 if (typeof type.code !== 'number') {
1554 throw new TypeError('Type information not valid, only String and Number values are valid hints');
1555 }
1556 }
1557 else {
1558 //Lets guess
1559 type = Encoder.guessDataType(value);
1560 if (!type) {
1561 throw new TypeError('Target data type could not be guessed, you should use prepared statements for accurate type mapping. Value: ' + util.inspect(value));
1562 }
1563 }
1564
1565 const encoder = this.encoders[type.code];
1566
1567 if (!encoder) {
1568 throw new Error('Type not supported ' + type.code);
1569 }
1570
1571 return encoder.call(this, value, type.info);
1572};
1573
1574/**
1575 * Try to guess the Cassandra type to be stored, based on the javascript value type
1576 * @param value
1577 * @returns {{code: number, info: object}|null}
1578 * @ignore
1579 * @internal
1580 */
1581Encoder.guessDataType = function (value) {
1582 let code = null;
1583 let info = null;
1584 const esTypeName = (typeof value);
1585 if (esTypeName === 'number') {
1586 code = dataTypes.double;
1587 }
1588 else if (esTypeName === 'string') {
1589 code = dataTypes.text;
1590 if (value.length === 36 && uuidRegex.test(value)){
1591 code = dataTypes.uuid;
1592 }
1593 }
1594 else if (esTypeName === 'boolean') {
1595 code = dataTypes.boolean;
1596 }
1597 else if (value instanceof Buffer) {
1598 code = dataTypes.blob;
1599 }
1600 else if (value instanceof Date) {
1601 code = dataTypes.timestamp;
1602 }
1603 else if (value instanceof Long) {
1604 code = dataTypes.bigint;
1605 }
1606 else if (value instanceof Integer) {
1607 code = dataTypes.varint;
1608 }
1609 else if (value instanceof BigDecimal) {
1610 code = dataTypes.decimal;
1611 }
1612 else if (value instanceof types.Uuid) {
1613 code = dataTypes.uuid;
1614 }
1615 else if (value instanceof types.InetAddress) {
1616 code = dataTypes.inet;
1617 }
1618 else if (value instanceof types.Tuple) {
1619 code = dataTypes.tuple;
1620 }
1621 else if (value instanceof types.LocalDate) {
1622 code = dataTypes.date;
1623 }
1624 else if (value instanceof types.LocalTime) {
1625 code = dataTypes.time;
1626 }
1627 else if (value instanceof types.Duration) {
1628 code = dataTypes.custom;
1629 info = customTypeNames.duration;
1630 }
1631 else if (Array.isArray(value)) {
1632 code = dataTypes.list;
1633 }
1634 else if (value instanceof Geometry) {
1635 code = dataTypes.custom;
1636 if (value instanceof LineString) {
1637 info = customTypeNames.lineString;
1638 } else if (value instanceof Point) {
1639 info = customTypeNames.point;
1640 } else if (value instanceof Polygon) {
1641 info = customTypeNames.polygon;
1642 }
1643 }
1644 else if (value instanceof DateRange) {
1645 code = dataTypes.custom;
1646 info = customTypeNames.dateRange;
1647 }
1648
1649 if (code === null) {
1650 return null;
1651 }
1652 return { code: code, info: info };
1653};
1654
1655/**
1656 * Gets a buffer containing with the bytes (BE) representing the collection length for protocol v2 and below
1657 * @param {Buffer|Number} value
1658 * @returns {Buffer}
1659 * @private
1660 */
1661function getLengthBufferV2(value) {
1662 if (!value) {
1663 return buffers.int16Zero;
1664 }
1665 const lengthBuffer = utils.allocBufferUnsafe(2);
1666 if (typeof value === 'number') {
1667 lengthBuffer.writeUInt16BE(value, 0);
1668 }
1669 else {
1670 lengthBuffer.writeUInt16BE(value.length, 0);
1671 }
1672 return lengthBuffer;
1673}
1674
1675/**
1676 * Gets a buffer containing with the bytes (BE) representing the collection length for protocol v3 and above
1677 * @param {Buffer|Number} value
1678 * @returns {Buffer}
1679 * @private
1680 */
1681function getLengthBufferV3(value) {
1682 if (!value) {
1683 return buffers.int32Zero;
1684 }
1685 const lengthBuffer = utils.allocBufferUnsafe(4);
1686 if (typeof value === 'number') {
1687 lengthBuffer.writeInt32BE(value, 0);
1688 }
1689 else {
1690 lengthBuffer.writeInt32BE(value.length, 0);
1691 }
1692 return lengthBuffer;
1693}
1694
1695/**
1696 * @param {Buffer} buffer
1697 * @private
1698 */
1699function handleBufferCopy(buffer) {
1700 if (buffer === null) {
1701 return null;
1702 }
1703 return utils.copyBuffer(buffer);
1704}
1705
1706/**
1707 * @param {Buffer} buffer
1708 * @private
1709 */
1710function handleBufferRef(buffer) {
1711 return buffer;
1712}
1713/**
1714 * Decodes collection length for protocol v3 and above
1715 * @param bytes
1716 * @param offset
1717 * @returns {Number}
1718 * @private
1719 */
1720function decodeCollectionLengthV3(bytes, offset) {
1721 return bytes.readInt32BE(offset);
1722}
1723/**
1724 * Decodes collection length for protocol v2 and below
1725 * @param bytes
1726 * @param offset
1727 * @returns {Number}
1728 * @private
1729 */
1730function decodeCollectionLengthV2(bytes, offset) {
1731 return bytes.readUInt16BE(offset);
1732}
1733
1734function decodeDuration(bytes) {
1735 return types.Duration.fromBuffer(bytes);
1736}
1737
1738function encodeDuration(value) {
1739 if (!(value instanceof types.Duration)) {
1740 throw new TypeError('Not a valid duration, expected Duration/Buffer obtained ' + util.inspect(value));
1741 }
1742 return value.toBuffer();
1743}
1744
1745/**
1746 * @private
1747 * @param {Buffer} buffer
1748 */
1749function decodeLineString(buffer) {
1750 return LineString.fromBuffer(buffer);
1751}
1752
1753/**
1754 * @private
1755 * @param {LineString} value
1756 */
1757function encodeLineString(value) {
1758 return value.toBuffer();
1759}
1760
1761/**
1762 * @private
1763 * @param {Buffer} buffer
1764 */
1765function decodePoint(buffer) {
1766 return Point.fromBuffer(buffer);
1767}
1768
1769/**
1770 * @private
1771 * @param {LineString} value
1772 */
1773function encodePoint(value) {
1774 return value.toBuffer();
1775}
1776
1777/**
1778 * @private
1779 * @param {Buffer} buffer
1780 */
1781function decodePolygon(buffer) {
1782 return Polygon.fromBuffer(buffer);
1783}
1784
1785/**
1786 * @private
1787 * @param {Polygon} value
1788 */
1789function encodePolygon(value) {
1790 return value.toBuffer();
1791}
1792
1793function decodeDateRange(buffer) {
1794 return DateRange.fromBuffer(buffer);
1795}
1796
1797/**
1798 * @private
1799 * @param {DateRange} value
1800 */
1801function encodeDateRange(value) {
1802 return value.toBuffer();
1803}
1804
1805/**
1806 * @param {String} value
1807 * @param {Number} startIndex
1808 * @param {Number} length
1809 * @param {String} [open]
1810 * @param {String} [close]
1811 * @returns {Array}
1812 * @private
1813 */
1814function parseParams(value, startIndex, length, open, close) {
1815 open = open || '(';
1816 close = close || ')';
1817 const types = [];
1818 let paramStart = startIndex;
1819 let level = 0;
1820 for (let i = startIndex; i < startIndex + length; i++) {
1821 const c = value[i];
1822 if (c === open) {
1823 level++;
1824 }
1825 if (c === close) {
1826 level--;
1827 }
1828 if (level === 0 && c === ',') {
1829 types.push(value.substr(paramStart, i - paramStart));
1830 paramStart = i + 1;
1831 }
1832 }
1833 //Add the last one
1834 types.push(value.substr(paramStart, length - (paramStart - startIndex)));
1835 return types;
1836}
1837
1838/**
1839 * @param {Array.<Buffer>} parts
1840 * @param {Number} totalLength
1841 * @returns {Buffer}
1842 * @private
1843 */
1844function concatRoutingKey(parts, totalLength) {
1845 if (totalLength === 0) {
1846 return null;
1847 }
1848 if (parts.length === 1) {
1849 return parts[0];
1850 }
1851 const routingKey = utils.allocBufferUnsafe(totalLength);
1852 let offset = 0;
1853 for (let i = 0; i < parts.length; i++) {
1854 const item = parts[i];
1855 routingKey.writeUInt16BE(item.length, offset);
1856 offset += 2;
1857 item.copy(routingKey, offset);
1858 offset += item.length;
1859 routingKey[offset] = 0;
1860 offset++;
1861 }
1862 return routingKey;
1863}
1864
1865module.exports = Encoder;