diff --git a/.gitignore b/.gitignore index 5aae9fa8f71f816a26b7004989082e4fe9d70185..e71b3692d76959bf971a04643cc14dc7bc7f003d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ debs/ rpms/ *.pyc *.tmp +src/connector/nodejs/node_modules/ +src/connector/nodejs/out/ diff --git a/src/connector/nodejs/nodetaos/cinterface.js b/src/connector/nodejs/nodetaos/cinterface.js index dc9fc2f4f1879d4fddf62fbe1270c6e1b167ad6c..2a234d0862dde67144ba0300282b3de593f21cc3 100644 --- a/src/connector/nodejs/nodetaos/cinterface.js +++ b/src/connector/nodejs/nodetaos/cinterface.js @@ -1,8 +1,13 @@ +/** + * C Interface with TDengine Module + * @module CTaosInterface + */ + const ref = require('ref'); const ffi = require('ffi'); const ArrayType = require('ref-array'); const Struct = require('ref-struct'); -const fieldTypes = require('./constants'); +const FieldTypes = require('./constants'); const errors = require ('./error') module.exports = CTaosInterface; @@ -145,18 +150,18 @@ function convertNchar(data, num_of_rows, nbytes = 0, offset = 0, micro=false) { return res; } -//Object with all the relevant converters from pblock data to javascript data +// Object with all the relevant converters from pblock data to javascript readable data let convertFunctions = { - [fieldTypes.C_BOOL] : convertBool, - [fieldTypes.C_TINYINT] : convertTinyint, - [fieldTypes.C_SMALLINT] : convertSmallint, - [fieldTypes.C_INT] : convertInt, - [fieldTypes.C_BIGINT] : convertBigint, - [fieldTypes.C_FLOAT] : convertFloat, - [fieldTypes.C_DOUBLE] : convertDouble, - [fieldTypes.C_BINARY] : convertBinary, - [fieldTypes.C_TIMESTAMP] : convertTimestamp, - [fieldTypes.C_NCHAR] : convertNchar + [FieldTypes.C_BOOL] : convertBool, + [FieldTypes.C_TINYINT] : convertTinyint, + [FieldTypes.C_SMALLINT] : convertSmallint, + [FieldTypes.C_INT] : convertInt, + [FieldTypes.C_BIGINT] : convertBigint, + [FieldTypes.C_FLOAT] : convertFloat, + [FieldTypes.C_DOUBLE] : convertDouble, + [FieldTypes.C_BINARY] : convertBinary, + [FieldTypes.C_TIMESTAMP] : convertTimestamp, + [FieldTypes.C_NCHAR] : convertNchar } // Define TaosField structure @@ -168,7 +173,12 @@ TaosField.fields.name.type.size = 64; TaosField.defineProperty('bytes', ref.types.short); TaosField.defineProperty('type', ref.types.char); -//The C interface with the Taos TSDB +/** + * Constructor for the C interface with TDengine + * @constructor + * @param {Object} config - Configuration options for the interface + * @return {CTaosInterface} + */ function CTaosInterface (config = null, pass = false) { ref.types.char_ptr = ref.refType(ref.types.char); ref.types.void_ptr = ref.refType(ref.types.void); @@ -208,7 +218,7 @@ function CTaosInterface (config = null, pass = false) { } else { try { - this._config = ref.allocCString(config);; + this._config = ref.allocCString(config); } catch(err){ throw "Attribute Error: config is expected as a str"; @@ -219,6 +229,7 @@ function CTaosInterface (config = null, pass = false) { } this.libtaos.taos_init(); } + return this; } CTaosInterface.prototype.config = function config() { return this._config; @@ -298,7 +309,7 @@ CTaosInterface.prototype.fetchBlock = function fetchBlock(result, fields) { if (num_of_rows == 0) { return {block:null, num_of_rows:0}; } - let isMicro = (this.libtaos.taos_result_precision(result) == fieldTypes.C_TIMESTAMP_MICRO) + let isMicro = (this.libtaos.taos_result_precision(result) == FieldTypes.C_TIMESTAMP_MICRO) let blocks = new Array(fields.length); blocks.fill(null); num_of_rows = Math.abs(num_of_rows); diff --git a/src/connector/nodejs/nodetaos/connection.js b/src/connector/nodejs/nodetaos/connection.js index d34aa92da0c9781386dd7b84a91ab1d64d5e296a..c9f9f3ee0eff7bab658a6329147bcff355dd384f 100644 --- a/src/connector/nodejs/nodetaos/connection.js +++ b/src/connector/nodejs/nodetaos/connection.js @@ -2,11 +2,14 @@ const TDengineCursor = require('./cursor') const CTaosInterface = require('./cinterface') module.exports = TDengineConnection; -/* - * TDengine Connection object - * @param {Object.} options - Options for configuring the connection with TDengine +/** + * TDengine Connection Class + * @param {object} options - Options for configuring the connection with TDengine * @return {TDengineConnection} - * + * @class TDengineConnection + * @constructor + * @example + * var conn = new TDengineConnection({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0}) * */ function TDengineConnection(options) { @@ -18,11 +21,15 @@ function TDengineConnection(options) { this._port = 0; this._config = null; this._chandle = null; - this.config(options) + this._configConn(options) return this; } - -TDengineConnection.prototype.config = function config(options) { +/** + * Configure the connection to TDengine + * @private + * @memberof TDengineConnection + */ +TDengineConnection.prototype._configConn = function _configConn(options) { if (options['host']) { this._host = options['host']; } @@ -44,10 +51,14 @@ TDengineConnection.prototype.config = function config(options) { this._chandle = new CTaosInterface(this._config); this._conn = this._chandle.connect(this._host, this._user, this._password, this._database, this._port); } - +/** Close the connection to TDengine */ TDengineConnection.prototype.close = function close() { - return this._chandle.close(this._conn); + this._chandle.close(this._conn); } +/** + * Initialize a new cursor to interact with TDengine with + * @return {TDengineCursor} + */ TDengineConnection.prototype.cursor = function cursor() { //Pass the connection object to the cursor return new TDengineCursor(this); @@ -58,7 +69,11 @@ TDengineConnection.prototype.commit = function commit() { TDengineConnection.prototype.rollback = function rollback() { return this; } -TDengineConnection.prototype.clear_result_set = function clear_result_set() { +/** + * Clear the results from connector + * @private + */ +TDengineConnection.prototype._clearResultSet = function _clearResultSet() { var result = this._chandle.useResult(this._conn).result; if (result) { this._chandle.freeResult(result) diff --git a/src/connector/nodejs/nodetaos/constants.js b/src/connector/nodejs/nodetaos/constants.js index bb65b6d91c0c67845ef795e24d28b1d87056bd7b..c50594bc427ab4b7f1e64eef977d40c279b88e6f 100644 --- a/src/connector/nodejs/nodetaos/constants.js +++ b/src/connector/nodejs/nodetaos/constants.js @@ -1,10 +1,25 @@ -/* - * TDengine Field Types +/** + * TDengine Field Types and their type codes + * @typedef {Object} FieldTypes + * @property {number} C_NULL - Null + * @property {number} C_BOOL - Boolean. Note, 0x02 is the C_BOOL_NULL value. + * @property {number} C_TINYINT - Tiny Int, values in the range [-2^7+1, 2^7-1]. Note, -2^7 has been used as the C_TINYINT_NULL value + * @property {number} C_SMALLINT - Small Int, values in the range [-2^15+1, 2^15-1]. Note, -2^15 has been used as the C_SMALLINT_NULL value + * @property {number} C_INT - Int, values in the range [-2^31+1, 2^31-1]. Note, -2^31 has been used as the C_INT_NULL value + * @property {number} C_BIGINT - Big Int, values in the range [-2^59, 2^59]. + * @property {number} C_FLOAT - Float, values in the range [-3.4E38, 3.4E38], accurate up to 6-7 decimal places. + * @property {number} C_DOUBLE - Double, values in the range [-1.7E308, 1.7E308], accurate up to 15-16 decimal places. + * @property {number} C_BINARY - Binary, encoded in utf-8. + * @property {number} C_TIMESTAMP - Timestamp in format "YYYY:MM:DD HH:MM:SS.MMM". Measured in number of milliseconds passed after + 1970-01-01 08:00:00.000 GMT. + * @property {number} C_NCHAR - NChar field type encoded in ASCII, a wide string. * * + * + * @property {number} C_TIMESTAMP_MILLI - The code for millisecond timestamps, as returned by libtaos.taos_result_precision(result). + * @property {number} C_TIMESTAMP_MICRO - The code for microsecond timestamps, as returned by libtaos.taos_result_precision(result). */ module.exports = { - // Type Code C_NULL : 0, C_BOOL : 1, C_TINYINT : 2, @@ -23,7 +38,10 @@ module.exports = { C_SMALLINT_NULL : -32768, C_INT_NULL : -2147483648, C_BIGINT_NULL : -9223372036854775808, - + C_FLOAT_NULL : 2146435072, + C_DOUBLE_NULL : -9223370937343148032, + C_NCHAR_NULL : 4294967295, + C_BINARY_NULL : 255, C_TIMESTAMP_MILLI : 0, C_TIMESTAMP_MICRO : 1, } diff --git a/src/connector/nodejs/nodetaos/cursor.js b/src/connector/nodejs/nodetaos/cursor.js index 8089ab13e24e9007345fc17be0d740391db0698a..d168e9d0cecc482848be6e500838202ec37a7ed0 100644 --- a/src/connector/nodejs/nodetaos/cursor.js +++ b/src/connector/nodejs/nodetaos/cursor.js @@ -1,72 +1,104 @@ const CTaosInterface = require('./cinterface') const errors = require ('./error') +const TaosQuery = require('./taosquery') module.exports = TDengineCursor; +/** + * Constructor for a TDengine Cursor + * @class TDengineCursor + * @constructor + * @param {TDengineConnection} - The TDengine Connection this cursor uses to interact with TDengine + * @property {data} - Latest retrieved data from query execution. It is an empty array by default + * @property {fieldNames} - Array of the field names in order from left to right of the latest data retrieved + */ function TDengineCursor(connection=null) { this._description = null; this._rowcount = -1; this._connection = null; this._result = null; this._fields = null; - this.data = null; + this.data = []; this.fieldNames = null; - this.chandle = new CTaosInterface(null, true); //pass through, just need library loaded. + this._chandle = new CTaosInterface(null, true); //pass through, just need library loaded. if (connection != null) { this._connection = connection } } +/** + * Get the description of the latest query + * @return {string} Description + */ TDengineCursor.prototype.description = function description() { return this._description; } +/** + * Get the row counts of the latest query + * @return {number} Rowcount + */ TDengineCursor.prototype.rowcount = function rowcount() { return this._rowcount; } TDengineCursor.prototype.callproc = function callproc() { return; } +/** + * Close the cursor by setting its connection to null and freeing results from the connection and resetting the results it has stored + * @return {boolean} Whether or not the cursor was succesfully closed + */ TDengineCursor.prototype.close = function close() { if (this._connection == null) { return false; } - this._connection.clear_result_set(); + this._connection._clearResultSet(); this._reset_result(); this._connection = null; return true; } -TDengineCursor.prototype.execute = function execute(operation, params=null) { +/** + * + * @return {TaosQuery} - A TaosQuery object + */ +TDengineCursor.prototype.query = function query(operation) { + return new TaosQuery(operation, this); +} + +/** + * Execute a query. Also stores all the field names returned from the query into cursor.fieldNames; + * @param {string} operation - The query operation to execute in the taos shell + * @param {number} verbose - A verbosity of 0 silences any logs from this function. A verbosity of 1 means this function will log query statues + * @return {number | buffer} Number of affected rows or a buffer that points to the results of the query + */ +TDengineCursor.prototype.execute = function execute(operation, verbose) { if (operation == undefined) { return null; } if (this._connection == null) { throw new errors.ProgrammingError('Cursor is not connected'); } - this._connection.clear_result_set(); + this._connection._clearResultSet(); this._reset_result(); let stmt = operation; - if (params != null) { - //why pass? - } - res = this.chandle.query(this._connection._conn, stmt); + res = this._chandle.query(this._connection._conn, stmt); if (res == 0) { - let fieldCount = this.chandle.fieldsCount(this._connection._conn); + let fieldCount = this._chandle.fieldsCount(this._connection._conn); if (fieldCount == 0) { - return this.chandle.affectedRows(this._connection._conn); //return num of affected rows, common with insert, use statements + let response = "Query OK" + return this._chandle.affectedRows(this._connection._conn); //return num of affected rows, common with insert, use statements } else { - let resAndField = this.chandle.useResult(this._connection._conn, fieldCount) + let resAndField = this._chandle.useResult(this._connection._conn, fieldCount) this._result = resAndField.result; this._fields = resAndField.fields; this.fieldNames = resAndField.fields.map(fieldData => fieldData.name); - return this._handle_result(); //return a pointer + return this._handle_result(); //return a pointer to the result } } else { - throw new errors.ProgrammingError(this.chandle.errStr(this._connection._conn)) + throw new errors.ProgrammingError(this._chandle.errStr(this._connection._conn)) } - } TDengineCursor.prototype.executemany = function executemany() { @@ -77,6 +109,15 @@ TDengineCursor.prototype.fetchone = function fetchone() { TDengineCursor.prototype.fetchmany = function fetchmany() { } +/** + * Fetches all results from a query and also stores results into cursor.data + * + * @return {Array} The resultant array, with entries corresponding to each retreived row from the query results, sorted in + * order by the field name ordering in the table. + * @example + * cursor.execute('select * from db.table'); + * var data = cursor.fetchall() + */ TDengineCursor.prototype.fetchall = function fetchall() { if (this._result == null || this._fields == null) { throw new errors.OperationalError("Invalid use of fetchall, either result or fields from query are null"); @@ -86,7 +127,7 @@ TDengineCursor.prototype.fetchall = function fetchall() { let k = 0; while(true) { k+=1; - let blockAndRows = this.chandle.fetchBlock(this._result, this._fields); + let blockAndRows = this._chandle.fetchBlock(this._result, this._fields); let block = blockAndRows.blocks; let num_of_rows = blockAndRows.num_of_rows; @@ -101,9 +142,9 @@ TDengineCursor.prototype.fetchall = function fetchall() { } } } - this._connection.clear_result_set(); + this._connection._clearResultSet(); this.data = data; - return data; //data[i] returns the ith row, with all the data + return data; } TDengineCursor.prototype.nextset = function nextset() { return; @@ -119,7 +160,7 @@ TDengineCursor.prototype._reset_result = function _reset_result() { this._rowcount = -1; this._result = null; this._fields = null; - this.data = null; + this.data = []; this.fieldNames = null; } TDengineCursor.prototype._handle_result = function _handle_result() { diff --git a/src/connector/nodejs/nodetaos/taosquery.js b/src/connector/nodejs/nodetaos/taosquery.js new file mode 100644 index 0000000000000000000000000000000000000000..8f5b82c06135cfea6d1c2b4959dfe32f9e282caf --- /dev/null +++ b/src/connector/nodejs/nodetaos/taosquery.js @@ -0,0 +1,52 @@ +module.exports = TaosQuery; + + +/** + * Constructor for TaosQuery object + * @class TaosQuery + * @constructor + * @param {string} query - Query to construct object from + * @param {TDengineCursor} - The cursor from which this query will execute from + */ +function TaosQuery(query = "", cursor = null) { + this._query = query; + this._cursor = cursor; +} + +/** + * Executes the query object and returns a Promise object + * @memberof TaosQuery + * @param {function} resultCallback - A callback function that takes the results as input + * @param {function} metaCallback - A callback function that takes the metadata or fields as input + * @return {Promise} A promise that evaluates into an object with keys "results" and "fields" + */ +TaosQuery.prototype.execute = async function execute(resultCallback, metaCallback) { + var taosQuery = this; //store the current instance of taosQuery to avoid async issues? + var executionPromise = new Promise(function(resolve, reject) { + let results = []; + try { + taosQuery._cursor.execute(taosQuery._query); + if (taosQuery._cursor._result != null){ + results = taosQuery._cursor.fetchall(); + } + if (metaCallback) metaCallback(taosQuery._cursor._fields); + if (resultCallback) resultCallback(results); + } + catch(err) { + reject(err); + } + resolve({results:results, fields:taosQuery._cursor._fields}) + + }); + return executionPromise; +} + +/** + * + * @param {...args} args - A + */ +TaosQuery.prototype.bind = function bind(...args) { + args.forEach(function(arg) { + + }); +} diff --git a/src/connector/nodejs/nodetaos/taosresult.js b/src/connector/nodejs/nodetaos/taosresult.js new file mode 100644 index 0000000000000000000000000000000000000000..5d736674285a5d46f4d81425b693fe95f2f23177 --- /dev/null +++ b/src/connector/nodejs/nodetaos/taosresult.js @@ -0,0 +1,25 @@ +/** + * TaosResult + * @module TaosResult + */ + + module.exports = TaosResult; +/** + * Constructor for a TaosResult object; + * @class TaosResult + * @constructor + * @param {Array} - Array of result rows + * @param {Array} - Array of field meta data + * @return {TaosResult} + */ +function TaosResult(result, fields) { + this.result = result; + this.rowcount = this.result.length; + this.fields = parseFields(fields); +} + +TaosResult.prototype.parseFields = function parseFields(fields) { + fields.map(function(field) { + return {} + }) +} diff --git a/src/connector/nodejs/package-lock.json b/src/connector/nodejs/package-lock.json index 230689532ac55b04b86f67ca1431314a139c75a1..38c189f410c8d0f15ae4449c45c69911a85d0290 100644 --- a/src/connector/nodejs/package-lock.json +++ b/src/connector/nodejs/package-lock.json @@ -1,5 +1,5 @@ { - "name": "tdconnector", + "name": "td-connector", "version": "1.0.5", "lockfileVersion": 1, "requires": true, @@ -144,22 +144,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "ctypes": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/ctypes/-/ctypes-0.0.1.tgz", - "integrity": "sha1-/C7F5ZmRwG9mNksh7yxy/SkQnLE=", - "requires": { - "array-index": "^1.0.0", - "debug": "^2.2.0", - "es6-weak-map": "^2.0.1", - "ffi": "^2.0.0", - "function-class": "^1.1.0", - "ref": "^1.3.1", - "ref-array": "^1.1.2", - "ref-struct": "^1.0.2", - "setprototypeof": "^1.0.0" - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -238,17 +222,6 @@ "es5-ext": "~0.10.14" } }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -321,25 +294,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "function-class": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/function-class/-/function-class-1.1.0.tgz", - "integrity": "sha1-yCK8J5x4py11je/y4kzXwPtYWLo=", - "requires": { - "es6-symbol": "^3.0.2", - "function-name": "^1.0.0", - "setprototypeof": "^1.0.0" - } - }, - "function-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/function-name/-/function-name-1.0.0.tgz", - "integrity": "sha1-r+4YP9eh3JIi1fZae/htpYNd90U=", - "requires": { - "bindings": "^1.2.1", - "nan": "2" - } - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -737,11 +691,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -845,11 +794,6 @@ "punycode": "^2.1.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/src/connector/nodejs/readme.md b/src/connector/nodejs/readme.md index e45443cfa0916a09ddff7b8bd4c653d7f48eb633..e5e866a706aae409f8e57fddb420541248540757 100644 --- a/src/connector/nodejs/readme.md +++ b/src/connector/nodejs/readme.md @@ -1,4 +1,6 @@ # TDengine Node.js connector +[![minzip](https://img.shields.io/bundlephobia/minzip/td-connector.svg)](https://github.com/taosdata/TDengine/tree/master/src/connector/nodejs) [![NPM](https://img.shields.io/npm/l/td-connector.svg)](https://github.com/taosdata/TDengine/#what-is-tdengine) + This is the Node.js library that lets you connect to [TDengine](https://www.github.com/taosdata/tdengine). ## Installation @@ -9,7 +11,7 @@ To get started, just type in the following to install the connector through [npm npm install td-connector ``` -To interact with TDengine, we make use of the [node-gyp[(https://github.com/nodejs/node-gyp)] library. To install, you will need to install the following depending on platform (the following instructions are quoted from node-gyp) +To interact with TDengine, we make use of the [node-gyp](https://github.com/nodejs/node-gyp) library. To install, you will need to install the following depending on platform (the following instructions are quoted from node-gyp) ### On Unix @@ -23,25 +25,25 @@ To interact with TDengine, we make use of the [node-gyp[(https://github.com/node - Xcode - - You also need to install the + - You also need to install the ``` Command Line Tools ``` - via Xcode. You can find this under the menu + via Xcode. You can find this under the menu ``` Xcode -> Preferences -> Locations ``` - (or by running + (or by running ``` xcode-select --install ``` - in your Terminal) + in your Terminal) - This step will install `gcc` and the related toolchain containing `make` @@ -230,4 +232,4 @@ Please follow the [contribution guidelines](https://github.com/taosdata/TDengine ## License -[GNU AGPL v3.0](http://www.gnu.org/licenses/agpl-3.0.html) \ No newline at end of file +[GNU AGPL v3.0](http://www.gnu.org/licenses/agpl-3.0.html)