http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/ObjectType.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/ObjectType.js b/modules/platforms/nodejs/lib/ObjectType.js new file mode 100644 index 0000000..f1facfd --- /dev/null +++ b/modules/platforms/nodejs/lib/ObjectType.js @@ -0,0 +1,600 @@ +/* + * 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 Util = require('util'); +const Errors = require('./Errors'); +const ArgumentChecker = require('./internal/ArgumentChecker'); + +/** + * Supported Ignite type codes for primitive (simple) types. + * @typedef ObjectType.PRIMITIVE_TYPE + * @enum + * @readonly + * @property BYTE 1 + * @property SHORT 2 + * @property INTEGER 3 + * @property LONG 4 + * @property FLOAT 5 + * @property DOUBLE 6 + * @property CHAR 7 + * @property BOOLEAN 8 + * @property STRING 9 + * @property UUID 10 + * @property DATE 11 + * @property BYTE_ARRAY 12 + * @property SHORT_ARRAY 13 + * @property INTEGER_ARRAY 14 + * @property LONG_ARRAY 15 + * @property FLOAT_ARRAY 16 + * @property DOUBLE_ARRAY 17 + * @property CHAR_ARRAY 18 + * @property BOOLEAN_ARRAY 19 + * @property STRING_ARRAY 20 + * @property UUID_ARRAY 21 + * @property DATE_ARRAY 22 + * @property ENUM 28 + * @property ENUM_ARRAY 29 + * @property DECIMAL 30 + * @property DECIMAL_ARRAY 31 + * @property TIMESTAMP 33 + * @property TIMESTAMP_ARRAY 34 + * @property TIME 36 + * @property TIME_ARRAY 37 + */ +const PRIMITIVE_TYPE = Object.freeze({ + BYTE : 1, + SHORT : 2, + INTEGER : 3, + LONG : 4, + FLOAT : 5, + DOUBLE : 6, + CHAR : 7, + BOOLEAN : 8, + STRING : 9, + UUID : 10, + DATE : 11, + BYTE_ARRAY : 12, + SHORT_ARRAY : 13, + INTEGER_ARRAY : 14, + LONG_ARRAY : 15, + FLOAT_ARRAY : 16, + DOUBLE_ARRAY : 17, + CHAR_ARRAY : 18, + BOOLEAN_ARRAY : 19, + STRING_ARRAY : 20, + UUID_ARRAY : 21, + DATE_ARRAY : 22, + ENUM : 28, + ENUM_ARRAY : 29, + DECIMAL : 30, + DECIMAL_ARRAY : 31, + TIMESTAMP : 33, + TIMESTAMP_ARRAY : 34, + TIME : 36, + TIME_ARRAY : 37 +}); + +/** + * Supported Ignite type codes for non-primitive (composite) types. + * @typedef ObjectType.COMPOSITE_TYPE + * @enum + * @readonly + * @property OBJECT_ARRAY 23 + * @property COLLECTION 24 + * @property MAP 25 + * @property NULL 101 + * @property COMPLEX_OBJECT 103 + */ +const COMPOSITE_TYPE = Object.freeze({ + OBJECT_ARRAY : 23, + COLLECTION : 24, + MAP : 25, + NULL : 101, + COMPLEX_OBJECT : 103 +}); + +/** + * Base class representing a type of Ignite object. + * + * The class has no public constructor. Only subclasses may be instantiated. + * + * There are two groups of Ignite object types: + * + * - Primitive (simple) types. To fully describe such a type it is enough to specify + * Ignite type code {@link ObjectType.PRIMITIVE_TYPE} only. + * + * - Non-primitive (composite) types. To fully describe such a type + * Ignite type code {@link ObjectType.COMPOSITE_TYPE} with additional information should be specified. + * Eg. a kind of map or a kind of collection. + * + * This class helps the Ignite client to make a mapping between JavaScript types + * and types used by Ignite. + * + * In many methods the Ignite client does not require to directly specify a type of Ignite object. + * In this case the Ignite client tries to make automatic mapping between JavaScript types + * and Ignite object types according to the following mapping tables: + * + * ---------------------------------------------------------------------------- + * + * DEFAULT MAPPING FROM JavaScript type TO Ignite type code. + * + * This mapping is used when an application does not explicitly specify an Ignite type + * for a field and is writing data to that field. + * + * <pre> + * | JavaScript type | Ignite type code | + * | ------------------------- | ----------------------| + * | number | DOUBLE | + * | boolean | BOOLEAN | + * | string | STRING | + * | Date | DATE | + * | Timestamp* | TIMESTAMP | + * | EnumItem* | ENUM | + * | Decimal** | DECIMAL | + * | BinaryObject* | COMPLEX_OBJECT | + * | Array of number | DOUBLE_ARRAY | + * | Array of boolean | BOOLEAN_ARRAY | + * | Array of string | STRING_ARRAY | + * | Array of Date | DATE_ARRAY | + * | Array of Timestamp* | TIMESTAMP_ARRAY | + * | Array of EnumItem* | ENUM_ARRAY | + * | Array of Decimal** | DECIMAL_ARRAY | + * | Array of BinaryObject* | OBJECT_ARRAY | + * | Array of any other Object | OBJECT_ARRAY | + * | Set | COLLECTION (HASH_SET) | + * | Map | MAP (HASH_MAP) | + * | any other Object | COMPLEX_OBJECT | + * </pre> + * + * Type of an array content is determined by the type of the first element of the array. + * Empty array has no default mapping. + * + * All other JavaScript types have no default mapping. + * + * ---------------------------------------------------------------------------- + * + * DEFAULT MAPPING FROM Ignite type code TO JavaScript type. + * + * This mapping is used when an application does not explicitly specify an Ignite type + * for a field and is reading data from that field. + * + * <pre> + * | Ignite type code | JavaScript type | + * | ---------------------------- | --------------------------------------| + * | BYTE | number | + * | SHORT | number | + * | INTEGER | number | + * | LONG | number | + * | FLOAT | number | + * | DOUBLE | number | + * | DECIMAL | Decimal** | + * | BOOLEAN | boolean | + * | STRING | string | + * | CHAR | string (one character) | + * | UUID | Array of number (16 numbers) | + * | DATE | Date | + * | TIME | Date | + * | TIMESTAMP | Timestamp* | + * | ENUM | EnumItem* | + * | COMPLEX_OBJECT | BinaryObject* | + * | BYTE_ARRAY | Array of number | + * | SHORT_ARRAY | Array of number | + * | INTEGER_ARRAY | Array of number | + * | LONG_ARRAY | Array of number | + * | FLOAT_ARRAY | Array of number | + * | DOUBLE_ARRAY | Array of number | + * | DECIMAL_ARRAY | Array of Decimal** | + * | BOOLEAN_ARRAY | Array of boolean | + * | STRING_ARRAY | Array of string | + * | CHAR_ARRAY | Array of string (one character) | + * | UUID_ARRAY | Array of Array of number (16 numbers) | + * | DATE_ARRAY | Array of Date | + * | TIME_ARRAY | Array of Date | + * | TIMESTAMP_ARRAY | Array of Timestamp* | + * | ENUM_ARRAY | Array of EnumItem* | + * | OBJECT_ARRAY | Array | + * | COLLECTION (USER_COL) | Array | + * | COLLECTION (ARR_LIST) | Array | + * | COLLECTION (LINKED_LIST) | Array | + * | COLLECTION (SINGLETON_LIST) | Array | + * | COLLECTION (HASH_SET) | Set | + * | COLLECTION (LINKED_HASH_SET) | Set | + * | COLLECTION (USER_SET) | Set | + * | MAP (HASH_MAP) | Map | + * | MAP (LINKED_HASH_MAP) | Map | + * | NULL | null | + * </pre> + * + * ---------------------------------------------------------------------------- + * + * RETURNED JavaScript types WHEN READING DATA OF THE SPECIFIED Ignite type code. + * + * When an application explicitly specifies an Ignite type for a field + * and is reading data from that field - the following JavaScript types + * are returned for every concrete Ignite type code - + * + * SEE THE PREVIOUS TABLE with the following additional comments: + * + * - for COMPLEX_OBJECT the Ignite Client returns a JavaScript Object + * which is defined by the specified {@link ComplexObjectType}. + * + * - the returned Map for MAP is defined by the specified {@link MapObjectType}. + * + * - the returned Set or Array for COLLECTION is defined by the specified {@link CollectionObjectType}. + * + * - the returned Array for OBJECT_ARRAY is defined by the specified {@link ObjectArrayType}. + * + * - NULL cannot be specified as a type of a field but JavaScript null may be returned + * as a value of a field. + * + * ---------------------------------------------------------------------------- + * + * ALLOWED JavaScript types WHEN WRITING DATA OF THE SPECIFIED Ignite type code. + * + * When an application explicitly specifies an Ignite type for a field + * and is writing data to that field - the following JavaScript types + * are allowed for every concrete Ignite type code - + * + * SEE THE PREVIOUS TABLE with the following additional comments: + * + * - for COMPLEX_OBJECT the Ignite Client allows a JavaScript Object + * which is defined by the specified {@link ComplexObjectType}. + * + * - the allowed Map for MAP is defined by the specified {@link MapObjectType}. + * + * - the allowed Set or Array for COLLECTION is defined by the specified {@link CollectionObjectType}. + * + * - the allowed Array for OBJECT_ARRAY is defined by the specified {@link ObjectArrayType}. + * + * - NULL cannot be specified as a type of a field but JavaScript null is allowed + * as value of a field (but not as a key/value in a cache) or as a value of Array/Set/Map element + * for all Ignite types, except BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, CHAR, BOOLEAN. + * + * - for all *_ARRAY Ignite types an empty JavaScript Array is allowed. + * + * ---------------------------------------------------------------------------- + * + * COMMENTS TO ALL TABLES + * + * JavaScript type - is a JavaScript primitive or a JavaScript Object + * ({@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures}) + * + * (*) Timestamp, EnumItem and BinaryObject - are JavaScript Objects introduced by the Ignite client. + * + * (**) Decimal - is an external JavaScript Object exported into the Ignite client + * ({@link https://github.com/MikeMcl/decimal.js}) + * + * Ignite type code - is the type code of an Ignite primitive type ({@link ObjectType.PRIMITIVE_TYPE}) + * or an Ignite composite type ({@link ObjectType.COMPOSITE_TYPE}). + * + * ---------------------------------------------------------------------------- + * + * @hideconstructor + */ + +class ObjectType { + static get PRIMITIVE_TYPE() { + return PRIMITIVE_TYPE; + } + + static get COMPOSITE_TYPE() { + return COMPOSITE_TYPE; + } + + /** Private methods */ + + constructor(typeCode) { + this._typeCode = typeCode; + } +} + +/** + * Base class representing a non-primitive (composite) type of Ignite object. + * + * The class has no public constructor. Only subclasses may be instantiated. + * + * @hideconstructor + * @extends ObjectType + */ +class CompositeType extends ObjectType { +} + +/** + * Supported kinds of map. + * @typedef MapObjectType.MAP_SUBTYPE + * @enum + * @readonly + * @property HASH_MAP 1 + * @property LINKED_HASH_MAP 2 + */ +const MAP_SUBTYPE = Object.freeze({ + HASH_MAP : 1, + LINKED_HASH_MAP : 2 +}); + +/** + * Class representing a map type of Ignite object. + * + * It is described by COMPOSITE_TYPE.MAP {@link ObjectType.COMPOSITE_TYPE} + * and one of {@link MapObjectType.MAP_SUBTYPE}. + * + * @extends CompositeType + */ +class MapObjectType extends CompositeType { + static get MAP_SUBTYPE() { + return MAP_SUBTYPE; + } + + /** + * Public constructor. + * + * Optionally specifies a kind of map and types of keys and values in the map. + * + * If a kind of map is not specified, MAP_SUBTYPE.HASH_MAP is assumed. + * + * If key and/or value type is not specified then during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {MapObjectType.MAP_SUBTYPE} [mapSubType=MAP_SUBTYPE.HASH_MAP] - map subtype, one of the + * {@link MapObjectType.MAP_SUBTYPE} constants. + * @param {ObjectType.PRIMITIVE_TYPE | CompositeType} [keyType=null] - type of the keys in the map: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (or not specified) that means the type is not specified + * @param {ObjectType.PRIMITIVE_TYPE | CompositeType} [valueType=null] - type of the values in the map: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (or not specified) that means the type is not specified + * + * @return {MapObjectType} - new MapObjectType instance + * + * @throws {IgniteClientError} if error. + */ + constructor(mapSubType = MapObjectType.MAP_SUBTYPE.HASH_MAP, keyType = null, valueType = null) { + super(COMPOSITE_TYPE.MAP); + const BinaryUtils = require('./internal/BinaryUtils'); + ArgumentChecker.hasValueFrom(mapSubType, 'mapSubType', false, MapObjectType.MAP_SUBTYPE); + this._subType = mapSubType; + BinaryUtils.checkObjectType(keyType, 'keyType'); + BinaryUtils.checkObjectType(valueType, 'valueType'); + this._keyType = keyType; + this._valueType = valueType; + } +} + +/** + * Supported kinds of collections. + * @typedef CollectionObjectType.COLLECTION_SUBTYPE + * @enum + * @readonly + * @property USER_SET -1 + * @property USER_COL 0 + * @property ARRAY_LIST 1 + * @property LINKED_LIST 2 + * @property HASH_SET 3 + * @property LINKED_HASH_SET 4 + * @property SINGLETON_LIST 5 + */ +const COLLECTION_SUBTYPE = Object.freeze({ + USER_SET : -1, + USER_COL : 0, + ARRAY_LIST : 1, + LINKED_LIST : 2, + HASH_SET : 3, + LINKED_HASH_SET : 4, + SINGLETON_LIST : 5 +}); + +/** + * Class representing a collection type of Ignite object. + * + * It is described by COMPOSITE_TYPE.COLLECTION {@link ObjectType.COMPOSITE_TYPE} + * and one of {@link CollectionObjectType.COLLECTION_SUBTYPE}. + * + * @extends CompositeType + */ +class CollectionObjectType extends CompositeType { + static get COLLECTION_SUBTYPE() { + return COLLECTION_SUBTYPE; + } + + /** + * Public constructor. + * + * Specifies a kind of collection + * and optionally specifies a type of elements in the collection. + * + * If the type of elements is not specified then during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {CollectionObjectType.COLLECTION_SUBTYPE} collectionSubType - collection subtype, one of the + * {@link CollectionObjectType.COLLECTION_SUBTYPE} constants. + * @param {ObjectType.PRIMITIVE_TYPE | CompositeType} [elementType=null] - type of elements in the collection: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (or not specified) that means the type is not specified + * + * @return {CollectionObjectType} - new CollectionObjectType instance + * + * @throws {IgniteClientError} if error. + */ + constructor(collectionSubType, elementType = null) { + super(COMPOSITE_TYPE.COLLECTION); + const BinaryUtils = require('./internal/BinaryUtils'); + ArgumentChecker.hasValueFrom( + collectionSubType, 'collectionSubType', false, CollectionObjectType.COLLECTION_SUBTYPE); + this._subType = collectionSubType; + BinaryUtils.checkObjectType(elementType, 'elementType'); + this._elementType = elementType; + } + + /** Private methods */ + + /** + * @ignore + */ + static _isSet(subType) { + return subType === CollectionObjectType.COLLECTION_SUBTYPE.USER_SET || + subType === CollectionObjectType.COLLECTION_SUBTYPE.HASH_SET || + subType === CollectionObjectType.COLLECTION_SUBTYPE.LINKED_HASH_SET; + } + + /** + * @ignore + */ + _isSet() { + return CollectionObjectType._isSet(this._subType); + } +} + +/** + * Class representing an array type of Ignite objects. + * + * It is described by COMPOSITE_TYPE.OBJECT_ARRAY {@link ObjectType.COMPOSITE_TYPE}. + * + * @extends CompositeType + */ +class ObjectArrayType extends CompositeType { + + /** + * Public constructor. + * + * Optionally specifies a type of elements in the array. + * + * If the type of elements is not specified then during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {ObjectType.PRIMITIVE_TYPE | CompositeType} [elementType=null] - type of the array element: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (or not specified) that means the type is not specified + * + * @return {ObjectArrayType} - new ObjectArrayType instance + * + * @throws {IgniteClientError} if error. + */ + constructor(elementType = null) { + super(COMPOSITE_TYPE.OBJECT_ARRAY); + const BinaryUtils = require('./internal/BinaryUtils'); + BinaryUtils.checkObjectType(elementType, 'elementType'); + this._elementType = elementType; + } +} + +/** + * Class representing a complex type of Ignite object. + * + * It is described by COMPOSITE_TYPE.COMPLEX_OBJECT {@link ObjectType.COMPOSITE_TYPE}, + * by a name of the complex type and by a JavaScript Object which is mapped to/from the Ignite complex type. + * + * @extends CompositeType + */ +class ComplexObjectType extends CompositeType { + + /** + * Public constructor. + * + * Specifies a JavaScript Object type which will be mapped to/from the complex type. + * This specification is done using an instance of the JavaScript Object. + * + * If an object of the complex type is going to be received (deserialized), + * the JavaScript Object must have a constructor without parameters or with optional parameters only. + * + * The JavaScript Object defines a set of fields of the complex type. + * + * By default, the fields have no types specified. It means during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * A type of any field may be specified later by setFieldType() method. + * + * By default, the name of the complex type is the name of the JavaScript Object. + * The name may be explicitely specified using optional typeName parameter in the constructor. + * + * @param {object} jsObject - instance of JavaScript Object which will be mapped to/from this complex type. + * @param {string} [typeName] - name of the complex type. + * + * @return {ComplexObjectType} - new ComplexObjectType instance + * + * @throws {IgniteClientError} if error. + */ + constructor(jsObject, typeName = null) { + super(COMPOSITE_TYPE.COMPLEX_OBJECT); + ArgumentChecker.notEmpty(jsObject, 'jsObject'); + this._template = jsObject; + this._objectConstructor = jsObject && jsObject.constructor ? + jsObject.constructor : Object; + if (!typeName) { + typeName = this._objectConstructor.name; + } + this._typeName = typeName; + this._fields = new Map(); + const BinaryUtils = require('./internal/BinaryUtils'); + for (let fieldName of BinaryUtils.getJsObjectFieldNames(this._template)) { + this._fields.set(fieldName, null); + } + } + + /** + * Specifies a type of the field in the complex type. + * + * If the type is not specified then during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {string} fieldName - name of the field. + * @param {ObjectType.PRIMITIVE_TYPE | CompositeType} fieldType - type of the field: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (means the type is not specified). + * + * @return {ComplexObjectType} - the same instance of the ComplexObjectType. + * + * @throws {IgniteClientError} if error. + */ + setFieldType(fieldName, fieldType) { + if (!this._fields.has(fieldName)) { + throw Errors.IgniteClientError.illegalArgumentError( + Util.format('Field "%s" is absent in the complex object type', fieldName)); + } + const BinaryUtils = require('./internal/BinaryUtils'); + BinaryUtils.checkObjectType(fieldType, 'fieldType'); + this._fields.set(fieldName, fieldType); + return this; + } + + /** Private methods */ + + /** + * @ignore + */ + _getFieldType(fieldName) { + return this._fields.get(fieldName); + } +} + +module.exports.ObjectType = ObjectType; +module.exports.CompositeType = CompositeType; +module.exports.MapObjectType = MapObjectType; +module.exports.CollectionObjectType = CollectionObjectType; +module.exports.ComplexObjectType = ComplexObjectType; +module.exports.ObjectArrayType = ObjectArrayType;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/Query.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/Query.js b/modules/platforms/nodejs/lib/Query.js new file mode 100644 index 0000000..5c230df --- /dev/null +++ b/modules/platforms/nodejs/lib/Query.js @@ -0,0 +1,508 @@ +/* + * 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 Cursor = require('./Cursor').Cursor; +const SqlFieldsCursor = require('./Cursor').SqlFieldsCursor; +const ArgumentChecker = require('./internal/ArgumentChecker'); +const BinaryWriter = require('./internal/BinaryWriter'); +const BinaryUtils = require('./internal/BinaryUtils'); + +const PAGE_SIZE_DEFAULT = 1024; + +/** + * Base class representing an Ignite SQL or Scan query. + * + * The class has no public constructor. Only subclasses may be instantiated. + * + * @hideconstructor + */ +class Query { + + /** + * Set local query flag. + * + * @param {boolean} local - local query flag: true or false. + * + * @return {Query} - the same instance of the Query. + */ + setLocal(local) { + this._local = local; + return this; + } + + /** + * Set {@link Cursor} page size. + * + * @param {number} pageSize - cursor page size. + * + * @return {Query} - the same instance of the Query. + */ + setPageSize(pageSize) { + this._pageSize = pageSize; + return this; + } + + /** Private methods */ + + /** + * @ignore + */ + constructor(operation) { + this._operation = operation; + this._local = false; + this._pageSize = PAGE_SIZE_DEFAULT; + } +} + +/** + * Class representing an SQL query which returns the whole cache entries (key-value pairs). + * @extends Query + */ +class SqlQuery extends Query { + + /** + * Public constructor. + * + * Requires name of a type (or SQL table) and SQL query string to be specified. + * Other SQL query settings have the following defaults: + * <pre> + * SQL Query setting : Default value + * Local query flag : false + * Cursor page size : 1024 + * Query arguments : not specified + * Distributed joins flag : false + * Replicated only flag : false + * Timeout : 0 (disabled) + * </pre> + * Every setting may be changed using set methods. + * + * @param {string} type - name of a type or SQL table. + * @param {string} sql - SQL query string. + * + * @return {SqlQuery} - new SqlQuery instance. + */ + constructor(type, sql) { + super(BinaryUtils.OPERATION.QUERY_SQL); + this.setType(type); + this.setSql(sql); + this._args = null; + this._argTypes = null; + this._distributedJoins = false; + this._replicatedOnly = false; + this._timeout = 0; + } + + /** + * Set name of a type or SQL table. + * + * @param {string} type - name of a type or SQL table. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setType(type) { + if (this instanceof SqlFieldsQuery) { + ArgumentChecker.invalidArgument(type, 'type', SqlFieldsQuery); + } + else { + ArgumentChecker.notNull(type, 'type'); + } + this._type = type; + return this; + } + + /** + * Set SQL query string. + * + * @param {string} sql - SQL query string. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setSql(sql) { + ArgumentChecker.notNull(sql, 'sql'); + this._sql = sql; + return this; + } + + /** + * Set query arguments. + * + * Type of any argument may be specified using setArgTypes() method. + * If type of an argument is not specified then during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {...*} args - Query arguments. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setArgs(...args) { + this._args = args; + return this; + } + + /** + * Specifies types of query arguments. + * + * Query arguments itself are set using setArgs() method. + * By default, a type of every argument is not specified that means during operations the Ignite client + * will try to make automatic mapping between JavaScript types and Ignite object types - + * according to the mapping table defined in the description of the {@link ObjectType} class. + * + * @param {...ObjectType.PRIMITIVE_TYPE | CompositeType} argTypes - types of Query arguments. + * The order of types must follow the order of arguments in the setArgs() method. + * A type of every argument can be: + * - either a type code of primitive (simple) type + * - or an instance of class representing non-primitive (composite) type + * - or null (means the type is not specified) + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setArgTypes(...argTypes) { + this._argTypes = argTypes; + return this; + } + + /** + * Set distributed joins flag. + * + * @param {boolean} distributedJoins - distributed joins flag: true or false. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setDistributedJoins(distributedJoins) { + this._distributedJoins = distributedJoins; + return this; + } + + /** + * Set replicated only flag. + * + * @param {boolean} replicatedOnly - replicated only flag: true or false. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setReplicatedOnly(replicatedOnly) { + this._replicatedOnly = replicatedOnly; + return this; + } + + /** + * Set timeout. + * + * @param {number} timeout - timeout value in milliseconds. + * Must be non-negative. Zero value disables timeout. + * + * @return {SqlQuery} - the same instance of the SqlQuery. + */ + setTimeout(timeout) { + this._timeout = timeout; + return this; + } + + /** Private methods */ + + /** + * @ignore + */ + async _write(buffer) { + await BinaryWriter.writeString(buffer, this._type); + await BinaryWriter.writeString(buffer, this._sql); + await this._writeArgs(buffer); + buffer.writeBoolean(this._distributedJoins); + buffer.writeBoolean(this._local); + buffer.writeBoolean(this._replicatedOnly); + buffer.writeInteger(this._pageSize); + buffer.writeLong(this._timeout); + } + + /** + * @ignore + */ + async _writeArgs(buffer) { + const argsLength = this._args ? this._args.length : 0; + buffer.writeInteger(argsLength); + if (argsLength > 0) { + let argType; + for (let i = 0; i < argsLength; i++) { + argType = this._argTypes && i < this._argTypes.length ? this._argTypes[i] : null; + await BinaryWriter.writeObject(buffer, this._args[i], argType); + } + } + } + + /** + * @ignore + */ + async _getCursor(socket, payload, keyType = null, valueType = null) { + const cursor = new Cursor(socket, BinaryUtils.OPERATION.QUERY_SQL_CURSOR_GET_PAGE, payload, keyType, valueType); + cursor._readId(payload); + return cursor; + } +} + +/** + * Statement type of SQL Fields query. + * @typedef SqlFieldsQuery.STATEMENT_TYPE + * @enum + * @readonly + * @property ANY 0 + * @property SELECT 1 + * @property UPDATE 2 + */ +const STATEMENT_TYPE = Object.freeze({ + ANY : 0, + SELECT : 1, + UPDATE : 2 +}); + + +/** + * Class representing an SQL Fields query. + * @extends SqlQuery + */ +class SqlFieldsQuery extends SqlQuery { + + /** + * Public constructor. + * + * Requires SQL query string to be specified. + * Other SQL Fields query settings have the following defaults: + * <pre> + * SQL Fields Query setting : Default value + * Local query flag : false + * Cursor page size : 1024 + * Query arguments : not specified + * Distributed joins flag : false + * Replicated only flag : false + * Timeout : 0 (disabled) + * Schema for the query : not specified + * Max rows : -1 + * Statement type : STATEMENT_TYPE.ANY + * Enforce join order flag : false + * Collocated flag : false + * Lazy query execution flag : false + * Include field names flag : false + * </pre> + * Every setting may be changed using set methods. + * + * @param {string} sql - SQL query string. + * + * @return {SqlFieldsQuery} - new SqlFieldsQuery instance. + */ + constructor(sql) { + super(null, sql); + this._operation = BinaryUtils.OPERATION.QUERY_SQL_FIELDS; + this._schema = null; + this._maxRows = -1; + this._statementType = SqlFieldsQuery.STATEMENT_TYPE.ANY; + this._enforceJoinOrder = false; + this._collocated = false; + this._lazy = false; + this._includeFieldNames = false; + } + + static get STATEMENT_TYPE() { + return STATEMENT_TYPE; + } + + /** + * Set schema for the query. + * + * @param {string} schema - schema for the query. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setSchema(schema) { + this._schema = schema; + return this; + } + + /** + * Set max rows. + * + * @param {number} maxRows - max rows. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setMaxRows(maxRows) { + this._maxRows = maxRows; + return this; + } + + /** + * Set statement type. + * + * @param {SqlFieldsQuery.STATEMENT_TYPE} type - statement type. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setStatementType(type) { + this._statementType = type; + return this; + } + + /** + * Set enforce join order flag. + * + * @param {boolean} enforceJoinOrder - enforce join order flag: true or false. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setEnforceJoinOrder(enforceJoinOrder) { + this._enforceJoinOrder = enforceJoinOrder; + return this; + } + + /** + * Set collocated flag. + * + * @param {boolean} collocated - collocated flag: true or false. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setCollocated(collocated) { + this._collocated = collocated; + return this; + } + + /** + * Set lazy query execution flag. + * + * @param {boolean} lazy - lazy query execution flag: true or false. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setLazy(lazy) { + this._lazy = lazy; + return this; + } + + /** + * Set include field names flag. + * + * @param {boolean} includeFieldNames - include field names flag: true or false. + * + * @return {SqlFieldsQuery} - the same instance of the SqlFieldsQuery. + */ + setIncludeFieldNames(includeFieldNames) { + this._includeFieldNames = includeFieldNames; + return this; + } + + /** Private methods */ + + /** + * @ignore + */ + async _write(buffer) { + await BinaryWriter.writeString(buffer, this._schema); + buffer.writeInteger(this._pageSize); + buffer.writeInteger(this._maxRows); + await BinaryWriter.writeString(buffer, this._sql); + await this._writeArgs(buffer) + buffer.writeByte(this._statementType); + buffer.writeBoolean(this._distributedJoins); + buffer.writeBoolean(this._local); + buffer.writeBoolean(this._replicatedOnly); + buffer.writeBoolean(this._enforceJoinOrder); + buffer.writeBoolean(this._collocated); + buffer.writeBoolean(this._lazy); + buffer.writeLong(this._timeout); + buffer.writeBoolean(this._includeFieldNames); + } + + /** + * @ignore + */ + async _getCursor(socket, payload, keyType = null, valueType = null) { + const cursor = new SqlFieldsCursor(socket, payload); + await cursor._readFieldNames(payload, this._includeFieldNames); + return cursor; + } +} + +/** + * Class representing a Scan query which returns the whole cache entries (key-value pairs). + * + * This version of the class does not support a possibility to specify a Filter object for the query. + * The query returns all entries from the entire cache or from the specified partition. + * @extends Query + */ +class ScanQuery extends Query { + + /** + * Public constructor. + * + * Scan query settings have the following defaults: + * <pre> + * Scan Query setting : Default value + * Local query flag : false + * Cursor page size : 1024 + * Partition number : -1 (entire cache) + * Filter object : null (not supported) + * </pre> + * Every setting (except Filter object) may be changed using set methods. + * + * @return {ScanQuery} - new ScanQuery instance. + */ + constructor() { + super(BinaryUtils.OPERATION.QUERY_SCAN); + this._partitionNumber = -1; + } + + /** + * Sets a partition number over which this query should iterate. + * + * If negative, the query will iterate over all partitions in the cache. + * + * @param {number} partitionNumber - partition number over which this query should iterate. + * + * @return {ScanQuery} - the same instance of the ScanQuery. + */ + setPartitionNumber(partitionNumber) { + this._partitionNumber = partitionNumber; + return this; + } + + /** Private methods */ + + /** + * @ignore + */ + async _write(buffer) { + // filter + await BinaryWriter.writeObject(buffer, null); + buffer.writeInteger(this._pageSize); + buffer.writeInteger(this._partitionNumber); + buffer.writeBoolean(this._local); + } + + /** + * @ignore + */ + async _getCursor(socket, payload, keyType = null, valueType = null) { + const cursor = new Cursor(socket, BinaryUtils.OPERATION.QUERY_SCAN_CURSOR_GET_PAGE, payload, keyType, valueType); + cursor._readId(payload); + return cursor; + } +} + +module.exports.SqlQuery = SqlQuery; +module.exports.SqlFieldsQuery = SqlFieldsQuery; +module.exports.ScanQuery = ScanQuery; http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/Timestamp.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/Timestamp.js b/modules/platforms/nodejs/lib/Timestamp.js new file mode 100644 index 0000000..04d750c --- /dev/null +++ b/modules/platforms/nodejs/lib/Timestamp.js @@ -0,0 +1,76 @@ +/* + * 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 ArgumentChecker = require('./internal/ArgumentChecker'); + +/** + * Class representing an Ignite timestamp type. + * + * The timestamp extends the standard JavaScript {@link Date} Object and consists of: + * - time - the number of milliseconds since January 1, 1970, 00:00:00 UTC, + * methods of the JavaScript {@link Date} Object can be used to operate with the time. + * - nanoseconds - fraction of the last millisecond in the range from 0 to 999999 nanoseconds, + * this class specifies additional methods to operate with the nanoseconds. + * @extends Date + */ +class Timestamp extends Date { + + /** + * Public constructor. + * + * @param {number} time - integer value representing the number of milliseconds since January 1, 1970, 00:00:00 UTC. + * @param {number} nanos - integer value representing the nanoseconds of the last millisecond, + * should be in the range from 0 to 999999. + * + * @return {Timestamp} - new Timestamp instance + * + * @throws {IgniteClientError} if error. + */ + constructor(time, nanos) { + super(time); + this.setNanos(nanos); + } + + /** + * Returns the nanoseconds of the last millisecond from the timestamp. + * + * @return {number} - nanoseconds of the last millisecond. + */ + getNanos() { + return this._nanos; + } + + /** + * Updates the nanoseconds of the last millisecond in the timestamp. + * + * @param {number} nanos - new value for the nanoseconds of the last millisecond, + * should be in the range from 0 to 999999. + * + * @return {Timestamp} - the same instance of Timestamp + * + * @throws {IgniteClientError} if error. + */ + setNanos(nanos) { + ArgumentChecker.isInteger(nanos, 'nanos'); + this._nanos = nanos; + return this; + } +} + +module.exports = Timestamp; http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/ArgumentChecker.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/internal/ArgumentChecker.js b/modules/platforms/nodejs/lib/internal/ArgumentChecker.js new file mode 100644 index 0000000..9e60ad6 --- /dev/null +++ b/modules/platforms/nodejs/lib/internal/ArgumentChecker.js @@ -0,0 +1,83 @@ +/* + * 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 Util = require('util'); +const Errors = require('../Errors'); + +/** Helper class for the library methods arguments check. */ +class ArgumentChecker { + static notEmpty(arg, argName) { + if (!arg || arg instanceof Array && arg.length === 0) { + throw Errors.IgniteClientError.illegalArgumentError(Util.format('"%s" argument should not be empty', argName)); + } + } + + static notNull(arg, argName) { + if (arg === null || arg === undefined) { + throw Errors.IgniteClientError.illegalArgumentError(Util.format('"%s" argument should not be null', argName)); + } + } + + static hasType(arg, argName, isArray, ...types) { + if (arg === null) { + return; + } + if (isArray && arg instanceof Array) { + for (let a of arg) { + ArgumentChecker.hasType(a, argName, false, ...types); + } + } + else { + for (let type of types) { + if (arg instanceof type) { + return; + } + } + throw Errors.IgniteClientError.illegalArgumentError(Util.format('"%s" argument has incorrect type', argName)); + } + } + + static hasValueFrom(arg, argName, isArray, values) { + if (isArray && arg instanceof Array) { + for (let a of arg) { + ArgumentChecker.hasValueFrom(a, argName, false, values); + } + } + else { + if (!Object.values(values).includes(arg)) { + throw Errors.IgniteClientError.illegalArgumentError(Util.format('"%s" argument has incorrect value', argName)); + } + } + } + + static isInteger(arg, argName) { + if (arg === null || arg === undefined || !Number.isInteger(arg)) { + throw Errors.IgniteClientError.illegalArgumentError(Util.format('"%s" argument should be integer', argName)); + } + } + + static invalidArgument(arg, argName, type) { + if (arg !== null && arg !== undefined) { + throw Errors.IgniteClientError.illegalArgumentError( + Util.format('"%s" argument is invalid for %s', argName, type.constructor.name)); + } + } +} + +module.exports = ArgumentChecker; http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryReader.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/internal/BinaryReader.js b/modules/platforms/nodejs/lib/internal/BinaryReader.js new file mode 100644 index 0000000..8c25c39 --- /dev/null +++ b/modules/platforms/nodejs/lib/internal/BinaryReader.js @@ -0,0 +1,197 @@ +/* + * 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 Decimal = require('decimal.js'); +const BinaryObject = require('../BinaryObject'); +const CollectionObjectType = require('../ObjectType').CollectionObjectType; +const Errors = require('../Errors'); +const Timestamp = require('../Timestamp'); +const EnumItem = require('../EnumItem'); +const BinaryUtils = require('./BinaryUtils'); + +class BinaryReader { + + static async readObject(buffer, expectedType = null) { + const typeCode = buffer.readByte(); + BinaryUtils.checkTypesComatibility(expectedType, typeCode); + return await BinaryReader._readTypedObject(buffer, typeCode, expectedType); + } + + static async readStringArray(buffer) { + return await BinaryReader._readTypedObject(buffer, BinaryUtils.TYPE_CODE.STRING_ARRAY); + } + + static async _readTypedObject(buffer, objectTypeCode, expectedType = null) { + switch (objectTypeCode) { + case BinaryUtils.TYPE_CODE.BYTE: + case BinaryUtils.TYPE_CODE.SHORT: + case BinaryUtils.TYPE_CODE.INTEGER: + case BinaryUtils.TYPE_CODE.FLOAT: + case BinaryUtils.TYPE_CODE.DOUBLE: + return buffer.readNumber(objectTypeCode); + case BinaryUtils.TYPE_CODE.LONG: + return buffer.readLong().toNumber(); + case BinaryUtils.TYPE_CODE.CHAR: + return buffer.readChar(); + case BinaryUtils.TYPE_CODE.BOOLEAN: + return buffer.readBoolean(); + case BinaryUtils.TYPE_CODE.STRING: + return buffer.readString(); + case BinaryUtils.TYPE_CODE.UUID: + return BinaryReader._readUUID(buffer); + case BinaryUtils.TYPE_CODE.DATE: + return buffer.readDate(); + case BinaryUtils.TYPE_CODE.ENUM: + case BinaryUtils.TYPE_CODE.BINARY_ENUM: + return await BinaryReader._readEnum(buffer); + case BinaryUtils.TYPE_CODE.DECIMAL: + return BinaryReader._readDecimal(buffer); + case BinaryUtils.TYPE_CODE.TIMESTAMP: + return BinaryReader._readTimestamp(buffer); + case BinaryUtils.TYPE_CODE.TIME: + return buffer.readDate(); + 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: + return await BinaryReader._readArray(buffer, objectTypeCode, expectedType); + case BinaryUtils.TYPE_CODE.COLLECTION: + return await BinaryReader._readCollection(buffer, expectedType); + case BinaryUtils.TYPE_CODE.MAP: + return await BinaryReader._readMap(buffer, expectedType); + case BinaryUtils.TYPE_CODE.BINARY_OBJECT: + return await BinaryReader._readBinaryObject(buffer, expectedType); + case BinaryUtils.TYPE_CODE.NULL: + return null; + case BinaryUtils.TYPE_CODE.COMPLEX_OBJECT: + return await BinaryReader._readComplexObject(buffer, expectedType); + default: + throw Errors.IgniteClientError.unsupportedTypeError(objectTypeCode); + } + } + + static _readUUID(buffer) { + return [...buffer.readBuffer(BinaryUtils.getSize(BinaryUtils.TYPE_CODE.UUID))]; + } + + static async _readEnum(buffer) { + const enumItem = new EnumItem(0); + await enumItem._read(buffer); + return enumItem; + } + + static _readDecimal(buffer) { + const scale = buffer.readInteger(); + const dataLength = buffer.readInteger(); + const data = buffer.readBuffer(dataLength); + const isNegative = (data[0] & 0x80) !== 0; + if (isNegative) { + data[0] &= 0x7F; + } + let result = new Decimal('0x' + data.toString('hex')); + if (isNegative) { + result = result.negated(); + } + return result.mul(Decimal.pow(10, -scale)); + } + + static _readTimestamp(buffer) { + return new Timestamp(buffer.readLong().toNumber(), buffer.readInteger()); + } + + static async _readArray(buffer, arrayTypeCode, arrayType) { + if (arrayTypeCode === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) { + buffer.readInteger(); + } + const length = buffer.readInteger(); + const elementType = BinaryUtils.getArrayElementType(arrayType ? arrayType : arrayTypeCode); + const keepElementType = elementType === null ? true : BinaryUtils.keepArrayElementType(arrayTypeCode); + const result = new Array(length); + for (let i = 0; i < length; i++) { + result[i] = keepElementType ? + await BinaryReader.readObject(buffer, elementType) : + await BinaryReader._readTypedObject(buffer, elementType); + } + return result; + } + + static async _readMap(buffer, expectedMapType) { + const result = new Map(); + const size = buffer.readInteger(); + const subType = buffer.readByte(); + let key, value; + for (let i = 0; i < size; i++) { + key = await BinaryReader.readObject(buffer, expectedMapType ? expectedMapType._keyType : null); + value = await BinaryReader.readObject(buffer, expectedMapType ? expectedMapType._valueType : null); + result.set(key, value); + } + return result; + } + + static async _readCollection(buffer, expectedColType) { + const size = buffer.readInteger(); + const subType = buffer.readByte(); + const isSet = CollectionObjectType._isSet(subType); + const result = isSet ? new Set() : new Array(size); + let element; + for (let i = 0; i < size; i++) { + element = await BinaryReader.readObject(buffer, expectedColType ? expectedColType._elementType : null); + if (isSet) { + result.add(element); + } + else { + result[i] = element; + } + } + return result; + } + + static async _readBinaryObject(buffer, expectedType) { + const size = buffer.readInteger(); + const startPos = buffer.position; + buffer.position = startPos + size; + const offset = buffer.readInteger(); + const endPos = buffer.position; + buffer.position = startPos + offset; + const result = await BinaryReader.readObject(buffer, expectedType); + buffer.position = endPos; + return result; + } + + static async _readComplexObject(buffer, expectedType) { + buffer.position = buffer.position - 1; + const binaryObject = await BinaryObject._fromBuffer(buffer); + return expectedType ? + await binaryObject.toObject(expectedType) : binaryObject; + } +} + +module.exports = BinaryReader; http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryType.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/internal/BinaryType.js b/modules/platforms/nodejs/lib/internal/BinaryType.js new file mode 100644 index 0000000..b9e239d --- /dev/null +++ b/modules/platforms/nodejs/lib/internal/BinaryType.js @@ -0,0 +1,472 @@ +/* + * 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 Util = require('util'); +const ComplexObjectType = require('../ObjectType').ComplexObjectType; +const BinaryTypeStorage = require('./BinaryTypeStorage'); +const BinaryUtils = require('./BinaryUtils'); +const BinaryWriter = require('./BinaryWriter'); +const Errors = require('../Errors'); + +class BinaryType { + constructor(name) { + this._name = name; + this._id = BinaryType._calculateId(name); + this._fields = new Map(); + this._schemas = new Map(); + this._isEnum = false; + this._enumValues = null; + } + + get id() { + return this._id; + } + + get name() { + return this._name; + } + + get fields() { + return [...this._fields.values()]; + } + + getField(fieldId) { + return this._fields.get(fieldId); + } + + hasField(fieldId) { + return this._fields.has(fieldId); + } + + removeField(fieldId) { + return this._fields.delete(fieldId); + } + + setField(field) { + this._fields.set(field.id, field); + } + + hasSchema(schemaId) { + return this._schemas.has(schemaId); + } + + addSchema(schema) { + if (!this.hasSchema(schema.id)) { + this._schemas.set(schema.id, schema); + } + } + + getSchema(schemaId) { + return this._schemas.get(schemaId); + } + + merge(binaryType, binarySchema) { + let fieldId; + for (let field of binaryType.fields) { + fieldId = field.id; + if (this.hasField(fieldId)) { + if (this.getField(fieldId).typeCode !== field.typeCode) { + throw Errors.IgniteClientError.serializationError( + true, Util.format('type conflict for field "%s" of complex object type "%s"'), + field.name, this._name); + } + } + else { + this.setField(field); + } + } + this.addSchema(binarySchema); + } + + clone() { + const result = new BinaryType(); + result._name = this._name; + result._id = this._id; + result._fields = new Map(this._fields.entries()); + result._schemas = new Map(this._schemas.entries()); + result._isEnum = this._isEnum; + return result; + } + + static _calculateId(name) { + return BinaryUtils.hashCodeLowerCase(name); + } + + async _write(buffer) { + // type id + buffer.writeInteger(this._id); + // type name + await BinaryWriter.writeString(buffer, this._name); + // affinity key field name + await BinaryWriter.writeString(buffer, null); + // fields count + buffer.writeInteger(this._fields.size); + // fields + for (let field of this._fields.values()) { + await field._write(buffer); + } + await this._writeEnum(buffer); + // schemas count + buffer.writeInteger(this._schemas.size); + for (let schema of this._schemas.values()) { + await schema._write(buffer); + } + } + + async _writeEnum(buffer) { + buffer.writeBoolean(this._isEnum); + if (this._isEnum) { + const length = this._enumValues ? this._enumValues.length : 0; + buffer.writeInteger(length); + if (length > 0) { + for (let [key, value] of this._enumValues) { + await BinaryWriter.writeString(buffer, key); + buffer.writeInteger(value); + } + } + } + } + + async _read(buffer) { + // type id + this._id = buffer.readInteger(); + // type name + const BinaryReader = require('./BinaryReader'); + this._name = await BinaryReader.readObject(buffer); + // affinity key field name + await BinaryReader.readObject(buffer); + // fields count + const fieldsCount = buffer.readInteger(); + // fields + let field; + for (let i = 0; i < fieldsCount; i++) { + field = new BinaryField(null, null); + await field._read(buffer); + this.setField(field); + } + await this._readEnum(buffer); + // schemas count + const schemasCount = buffer.readInteger(); + // schemas + let schema; + for (let i = 0; i < schemasCount; i++) { + schema = new BinarySchema(); + await schema._read(buffer); + this.addSchema(schema); + } + } + + async _readEnum(buffer) { + const BinaryReader = require('./BinaryReader'); + this._isEnum = buffer.readBoolean(); + if (this._isEnum) { + const valuesCount = buffer.readInteger(); + this._enumValues = new Array(valuesCount); + for (let i = 0; i < valuesCount; i++) { + this._enumValues[i] = [await BinaryReader.readObject(buffer), buffer.readInteger()]; + } + } + } +} + +/** FNV1 hash offset basis. */ +const FNV1_OFFSET_BASIS = 0x811C9DC5; +/** FNV1 hash prime. */ +const FNV1_PRIME = 0x01000193; + +class BinarySchema { + constructor() { + this._id = BinarySchema._schemaInitialId(); + this._fieldIds = new Set(); + this._isValid = true; + } + + get id() { + return this._id; + } + + get fieldIds() { + return [...this._fieldIds]; + } + + finalize() { + if (!this._isValid) { + this._id = BinarySchema._schemaInitialId(); + for (let fieldId of this._fieldIds) { + this._id = BinarySchema._updateSchemaId(this._id, fieldId); + } + this._isValid = true; + } + } + + clone() { + const result = new BinarySchema(); + result._id = this._id; + result._fieldIds = new Set(this._fieldIds); + result._isValid = this._isValid; + return result; + } + + addField(fieldId) { + if (!this.hasField(fieldId)) { + this._fieldIds.add(fieldId); + if (this._isValid) { + this._id = BinarySchema._updateSchemaId(this._id, fieldId); + } + } + } + + removeField(fieldId) { + if (this._fieldIds.delete(fieldId)) { + this._isValid = false; + } + } + + hasField(fieldId) { + return this._fieldIds.has(fieldId); + } + + static _schemaInitialId() { + return FNV1_OFFSET_BASIS | 0; + } + + static _updateSchemaId(schemaId, fieldId) { + schemaId = schemaId ^ (fieldId & 0xFF); + schemaId = schemaId * FNV1_PRIME; + schemaId |= 0; + schemaId = schemaId ^ ((fieldId >> 8) & 0xFF); + schemaId = schemaId * FNV1_PRIME; + schemaId |= 0; + schemaId = schemaId ^ ((fieldId >> 16) & 0xFF); + schemaId = schemaId * FNV1_PRIME; + schemaId |= 0; + schemaId = schemaId ^ ((fieldId >> 24) & 0xFF); + schemaId = schemaId * FNV1_PRIME; + schemaId |= 0; + + return schemaId; + } + + async _write(buffer) { + this.finalize(); + // schema id + buffer.writeInteger(this._id); + // fields count + buffer.writeInteger(this._fieldIds.size); + // field ids + for (let fieldId of this._fieldIds) { + buffer.writeInteger(fieldId); + } + } + + async _read(buffer) { + // schema id + this._id = buffer.readInteger(); + // fields count + const fieldsCount = buffer.readInteger(); + // field ids + for (let i = 0; i < fieldsCount; i++) { + this._fieldIds.add(buffer.readInteger()); + } + } +} + +class BinaryField { + constructor(name, typeCode) { + this._name = name; + this._id = BinaryField._calculateId(name); + this._typeCode = typeCode; + } + + get id() { + return this._id; + } + + get name() { + return this._name; + } + + get typeCode() { + return this._typeCode; + } + + static _calculateId(name) { + return BinaryUtils.hashCodeLowerCase(name); + } + + async _write(buffer) { + // field name + await BinaryWriter.writeString(buffer, this._name); + // type code + buffer.writeInteger(this._typeCode); + // field id + buffer.writeInteger(this._id); + } + + async _read(buffer) { + const BinaryReader = require('./BinaryReader'); + // field name + this._name = await BinaryReader.readObject(buffer); + // type code + this._typeCode = buffer.readInteger(); + // field id + this._id = buffer.readInteger(); + } +} + +class BinaryTypeBuilder { + + static fromTypeName(typeName) { + let result = new BinaryTypeBuilder(); + result._init(typeName); + return result; + } + + static async fromTypeId(typeId, schemaId, hasSchema) { + let result = new BinaryTypeBuilder(); + if (hasSchema) { + let type = await BinaryTypeStorage.getEntity().getType(typeId, schemaId); + if (type) { + result._type = type; + result._schema = type.getSchema(schemaId); + if (!result._schema) { + throw Errors.IgniteClientError.serializationError( + false, Util.format('schema id "%d" specified for complex object of type "%s" not found', + schemaId, type.name)); + } + result._fromStorage = true; + return result; + } + } + result._init(null); + result._type._id = typeId; + return result; + } + + static fromObject(jsObject, complexObjectType = null) { + if (complexObjectType) { + return BinaryTypeBuilder.fromComplexObjectType(complexObjectType, jsObject); + } + else { + const result = new BinaryTypeBuilder(); + result._fromComplexObjectType(new ComplexObjectType(jsObject), jsObject); + return result; + } + } + + static fromComplexObjectType(complexObjectType, jsObject) { + let result = new BinaryTypeBuilder(); + const typeInfo = BinaryTypeStorage.getEntity().getByComplexObjectType(complexObjectType); + if (typeInfo) { + result._type = typeInfo[0]; + result._schema = typeInfo[1]; + result._fromStorage = true; + } + else { + result._fromComplexObjectType(complexObjectType, jsObject); + BinaryTypeStorage.getEntity().setByComplexObjectType(complexObjectType, result._type, result._schema); + } + return result; + } + + getTypeId() { + return this._type.id; + } + + getTypeName() { + return this._type.name; + } + + getSchemaId() { + return this._schema.id; + } + + getFields() { + return this._type.fields; + } + + getField(fieldId) { + return this._type._fields.get(fieldId); + } + + setField(fieldName, fieldTypeCode = null) { + const fieldId = BinaryField._calculateId(fieldName); + if (!this._type.hasField(fieldId) || !this._schema.hasField(fieldId) || + this._type.getField(fieldId).typeCode !== fieldTypeCode) { + this._beforeModify(); + this._type.setField(new BinaryField(fieldName, fieldTypeCode)); + this._schema.addField(fieldId); + } + } + + removeField(fieldName) { + const fieldId = BinaryField._calculateId(fieldName); + if (this._type.hasField(fieldId)) { + this._beforeModify(); + this._type.removeField(fieldId); + this._schema.removeField(fieldId); + } + } + + async finalize() { + this._schema.finalize(); + await BinaryTypeStorage.getEntity().addType(this._type, this._schema); + } + + constructor() { + this._type = null; + this._schema = null; + this._fromStorage = false; + } + + _fromComplexObjectType(complexObjectType, jsObject) { + this._init(complexObjectType._typeName); + if (complexObjectType._template) { + this._setFields(complexObjectType, complexObjectType._template, jsObject); + } + } + + _init(typeName) { + this._type = new BinaryType(typeName); + this._schema = new BinarySchema(); + } + + _beforeModify() { + if (this._fromStorage) { + this._type = this._type.clone(); + this._schema = this._schema.clone(); + this._fromStorage = false; + } + } + + _setFields(complexObjectType, objectTemplate, jsObject) { + let fieldType; + for (let fieldName of BinaryUtils.getJsObjectFieldNames(objectTemplate)) { + fieldType = complexObjectType._getFieldType(fieldName); + if (!fieldType && jsObject[fieldName]) { + fieldType = BinaryUtils.calcObjectType(jsObject[fieldName]); + } + this.setField(fieldName, BinaryUtils.getTypeCode(fieldType)); + } + } +} + +module.exports = BinaryType; +module.exports.BinaryField = BinaryField; +module.exports.BinaryTypeBuilder = BinaryTypeBuilder; http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryTypeStorage.js ---------------------------------------------------------------------- diff --git a/modules/platforms/nodejs/lib/internal/BinaryTypeStorage.js b/modules/platforms/nodejs/lib/internal/BinaryTypeStorage.js new file mode 100644 index 0000000..d79156b --- /dev/null +++ b/modules/platforms/nodejs/lib/internal/BinaryTypeStorage.js @@ -0,0 +1,112 @@ +/* + * 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 Errors = require('../Errors'); +const BinaryUtils = require('./BinaryUtils'); + +class BinaryTypeStorage { + + static getEntity() { + if (!BinaryTypeStorage._entity) { + throw Errors.IgniteClientError.internalError(); + } + return BinaryTypeStorage._entity; + } + + static createEntity(socket) { + BinaryTypeStorage._entity = new BinaryTypeStorage(socket); + } + + async addType(binaryType, binarySchema) { + const typeId = binaryType.id; + const schemaId = binarySchema.id; + let storageType = this._types.get(typeId); + if (!storageType || !storageType.hasSchema(schemaId)) { + binaryType.addSchema(binarySchema); + if (!storageType) { + this._types.set(typeId, binaryType); + storageType = binaryType; + } + else { + storageType.merge(binaryType, binarySchema); + } + await this._putBinaryType(binaryType); + } + } + + async getType(typeId, schemaId = null) { + let storageType = this._types.get(typeId); + if (!storageType || schemaId && !storageType.hasSchema(schemaId)) { + storageType = await this._getBinaryType(typeId); + if (storageType) { + this._types.set(storageType.id, storageType); + } + } + return storageType; + } + + getByComplexObjectType(complexObjectType) { + return this._complexObjectTypes.get(complexObjectType); + } + + setByComplexObjectType(complexObjectType, type, schema) { + if (!this._complexObjectTypes.has(complexObjectType)) { + this._complexObjectTypes.set(complexObjectType, [type, schema]); + } + } + + /** Private methods */ + + constructor(socket) { + this._socket = socket; + this._types = new Map(); + this._complexObjectTypes = new Map(); + } + + async _getBinaryType(typeId) { + const BinaryType = require('./BinaryType'); + let binaryType = new BinaryType(null); + binaryType._id = typeId; + await this._socket.send( + BinaryUtils.OPERATION.GET_BINARY_TYPE, + async (payload) => { + payload.writeInteger(typeId); + }, + async (payload) => { + const exist = payload.readBoolean(); + if (exist) { + await binaryType._read(payload); + } + else { + binaryType = null; + } + }); + return binaryType; + } + + async _putBinaryType(binaryType) { + await this._socket.send( + BinaryUtils.OPERATION.PUT_BINARY_TYPE, + async (payload) => { + await binaryType._write(payload); + }); + } +} + +module.exports = BinaryTypeStorage;
