src/internal/BinaryUtils.ts (642 lines of code) (raw):

/* * 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'; import { CollectionObjectType, ComplexObjectType, CompositeType, MapObjectType, ObjectArrayType, ObjectType } from "../ObjectType"; import {Timestamp} from "../Timestamp"; import {EnumItem} from "../EnumItem"; const Decimal = require('decimal.js').default; import ArgumentChecker from "./ArgumentChecker"; import { IgniteClientError } from "../Errors"; import { PRIMITIVE_TYPE, COMPOSITE_TYPE } from "./Constants"; import {BinaryObject} from "../BinaryObject"; import * as Long from "long"; // Operation codes export enum OPERATION { // Key-Value Queries CACHE_GET = 1000, CACHE_PUT = 1001, CACHE_PUT_IF_ABSENT = 1002, CACHE_GET_ALL = 1003, CACHE_PUT_ALL = 1004, CACHE_GET_AND_PUT = 1005, CACHE_GET_AND_REPLACE = 1006, CACHE_GET_AND_REMOVE = 1007, CACHE_GET_AND_PUT_IF_ABSENT = 1008, CACHE_REPLACE = 1009, CACHE_REPLACE_IF_EQUALS = 1010, CACHE_CONTAINS_KEY = 1011, CACHE_CONTAINS_KEYS = 1012, CACHE_CLEAR = 1013, CACHE_CLEAR_KEY = 1014, CACHE_CLEAR_KEYS = 1015, CACHE_REMOVE_KEY = 1016, CACHE_REMOVE_IF_EQUALS = 1017, CACHE_REMOVE_KEYS = 1018, CACHE_REMOVE_ALL = 1019, CACHE_GET_SIZE = 1020, CACHE_LOCAL_PEEK = 1021, // Cache Configuration CACHE_GET_NAMES = 1050, CACHE_CREATE_WITH_NAME = 1051, CACHE_GET_OR_CREATE_WITH_NAME = 1052, CACHE_CREATE_WITH_CONFIGURATION = 1053, CACHE_GET_OR_CREATE_WITH_CONFIGURATION = 1054, CACHE_GET_CONFIGURATION = 1055, CACHE_DESTROY = 1056, CACHE_PARTITIONS = 1101, // SQL and Scan Queries QUERY_SCAN = 2000, QUERY_SCAN_CURSOR_GET_PAGE = 2001, QUERY_SQL = 2002, QUERY_SQL_CURSOR_GET_PAGE = 2003, QUERY_SQL_FIELDS = 2004, QUERY_SQL_FIELDS_CURSOR_GET_PAGE = 2005, RESOURCE_CLOSE = 0, // Binary Types GET_BINARY_TYPE = 3002, PUT_BINARY_TYPE = 3003 } export const TYPE_CODE = { ...PRIMITIVE_TYPE, ...COMPOSITE_TYPE, BINARY_OBJECT: 27, BINARY_ENUM: 38 } const TYPE_INFO = Object.freeze({ [TYPE_CODE.BYTE] : { NAME : 'byte', SIZE : 1 }, [TYPE_CODE.SHORT] : { NAME : 'short', SIZE : 2 }, [TYPE_CODE.INTEGER] : { NAME : 'integer', SIZE : 4 }, [TYPE_CODE.LONG] : { NAME : 'long', SIZE : 8 }, [TYPE_CODE.FLOAT] : { NAME : 'float', SIZE : 4 }, [TYPE_CODE.DOUBLE] : { NAME : 'double', SIZE : 8 }, [TYPE_CODE.CHAR] : { NAME : 'char', SIZE : 2 }, [TYPE_CODE.BOOLEAN] : { NAME : 'boolean', SIZE : 1 }, [TYPE_CODE.STRING] : { NAME : 'string', NULLABLE : true }, [TYPE_CODE.UUID] : { NAME : 'UUID', SIZE : 16, NULLABLE : true }, [TYPE_CODE.DATE] : { NAME : 'date', SIZE : 8, NULLABLE : true }, [TYPE_CODE.BYTE_ARRAY] : { NAME : 'byte array', ELEMENT_TYPE : TYPE_CODE.BYTE, NULLABLE : true }, [TYPE_CODE.SHORT_ARRAY] : { NAME : 'short array', ELEMENT_TYPE : TYPE_CODE.SHORT, NULLABLE : true }, [TYPE_CODE.INTEGER_ARRAY] : { NAME : 'integer array', ELEMENT_TYPE : TYPE_CODE.INTEGER, NULLABLE : true }, [TYPE_CODE.LONG_ARRAY] : { NAME : 'long array', ELEMENT_TYPE : TYPE_CODE.LONG, NULLABLE : true }, [TYPE_CODE.FLOAT_ARRAY] : { NAME : 'float array', ELEMENT_TYPE : TYPE_CODE.FLOAT, NULLABLE : true }, [TYPE_CODE.DOUBLE_ARRAY] : { NAME : 'double array', ELEMENT_TYPE : TYPE_CODE.DOUBLE, NULLABLE : true }, [TYPE_CODE.CHAR_ARRAY] : { NAME : 'char array', ELEMENT_TYPE : TYPE_CODE.CHAR, NULLABLE : true }, [TYPE_CODE.BOOLEAN_ARRAY] : { NAME : 'boolean array', ELEMENT_TYPE : TYPE_CODE.BOOLEAN, NULLABLE : true }, [TYPE_CODE.STRING_ARRAY] : { NAME : 'string array', ELEMENT_TYPE : TYPE_CODE.STRING, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.DATE_ARRAY] : { NAME : 'date array', ELEMENT_TYPE : TYPE_CODE.DATE, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.UUID_ARRAY] : { NAME : 'UUID array', ELEMENT_TYPE : TYPE_CODE.UUID, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.OBJECT_ARRAY] : { NAME : 'object array', ELEMENT_TYPE : TYPE_CODE.COMPLEX_OBJECT, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.COLLECTION] : { NAME : 'collection', NULLABLE : true }, [TYPE_CODE.MAP] : { NAME : 'map', NULLABLE : true }, [TYPE_CODE.BINARY_OBJECT] : { NAME : 'BinaryObject', NULLABLE : true }, [TYPE_CODE.ENUM] : { NAME : 'enum', NULLABLE : true }, [TYPE_CODE.ENUM_ARRAY] : { NAME : 'enum array', ELEMENT_TYPE : TYPE_CODE.ENUM, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.DECIMAL] : { NAME : 'decimal', NULLABLE : true }, [TYPE_CODE.DECIMAL_ARRAY] : { NAME : 'decimal array', ELEMENT_TYPE : TYPE_CODE.DECIMAL, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.TIMESTAMP] : { NAME : 'timestamp', NULLABLE : true }, [TYPE_CODE.TIMESTAMP_ARRAY] : { NAME : 'timestamp array', ELEMENT_TYPE : TYPE_CODE.TIMESTAMP, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.TIME] : { NAME : 'time', NULLABLE : true }, [TYPE_CODE.TIME_ARRAY] : { NAME : 'time array', ELEMENT_TYPE : TYPE_CODE.TIME, KEEP_ELEMENT_TYPE : true, NULLABLE : true }, [TYPE_CODE.NULL] : { NAME : 'null', NULLABLE : true }, [TYPE_CODE.COMPLEX_OBJECT] : { NAME : 'complex object', NULLABLE : true } }); const UTF8_ENCODING = 'utf8'; export default class BinaryUtils { static get OPERATION() { return OPERATION; } static get TYPE_CODE() { return TYPE_CODE; } static get TYPE_INFO() { return TYPE_INFO; } static getSize(typeCode) { const size = TYPE_INFO[typeCode].SIZE; return size ? size : 0; } static get ENCODING(): BufferEncoding { return UTF8_ENCODING; } static getTypeName(type) { if (typeof type === 'string') { return type; } const typeCode = BinaryUtils.getTypeCode(type); return TYPE_INFO[typeCode] ? TYPE_INFO[typeCode].NAME : 'type code ' + typeCode; } static isNullable(type) { return TYPE_INFO[BinaryUtils.getTypeCode(type)].NULLABLE === true; } static getTypeCode(type: PRIMITIVE_TYPE | CompositeType): number { return type instanceof CompositeType ? type.typeCode : type; } static checkObjectType(type, argName) { if (type === null || type instanceof CompositeType) { return; } ArgumentChecker.hasValueFrom(type, argName, false, ObjectType.PRIMITIVE_TYPE); } static calcObjectType(object): PRIMITIVE_TYPE | CompositeType { const objectType = typeof object; if (object === null) { throw IgniteClientError.unsupportedTypeError(BinaryUtils.TYPE_CODE.NULL); } else if (objectType === 'number') { return BinaryUtils.TYPE_CODE.DOUBLE; } else if (objectType === 'string') { return BinaryUtils.TYPE_CODE.STRING; } else if (objectType === 'boolean') { return BinaryUtils.TYPE_CODE.BOOLEAN; } else if (object instanceof Timestamp) { return BinaryUtils.TYPE_CODE.TIMESTAMP; } else if (object instanceof Date) { return BinaryUtils.TYPE_CODE.DATE; } else if (object instanceof EnumItem) { return BinaryUtils.TYPE_CODE.ENUM; } else if (object instanceof Decimal) { return BinaryUtils.TYPE_CODE.DECIMAL; } else if (object instanceof Array) { if (object.length > 0 && object[0] !== null) { return BinaryUtils.getArrayType(BinaryUtils.calcObjectType(object[0])); } } else if (object instanceof Set) { return new CollectionObjectType(CollectionObjectType.COLLECTION_SUBTYPE.HASH_SET); } else if (object instanceof Map) { return new MapObjectType(); } else if (object instanceof BinaryObject) { return BinaryUtils.TYPE_CODE.BINARY_OBJECT; } else if (objectType === 'object') { return new ComplexObjectType(object); } throw IgniteClientError.unsupportedTypeError(objectType); } static checkCompatibility(value, type) { if (!type) { return; } const typeCode = BinaryUtils.getTypeCode(type); if (value === null) { if (!BinaryUtils.isNullable(typeCode)) { throw IgniteClientError.typeCastError(BinaryUtils.TYPE_CODE.NULL, typeCode); } return; } else if (BinaryUtils.isStandardType(typeCode)) { BinaryUtils.checkStandardTypeCompatibility(value, typeCode, type); return; } const valueTypeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value)); if (typeCode !== valueTypeCode) { throw IgniteClientError.typeCastError(valueTypeCode, typeCode); } } static isStandardType(typeCode) { return typeCode !== BinaryUtils.TYPE_CODE.BINARY_OBJECT && typeCode !== BinaryUtils.TYPE_CODE.COMPLEX_OBJECT; } static checkStandardTypeCompatibility(value, typeCode, type = null) { const valueType = typeof value; switch (typeCode) { case BinaryUtils.TYPE_CODE.BYTE: case BinaryUtils.TYPE_CODE.SHORT: case BinaryUtils.TYPE_CODE.INTEGER: case BinaryUtils.TYPE_CODE.LONG: if (!Number.isInteger(value)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.FLOAT: case BinaryUtils.TYPE_CODE.DOUBLE: if (valueType !== 'number') { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.CHAR: if (valueType !== 'string' || value.length !== 1) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.BOOLEAN: if (valueType !== 'boolean') { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.STRING: if (valueType !== 'string') { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.UUID: if (!(value instanceof Array) || value.length !== BinaryUtils.getSize(BinaryUtils.TYPE_CODE.UUID)) { throw IgniteClientError.valueCastError(value, typeCode); } value.forEach(element => BinaryUtils.checkStandardTypeCompatibility(element, BinaryUtils.TYPE_CODE.BYTE)); return; case BinaryUtils.TYPE_CODE.DATE: if (!(value instanceof Date)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.ENUM: if (!(value instanceof EnumItem)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.DECIMAL: if (!(value instanceof Decimal)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.TIMESTAMP: if (!(value instanceof Timestamp)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.TIME: if (!(value instanceof Date)) { throw IgniteClientError.valueCastError(value, typeCode); } return; case BinaryUtils.TYPE_CODE.BYTE_ARRAY: case BinaryUtils.TYPE_CODE.SHORT_ARRAY: case BinaryUtils.TYPE_CODE.INTEGER_ARRAY: case BinaryUtils.TYPE_CODE.LONG_ARRAY: case BinaryUtils.TYPE_CODE.FLOAT_ARRAY: case BinaryUtils.TYPE_CODE.DOUBLE_ARRAY: case BinaryUtils.TYPE_CODE.CHAR_ARRAY: case BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY: case BinaryUtils.TYPE_CODE.STRING_ARRAY: case BinaryUtils.TYPE_CODE.UUID_ARRAY: case BinaryUtils.TYPE_CODE.DATE_ARRAY: case BinaryUtils.TYPE_CODE.OBJECT_ARRAY: case BinaryUtils.TYPE_CODE.ENUM_ARRAY: case BinaryUtils.TYPE_CODE.DECIMAL_ARRAY: case BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY: case BinaryUtils.TYPE_CODE.TIME_ARRAY: if (!(value instanceof Array)) { throw IgniteClientError.typeCastError(valueType, typeCode); } return; case BinaryUtils.TYPE_CODE.MAP: if (!(value instanceof Map)) { throw IgniteClientError.typeCastError(valueType, typeCode); } return; case BinaryUtils.TYPE_CODE.COLLECTION: if (!(type && type._isSet() && value instanceof Set || value instanceof Array)) { throw IgniteClientError.typeCastError(valueType, type && type._isSet() ? 'set' : typeCode); } return; case BinaryUtils.TYPE_CODE.NULL: if (value !== null) { throw IgniteClientError.typeCastError('not null', typeCode); } return; default: const valueTypeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value)); if (valueTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT) { throw IgniteClientError.typeCastError(valueTypeCode, typeCode); } return; } } static checkTypesComatibility(expectedType, actualTypeCode) { if (expectedType === null) { return; } const expectedTypeCode = BinaryUtils.getTypeCode(expectedType); if (actualTypeCode === BinaryUtils.TYPE_CODE.NULL) { return; } else if (expectedTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT || actualTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT && expectedTypeCode === BinaryUtils.TYPE_CODE.COMPLEX_OBJECT) { return; } else if (expectedTypeCode === BinaryUtils.TYPE_CODE.ENUM && actualTypeCode === BinaryUtils.TYPE_CODE.BINARY_ENUM) { return; } else if (actualTypeCode !== expectedTypeCode) { throw IgniteClientError.typeCastError(actualTypeCode, expectedTypeCode); } } static getArrayElementType(arrayType): PRIMITIVE_TYPE | COMPOSITE_TYPE | CompositeType { if (arrayType instanceof ObjectArrayType) { return arrayType._elementType; } else if (arrayType === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) { return null; } const elementTypeCode: PRIMITIVE_TYPE | COMPOSITE_TYPE = TYPE_INFO[arrayType].ELEMENT_TYPE; if (!elementTypeCode) { throw IgniteClientError.internalError(); } return elementTypeCode; } static getArrayType(elementType) { switch (BinaryUtils.getTypeCode(elementType)) { case BinaryUtils.TYPE_CODE.BYTE: return BinaryUtils.TYPE_CODE.BYTE_ARRAY; case BinaryUtils.TYPE_CODE.SHORT: return BinaryUtils.TYPE_CODE.SHORT_ARRAY; case BinaryUtils.TYPE_CODE.INTEGER: return BinaryUtils.TYPE_CODE.INTEGER_ARRAY; case BinaryUtils.TYPE_CODE.LONG: return BinaryUtils.TYPE_CODE.LONG_ARRAY; case BinaryUtils.TYPE_CODE.FLOAT: return BinaryUtils.TYPE_CODE.FLOAT_ARRAY; case BinaryUtils.TYPE_CODE.DOUBLE: return BinaryUtils.TYPE_CODE.DOUBLE_ARRAY; case BinaryUtils.TYPE_CODE.CHAR: return BinaryUtils.TYPE_CODE.CHAR_ARRAY; case BinaryUtils.TYPE_CODE.BOOLEAN: return BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY; case BinaryUtils.TYPE_CODE.STRING: return BinaryUtils.TYPE_CODE.STRING_ARRAY; case BinaryUtils.TYPE_CODE.UUID: return BinaryUtils.TYPE_CODE.UUID_ARRAY; case BinaryUtils.TYPE_CODE.DATE: return BinaryUtils.TYPE_CODE.DATE_ARRAY; case BinaryUtils.TYPE_CODE.ENUM: return BinaryUtils.TYPE_CODE.ENUM_ARRAY; case BinaryUtils.TYPE_CODE.DECIMAL: return BinaryUtils.TYPE_CODE.DECIMAL_ARRAY; case BinaryUtils.TYPE_CODE.TIMESTAMP: return BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY; case BinaryUtils.TYPE_CODE.TIME: return BinaryUtils.TYPE_CODE.TIME_ARRAY; case BinaryUtils.TYPE_CODE.BINARY_OBJECT: return new ObjectArrayType(); default: return new ObjectArrayType(elementType); } } static keepArrayElementType(arrayTypeCode) { return TYPE_INFO[arrayTypeCode].KEEP_ELEMENT_TYPE === true; } static getJsObjectFieldNames(jsObject) { var fields = []; for (let field in jsObject) { if (typeof jsObject[field] !== 'function') { fields.push(field); } } return fields; } static async hashCode(object, communicator, typeCode = null) { if (typeCode === null) { typeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(object)); } if (BinaryUtils.isStandardType(typeCode)) { return BinaryUtils.standardHashCode(object, typeCode); } else { return await object._getHashCode(communicator); } } // Calculates hash code for an object of a standard type static standardHashCode(object, typeCode = null) { if (typeCode === null) { typeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(object)); } switch (typeCode) { case BinaryUtils.TYPE_CODE.BYTE: case BinaryUtils.TYPE_CODE.SHORT: case BinaryUtils.TYPE_CODE.INTEGER: return this.intHashCode(object); case BinaryUtils.TYPE_CODE.LONG: return this.longHashCode(object); case BinaryUtils.TYPE_CODE.FLOAT: return this.floatHashCode(object); case BinaryUtils.TYPE_CODE.DOUBLE: return this.doubleHashCode(object); case BinaryUtils.TYPE_CODE.CHAR: return this.charHashCode(object); case BinaryUtils.TYPE_CODE.BOOLEAN: return this.boolHashCode(object); case BinaryUtils.TYPE_CODE.STRING: return this.strHashCode(object); case BinaryUtils.TYPE_CODE.UUID: return this.uuidHashCode(object); case BinaryUtils.TYPE_CODE.TIME: return this.timeHashCode(object); case BinaryUtils.TYPE_CODE.DATE: case BinaryUtils.TYPE_CODE.TIMESTAMP: return this.datetimeHashCode(object); default: return 0; } } static contentHashCode(buffer, startPos, endPos) { let hash = 1; for (let i = startPos; i <= endPos; i++) { hash = 31 * hash + buffer._buffer[i]; hash |= 0; // Convert to 32bit integer } return hash; } static strHashCode(str): number { // This method calcuates hash code for the String Ignite type // bool must be a js 'string' let hash = 0, char; if (str && str.length > 0) { for (let i = 0; i < str.length; i++) { char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash |= 0; // Convert to 32bit integer } } return hash; } static strHashCodeLowerCase(str): number { return BinaryUtils.strHashCode(str ? str.toLowerCase() : str); } static charHashCode(char) { // This method calcuates hash code for the Char Ignite type // char must be a js 'string' of length 1 return char.charCodeAt(0); } static intHashCode(int) { // This method calcuates hash code for Byte, Short or Integer Ignite types // int must be a js 'number' return int; } static longHashCode(long) { // This method calcuates hash code for the Long Ignite type // long must be a js 'number' const longObj = Long.fromNumber(long); return longObj.getLowBits() ^ longObj.getHighBits(); } static boolHashCode(bool) { // This method calcuates hash code for the Boolean Ignite type // bool must be a js 'boolean' return bool ? 1231 : 1237; } static floatHashCode(float) { // This method calcuates hash code for the Float Ignite type // float must be a js 'number' const buf = new ArrayBuffer(4); (new Float32Array(buf))[0] = float; const int32arr = new Int32Array(buf); return int32arr[0]; } static doubleHashCode(double) { // This method calcuates hash code for the Double Ignite type // double must be a js 'number' const buf = new ArrayBuffer(8); (new Float64Array(buf))[0] = double; const uint32arr = new Uint32Array(buf); return uint32arr[0] ^ uint32arr[1]; } static uuidHashCode(uuid) { // This method calcuates hash code for the UUID Ignite type // uuid must be a js Array of 'number' of length 16 const buf = Buffer.from(uuid); let xor = 0; for (let i = 0; i < 16; i += 4) { xor ^= buf.readUInt32BE(i); } return xor; } static timeHashCode(time) { // This method calcuates hash code for the Time Ignite type // time must be an instance of Date const midnight = new Date(time); midnight.setHours(0, 0, 0, 0); const totalmsec = time.getTime() - midnight.getTime(); return BinaryUtils.longHashCode(totalmsec); } static datetimeHashCode(date) { // This method calcuates hash code for the Timestamp and Date Ignite types // date must be an instance of Date or Timestamp return BinaryUtils.longHashCode(date.getTime()); } }