提交 e257c02a 编写于 作者: S StoneT2000

Updated nodejs connector to 1.1.1

Implemented higher level API to make it more modular
Using promises and callbacks
Argument binding is now available e.g query(select ? from ?).bind("*", "table")
上级 129d128d
......@@ -8,15 +8,16 @@ const ffi = require('ffi');
const ArrayType = require('ref-array');
const Struct = require('ref-struct');
const FieldTypes = require('./constants');
const errors = require ('./error')
const errors = require ('./error');
const TaosObjects = require('./taosobjects');
module.exports = CTaosInterface;
function convertMillisecondsToDatetime(time) {
return new Date(time);
return new TaosObjects.TaosTimestamp(time);
}
function convertMicrosecondsToDatetime(time) {
return new Date(time * 0.001);
return new TaosObjects.TaosTimestamp(time * 0.001);
}
function convertTimestamp(data, num_of_rows, nbytes = 0, offset = 0, micro=false) {
......@@ -174,10 +175,12 @@ TaosField.defineProperty('bytes', ref.types.short);
TaosField.defineProperty('type', ref.types.char);
/**
* Constructor for the C interface with TDengine
* @constructor
*
* @param {Object} config - Configuration options for the interface
* @return {CTaosInterface}
* @class CTaosInterface
* @classdesc The CTaosInterface is the interface through which Node.JS communicates data back and forth with TDengine. It is not advised to
* access this class directly and use it unless you understand what these functions do.
*/
function CTaosInterface (config = null, pass = false) {
ref.types.char_ptr = ref.refType(ref.types.char);
......@@ -280,8 +283,7 @@ CTaosInterface.prototype.close = function close(connection) {
console.log("Connection is closed");
}
CTaosInterface.prototype.query = function query(connection, sql) {
let res = this.libtaos.taos_query(connection, ref.allocCString(sql));
return res;
return this.libtaos.taos_query(connection, ref.allocCString(sql));
}
CTaosInterface.prototype.affectedRows = function affectedRows(connection) {
return this.libtaos.taos_affected_rows(connection);
......
......@@ -9,6 +9,7 @@ module.exports = TDengineConnection;
* @class TDengineConnection
* @constructor
* @example
* //Initialize a new connection
* var conn = new TDengineConnection({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0})
*
*/
......
/**
* Contains the the definitions/values assigned to various field types
* @module FieldTypes
*/
/**
* TDengine Field Types and their type codes
* @typedef {Object} FieldTypes
* @global
* @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
......@@ -44,4 +49,28 @@ module.exports = {
C_BINARY_NULL : 255,
C_TIMESTAMP_MILLI : 0,
C_TIMESTAMP_MICRO : 1,
getType,
}
const typeCodesToName = {
0 : 'Null',
1 : 'Boolean',
2 : 'Tiny Int',
3 : 'Small Int',
4 : 'Int',
5 : 'Big Int',
6 : 'Float',
7 : 'Double',
8 : 'Binary',
9 : 'Timestamp',
10 : 'Nchar',
}
/**
* @function
* @param {number} typecode - The code to get the name of the type for
* @return {string} Name of the field type
*/
function getType(typecode) {
return typeCodesToName[typecode];
}
require('./globalfunc.js')
const CTaosInterface = require('./cinterface')
const errors = require ('./error')
const TaosQuery = require('./taosquery')
const { PerformanceObserver, performance } = require('perf_hooks');
module.exports = TDengineCursor;
/**
* Constructor for a TDengine Cursor
* @typedef {Object} Buffer - A Node.JS buffer. Please refer to {@link https://nodejs.org/api/buffer.html} for more details
* @global
*/
/**
* @class TDengineCursor
* @constructor
* @classdesc The TDengine Cursor works directly with the C Interface which works with TDengine. It refrains from
* returning parsed data and majority of functions return the raw data such as cursor.fetchall() as compared to the TaosQuery class which
* has functions that "prettify" the data and add more functionality and can be used through cursor.query("your query"). Instead of
* promises, the class and its functions use callbacks.
* @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
* @property {fields} - Array of the field objects in order from left to right of the latest data retrieved
* @since 1.0.0
*/
function TDengineCursor(connection=null) {
this._description = null;
......@@ -18,7 +28,7 @@ function TDengineCursor(connection=null) {
this._result = null;
this._fields = null;
this.data = [];
this.fieldNames = null;
this.fields = null;
this._chandle = new CTaosInterface(null, true); //pass through, just need library loaded.
if (connection != null) {
this._connection = connection
......@@ -27,6 +37,7 @@ function TDengineCursor(connection=null) {
}
/**
* Get the description of the latest query
* @since 1.0.0
* @return {string} Description
*/
TDengineCursor.prototype.description = function description() {
......@@ -34,6 +45,7 @@ TDengineCursor.prototype.description = function description() {
}
/**
* Get the row counts of the latest query
* @since 1.0.0
* @return {number} Rowcount
*/
TDengineCursor.prototype.rowcount = function rowcount() {
......@@ -45,6 +57,7 @@ TDengineCursor.prototype.callproc = function callproc() {
/**
* 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
* @since 1.0.0
*/
TDengineCursor.prototype.close = function close() {
if (this._connection == null) {
......@@ -56,23 +69,38 @@ TDengineCursor.prototype.close = function close() {
return true;
}
/**
*
* @return {TaosQuery} - A TaosQuery object
* Create a TaosQuery object to perform a query to TDengine and retrieve data.
* @param {string} operation - The operation string to perform a query on
* @param {boolean} execute - Whether or not to immedietely perform the query. Default is false.
* @return {TaosQuery | Promise<TaosResult>} A TaosQuery object
* @example
* var query = cursor.query("select count(*) from meterinfo.meters");
* query.execute();
* @since 1.0.6
*/
TDengineCursor.prototype.query = function query(operation) {
return new TaosQuery(operation, this);
TDengineCursor.prototype.query = function query(operation, execute = false) {
return new TaosQuery(operation, this, execute);
}
/**
* 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
* Execute a query. Also stores all the field meta data returned from the query into cursor.fields. It is preferable to use cursor.query() to create
* queries and execute them instead of using the cursor object directly.
* @param {string} operation - The query operation to execute in the taos shell
* @param {Object} options - Execution options object. quiet : true turns off logging from queries
* @param {boolean} options.quiet - True if you want to surpress logging such as "Query OK, 1 row(s) ..."
* @param {function} callback - A callback function to execute after the query is made to TDengine
* @return {number | Buffer} Number of affected rows or a Buffer that points to the results of the query
* @since 1.0.0
*/
TDengineCursor.prototype.execute = function execute(operation, verbose) {
TDengineCursor.prototype.execute = function execute(operation, options, callback) {
if (operation == undefined) {
return null;
}
if (typeof options == 'function') {
callback = options;
}
if (typeof options != 'object') options = {}
if (this._connection == null) {
throw new errors.ProgrammingError('Cursor is not connected');
}
......@@ -80,18 +108,35 @@ TDengineCursor.prototype.execute = function execute(operation, verbose) {
this._reset_result();
let stmt = operation;
let time = 0;
const obs = new PerformanceObserver((items) => {
time = items.getEntries()[0].duration;
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
performance.mark('B');
res = this._chandle.query(this._connection._conn, stmt);
performance.measure('query', 'A', 'B');
if (res == 0) {
let fieldCount = this._chandle.fieldsCount(this._connection._conn);
if (fieldCount == 0) {
let response = "Query OK"
return this._chandle.affectedRows(this._connection._conn); //return num of affected rows, common with insert, use statements
let affectedRowCount = this._chandle.affectedRows(this._connection._conn);
let response = this._createAffectedResponse(affectedRowCount, time)
if (options['quiet'] != true) {
console.log(response);
}
wrapCB(callback);
return affectedRowCount; //return num of affected rows, common with insert, use statements
}
else {
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);
this.fields = resAndField.fields;
wrapCB(callback);
return this._handle_result(); //return a pointer to the result
}
}
......@@ -100,6 +145,12 @@ TDengineCursor.prototype.execute = function execute(operation, verbose) {
}
}
TDengineCursor.prototype._createAffectedResponse = function (num, time) {
return "Query OK, " + num + " row(s) affected (" + (time * 0.001).toFixed(8) + "s)";
}
TDengineCursor.prototype._createSetResponse = function (num, time) {
return "Query OK, " + num + " row(s) in set (" + (time * 0.001).toFixed(8) + "s)";
}
TDengineCursor.prototype.executemany = function executemany() {
}
......@@ -110,24 +161,47 @@ TDengineCursor.prototype.fetchmany = function fetchmany() {
}
/**
* Fetches all results from a query and also stores results into cursor.data
*
* @return {Array<Row>} The resultant array, with entries corresponding to each retreived row from the query results, sorted in
* Fetches all results from a query and also stores results into cursor.data. It is preferable to use cursor.query() to create
* queries and execute them instead of using the cursor object directly.
* @param {function} callback - callback function executing on the complete fetched data
* @return {Array<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.
* @since 1.0.0
* @example
* cursor.execute('select * from db.table');
* var data = cursor.fetchall()
* var data = cursor.fetchall(function(results) {
* results.forEach(row => console.log(row));
* })
*/
TDengineCursor.prototype.fetchall = function fetchall() {
TDengineCursor.prototype.fetchall = function fetchall(options, callback) {
if (this._result == null || this._fields == null) {
throw new errors.OperationalError("Invalid use of fetchall, either result or fields from query are null");
throw new errors.OperationalError("Invalid use of fetchall, either result or fields from query are null. First execute a query first");
}
let data = [];
this._rowcount = 0;
let k = 0;
//let nodetime = 0;
let time = 0;
const obs = new PerformanceObserver((items) => {
time += items.getEntries()[0].duration;
performance.clearMarks();
});
/*
const obs2 = new PerformanceObserver((items) => {
nodetime += items.getEntries()[0].duration;
performance.clearMarks();
});
obs2.observe({ entryTypes: ['measure'] });
performance.mark('nodea');
*/
while(true) {
k+=1;
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
let blockAndRows = this._chandle.fetchBlock(this._result, this._fields);
performance.mark('B');
performance.measure('query', 'A', 'B');
let block = blockAndRows.blocks;
let num_of_rows = blockAndRows.num_of_rows;
......@@ -137,13 +211,26 @@ TDengineCursor.prototype.fetchall = function fetchall() {
this._rowcount += num_of_rows;
for (let i = 0; i < num_of_rows; i++) {
data.push([]);
let rowBlock = new Array(this._fields.length);
for (let j = 0; j < this._fields.length; j++) {
data[data.length-1].push(block[j][i]);
rowBlock[j] = block[j][i];
}
data[data.length-1] = (rowBlock);
}
}
let response = this._createSetResponse(this._rowcount, time)
console.log(response);
this._connection._clearResultSet();
let fields = this.fields;
this._reset_result();
this.data = data;
this.fields = fields;
//performance.mark('nodeb');
//performance.measure('querynode', 'nodea', 'nodeb');
//console.log('nodetime: ' + nodetime/1000);
wrapCB(callback, data);
return data;
}
TDengineCursor.prototype.nextset = function nextset() {
......@@ -161,12 +248,12 @@ TDengineCursor.prototype._reset_result = function _reset_result() {
this._result = null;
this._fields = null;
this.data = [];
this.fieldNames = null;
this.fields = null;
}
TDengineCursor.prototype._handle_result = function _handle_result() {
this._description = [];
for (let field of this._fields) {
this._description.push([field.name, field.type, null, null, null, null, false]);
this._description.push([field.name, field.type]);
}
return this._result;
}
/*
Classes for exceptions
Note: All exceptions thrown by Node.js or the JavaScript runtime will be instances of Error.
https://nodejs.org/api/errors.html#errors_exceptions_vs_errors
*/
/**
* TDengine Error Class
* @ignore
*/
class TDError extends Error {
constructor(args) {
super(args)
this.name = "TDError";
}
}
/** Exception raised for important warnings like data truncations while inserting.
* @ignore
*/
class Warning extends Error {
// Exception raised for important warnings like data truncations while inserting.
constructor(args) {
super(args)
this.name = "Warning";
}
}
/** Exception raised for errors that are related to the database interface rather than the database itself.
* @ignore
*/
class InterfaceError extends TDError {
// Exception raised for errors that are related to the database interface rather than the database itself.
constructor(args) {
super(args)
this.name = "TDError.InterfaceError";
}
}
/** Exception raised for errors that are related to the database.
* @ignore
*/
class DatabaseError extends TDError {
// Exception raised for errors that are related to the database.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError";
}
}
/** Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range.
* @ignore
*/
class DataError extends DatabaseError {
// Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.DataError";
}
}
/** Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer
* @ignore
*/
class OperationalError extends DatabaseError {
// Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.OperationalError";
}
}
/** Exception raised when the relational integrity of the database is affected.
* @ignore
*/
class IntegrityError extends DatabaseError {
// Exception raised when the relational integrity of the database is affected.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.IntegrityError";
}
}
/** Exception raised when the database encounters an internal error.
* @ignore
*/
class InternalError extends DatabaseError {
// Exception raised when the database encounters an internal error.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.InternalError";
}
}
/** Exception raised for programming errors.
* @ignore
*/
class ProgrammingError extends DatabaseError {
// Exception raised for programming errors.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.ProgrammingError";
}
}
/** Exception raised in case a method or database API was used which is not supported by the database.
* @ignore
*/
class NotSupportedError extends DatabaseError {
// Exception raised in case a method or database API was used which is not supported by the database.
constructor(args) {
super(args)
this.name = "TDError.DatabaseError.NotSupportedError";
......
/* Wrap a callback, reduce code amount */
function wrapCB(callback,input) {
if (typeof callback === 'function') {
callback(input);
}
return;
}
global.wrapCB = wrapCB;
function toTaosTSString(date) {
date = new Date(date);
let tsArr = date.toISOString().split("T")
return tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1);
}
global.toTaosTSString = toTaosTSString;
const FieldTypes = require('./constants');
/**
* Various objects such as TaosRow and TaosColumn that help make parsing data easier
* @module TaosObjects
*
*/
/**
* The TaosRow object. Contains the data from a retrieved row from a database and functions that parse the data.
* @typedef {Object} TaosRow - A row of data retrieved from a table.
* @global
* @example
* var trow = new TaosRow(row);
* console.log(trow.data);
*/
function TaosRow (row) {
this.data = row;
this.length = row.length;
return this;
}
/**
* @typedef {Object} TaosField - A field/column's metadata from a table.
* @global
* @example
* var tfield = new TaosField(field);
* console.log(tfield.name);
*/
function TaosField(field) {
this._field = field;
this.name = field.name;
this.type = FieldTypes.getType(field.type);
return this;
}
/**
* A TaosTimestamp object, which is the standard date object with added functionality
* @global
* @memberof TaosObjects
* @param {Date} date - A Javascript date time object or the time in milliseconds past 1970-1-1 00:00:00.000
*/
class TaosTimestamp extends Date {
constructor(date) {
super(date);
this._type = 'TaosTimestamp';
}
/**
* @function Returns the date into a string usable by TDengine
* @return {string} A Taos Timestamp String
*/
toTaosString(){
let tsArr = this.toISOString().split("T")
return tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1);
}
}
module.exports = {TaosRow, TaosField, TaosTimestamp}
var TaosResult = require('./taosresult')
require('./globalfunc.js')
module.exports = TaosQuery;
/**
* Constructor for TaosQuery object
* @class TaosQuery
* @constructor
* @classdesc The TaosQuery class is one level above the TDengine Cursor in that it makes sure to generally return promises from functions, and wrap
* all data with objects such as wrapping a row of data with Taos Row. This is meant to enable an higher level API that allows additional
* functionality and save time whilst also making it easier to debug and enter less problems with the use of promises.
* @param {string} query - Query to construct object from
* @param {TDengineCursor} - The cursor from which this query will execute from
* @param {TDengineCursor} cursor - The cursor from which this query will execute from
* @param {boolean} execute - Whether or not to immedietely execute the query and fetch all results. Default is false.
* @return {TaosQuery}
* @since 1.0.6
*/
function TaosQuery(query = "", cursor = null) {
function TaosQuery(query = "", cursor = null, execute = false) {
this._query = query;
this._cursor = cursor;
if (execute == true) {
return this.execute();
}
return this;
}
/**
* Executes the query object and returns a Promise object
* Executes the query object and returns a Promise
* @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<results, fields>} A promise that evaluates into an object with keys "results" and "fields"
* @return {Promise<TaosResult>} A promise that resolves with a TaosResult object, or rejects with an error
* @since 1.0.6
*/
TaosQuery.prototype.execute = async function execute(resultCallback, metaCallback) {
TaosQuery.prototype.execute = async function execute() {
var taosQuery = this; //store the current instance of taosQuery to avoid async issues?
var executionPromise = new Promise(function(resolve, reject) {
let results = [];
let data = [];
let fields = [];
let result;
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);
if (taosQuery._cursor._fields) fields = taosQuery._cursor._fields;
if (taosQuery._cursor._result != null) data = taosQuery._cursor.fetchall();
result = new TaosResult(data, fields)
}
catch(err) {
reject(err);
}
resolve({results:results, fields:taosQuery._cursor._fields})
resolve(result)
});
return executionPromise;
}
/**
*
* @param {...args} args - A
* Bind arguments to the query and automatically parses them into the right format
* @param {array | ...args} args - A number of arguments to bind to each ? in the query
* @return {TaosQuery}
* @example
* // An example of binding a javascript date and a number to a query
* var query = cursor.query("select count(*) from meterinfo.meters where ts <= ? and areaid = ?").bind(new Date(), 3);
* var promise1 = query.execute();
* promise1.then(function(result) {
* result.pretty(); // Log the prettified version of the results.
* });
* @since 1.0.6
*/
TaosQuery.prototype.bind = function bind(...args) {
TaosQuery.prototype.bind = function bind(f, ...args) {
if (typeof f == 'object' && f.constructor.name != 'Array') args.unshift(f); //param is not an array object
else if (typeof f != 'object') args.unshift(f);
else { args = f; }
args.forEach(function(arg) {
});
if (arg.constructor.name == 'TaosTimestamp') arg = "\"" + arg.toTaosString() + "\"";
else if (arg.constructor.name == 'Date') arg = "\"" + toTaosTSString(arg) + "\"";
else if (typeof arg == 'string') arg = "\"" + arg + "\"";
this._query = this._query.replace(/\?/,arg);
}, this);
return this;
}
/**
* TaosResult
* @module TaosResult
*/
require('./globalfunc.js')
const TaosObjects = require('./taosobjects');
const TaosRow = TaosObjects.TaosRow;
const TaosField = TaosObjects.TaosField;
module.exports = TaosResult;
module.exports = TaosResult;
/**
* Constructor for a TaosResult object;
* @class TaosResult
* @constructor
* @param {Array<Row>} - Array of result rows
* @param {Array<Field>} - Array of field meta data
* @classdesc A TaosResult class consts of the row data and the fields metadata, all wrapped under various objects for higher functionality.
* @param {Array<TaosRow>} data - Array of result rows
* @param {Array<TaosField>} fields - Array of field meta data
* @property {Array<TaosRow>} data - Array of TaosRows forming the result data (this does not include field meta data)
* @property {Array<TaosField>} fields - Array of TaosFields forming the fields meta data array.
* @return {TaosResult}
* @since 1.0.6
*/
function TaosResult(result, fields) {
this.result = result;
this.rowcount = this.result.length;
this.fields = parseFields(fields);
function TaosResult(data, fields) {
this.data = data.map(row => new TaosRow(row));
this.rowcount = this.data.length;
this.fields = fields.map(field => new TaosField(field));
}
TaosResult.prototype.parseFields = function parseFields(fields) {
fields.map(function(field) {
return {}
})
return fields.map(function(field) {
return field;
});
}
/**
* Pretty print data and the fields meta data as if you were using the taos shell
* @memberof TaosResult
* @function pretty
*/
TaosResult.prototype.pretty = function pretty() {
// Pretty print of the fields and the data;
let fieldsStr = "";
let sizing = [];
this.fields.forEach((field,i) => {
if (field._field.type == 8 || field._field.type == 10){
sizing.push(Math.max(field.name.length, field._field.bytes));
}
else {
sizing.push(Math.max(field.name.length, suggestedMinWidths[field._field.type]));
}
fieldsStr +=fillEmpty(Math.floor(sizing[i]/2 - field.name.length / 2)) + field.name + fillEmpty(Math.ceil(sizing[i]/2 - field.name.length / 2)) + " | ";
});
var sumLengths = sizing.reduce((a,b)=> a+=b,(0)) + sizing.length * 3;
console.log("\n" + fieldsStr);
console.log(printN("=",sumLengths));
this.data.forEach(row => {
let rowStr = "";
row.data.forEach((entry, i) => {
if (this.fields[i]._field.type == 9) {
entry = entry.toTaosString();
}
else {
entry = entry.toString();
}
rowStr += entry
//console.log(this.fields[i]._field.bytes, suggestedWidths[this.fields[i]._field.type]);
rowStr += fillEmpty(sizing[i] - entry.length) + " | ";
});
console.log(rowStr);
});
}
const suggestedMinWidths = {
0: 4,
1: 4,
2: 4,
3: 6,
4: 11,
5: 12,
6: 24,
7: 24,
8: 10,
9: 25,
10: 10,
}
function printN(s, n) {
let f = "";
for (let i = 0; i < n; i ++) {
f += s;
}
return f;
}
function fillEmpty(n) {
let str = "";
for (let i = 0; i < n; i++) {
str += " ";
}
return str;
}
{
"name": "td-connector",
"version": "1.0.5",
"version": "1.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "td-connector",
"version": "1.0.5",
"version": "1.1.1",
"description": "A Node.js connector for TDengine.",
"main": "tdengine.js",
"scripts": {
......
# 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).
This is the Node.js library that lets you connect to [TDengine](https://www.github.com/taosdata/tdengine). It is built so that you can use as much of it as you want or as little of it as you want through providing an extensive API. If you want the raw data in the form of an array of arrays for the row data retrieved from a table, you can do that. If you want to wrap that data with objects that allow you easily manipulate and display data such as using a prettifier function, you can do that!
## Installation
......@@ -67,164 +67,73 @@ To target native ARM64 Node.js on Windows 10 on ARM, add the components "Visual
## Usage
To use the connector, first request the library ```td-connector```. Running the function ```taos.connect``` with the connection options passed in as an object will return a TDengine connection object. A cursor needs to be intialized in order to interact with TDengine from node.
### Connection
To use the connector, first require the library ```td-connector```. Running the function ```taos.connect``` with the connection options passed in as an object will return a TDengine connection object. A cursor needs to be initialized in order to interact with TDengine from Node.js.
```javascript
const taos = require('td-connector');
var conn = taos.connect({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0})
var c1 = conn.cursor(); // Initializing a new cursor
var cursor = conn.cursor(); // Initializing a new cursor
```
We can now start executing queries through the ```cursor.execute``` function.
Close a connection
```javascript
c1.execute('show databases;')
conn.close();
```
We can get the results of the queries by doing the following
### Queries
We can now start executing simple queries through the ```cursor.query``` function, which returns a TaosQuery object.
```javascript
var data = c1.fetchall();
console.log(c1.fieldNames); // Returns the names of the columns/fields
console.log(data); // Logs all the data from the query as an array of arrays, each of which represents a row and data[row_number] is sorted in order of the fields
var query = cursor.query('show databases;')
```
## Example
We can get the results of the queries through the ```query.execute()``` function, which returns a promise that resolves with a TaosResult object, which contains the raw data and additional functionalities such as pretty printing the results.
The following is an example use of the connector showing how to make a table with weather data, insert random data, and then retrieve it.
```javascript
var promise = query.execute();
promise.then(function(result) {
result.pretty(); //logs the results to the console as if you were in the taos shell
});
```
You can also query by binding parameters to a query by filling in the question marks in a string as so. The query will automatically parse what was binded and convert it to the proper format for use with TDengine
```javascript
// Get the td-connector package
const taos = require('td-connector');
var query = cursor.query('select * from meterinfo.meters where ts <= ? and areaid = ?').bind(new Date(), 5);
query.execute().then(function(result) {
result.pretty();
})
```
/* We will connect to TDengine by passing an object comprised of connection options to taos.connect and store the
* connection to the variable conn
*/
/*
* Connection Options
* host: the host to connect to
* user: the use to login as
* password: the password for the above user to login
* config: the location of the taos.cfg file, by default it is in /etc/taos
* port: the port we connect through
*/
var conn = taos.connect({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0});
// Initialize our TDengineCursor, which we use to interact with TDengine
var c1 = conn.cursor();
// c1.execute(query) will execute the query
// Let's create a database named db
try {
c1.execute('create database db;');
}
catch(err) {
conn.close();
throw err;
}
// Now we will use database db
try {
c1.execute('use db;');
}
catch (err) {
conn.close();
throw err;
}
// Let's create a table called weather
// which stores some weather data like humidity, AQI (air quality index), temperature, and some notes as text
try {
c1.execute('create table if not exists weather (ts timestamp, humidity smallint, aqi int, temperature float, notes binary(30));');
}
catch (err) {
conn.close();
throw err;
}
// Let's get the description of the table weather
try {
c1.execute('describe db.weather');
}
catch (err) {
conn.close();
throw err;
}
// To get results, we run the function c1.fetchall()
// It only returns the query results as an array of result rows, but also stores the latest results in c1.data
try {
var tableDesc = c1.fetchall(); // The description variable here is equal to c1.data;
console.log(tableDesc);
}
catch (err) {
conn.close();
throw err;
}
// Let's try to insert some random generated data to test with
let stime = new Date();
let interval = 1000;
// Timestamps must be in the form of "YYYY-MM-DD HH:MM:SS.MMM" if they are in milliseconds
// "YYYY-MM-DD HH:MM:SS.MMMMMM" if they are in microseconds
// Thus, we create the following function to convert a javascript Date object to the correct formatting
function convertDateToTS(date) {
let tsArr = date.toISOString().split("T")
return "\"" + tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1) + "\"";
}
try {
for (let i = 0; i < 10000; i++) {
stime.setMilliseconds(stime.getMilliseconds() + interval);
let insertData = [convertDateToTS(stime),
parseInt(Math.random()*100),
parseInt(Math.random()*300),
parseFloat(Math.random()*10 + 30),
"\"random note!\""];
c1.execute('insert into db.weather values(' + insertData.join(',') + ' );');
}
}
catch (err) {
conn.close();
throw err;
}
// Now let's look at our newly inserted data
var retrievedData;
try {
c1.execute('select * from db.weather;')
retrievedData = c1.fetchall();
// c1.fieldNames stores the names of each column retrieved
console.log(c1.fieldNames);
console.log(retrievedData);
// timestamps retrieved are always JS Date Objects
// Numbers are numbers, big ints are big ints, and strings are strings
}
catch (err) {
conn.close();
throw err;
}
// Let's try running some basic functions
try {
c1.execute('select count(*), avg(temperature), max(temperature), min(temperature), stddev(temperature) from db.weather;')
c1.fetchall();
console.log(c1.fieldNames);
console.log(c1.data);
}
catch(err) {
conn.close();
throw err;
}
The TaosQuery object can also be immediately executed upon creation by passing true as the second argument, returning a promise instead of a TaosQuery.
```javascript
var promise = cursor.query('select * from meterinfo.meters where v1 = 30', true)
query.execute().then(function(result) {
result.pretty();
})
```
conn.close();
If you want to execute queries without objects being wrapped around the data, use ```cursor.execute()``` directly and ```cursor.fetchall()``` to retrieve data if there is any.
```javascript
cursor.execute('select count(*), avg(v1), min(v2) from meterinfo.meters where ts >= \"2019-07-20 00:00:00.000\"');
var data = cursor.fetchall();
console.log(cursor.fields); // Latest query's Field metadata is stored in cursor.fields
console.log(cursor.data); // Latest query's result data is stored in cursor.data, also returned by fetchall.
```
// Feel free to fork this repository or copy this code and start developing your own apps and backends with NodeJS and TDengine!
### Async functionality
```
Coming soon
## Example
An example of using the NodeJS connector to create a table with weather data and create and execute queries can be found [here](https://github.com/taosdata/TDengine/tree/master/tests/examples/nodejs/node-example.js) (The preferred method for using the connector)
An example of using the NodeJS connector to achieve the same things but without all the object wrappers that wrap around the data returned to achieve higher functionality can be found [here](https://github.com/taosdata/TDengine/tree/master/tests/examples/nodejs/node-example-raw.js)
## Contributing to TDengine
......
const taos = require('td-connector');
const taos = require('../tdengine');
var conn = taos.connect({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0});
var c1 = conn.cursor();
let stime = new Date();
let interval = 1000;
// Timestamps must be in the form of "YYYY-MM-DD HH:MM:SS.MMM" if they are in milliseconds
// "YYYY-MM-DD HH:MM:SS.MMMMMM" if they are in microseconds
// Thus, we create the following function to convert a javascript Date object to the correct formatting
function convertDateToTS(date) {
let tsArr = date.toISOString().split("T")
return "\"" + tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1) + "\"";
......@@ -20,11 +17,18 @@ function randomBool() {
}
return false;
}
c1.execute('create database td_connector_test;');
// Initialize
c1.execute('create database if not exists td_connector_test;');
c1.execute('use td_connector_test;')
c1.execute('create table if not exists all_types (ts timestamp, _int int, _bigint bigint, _float float, _double double, _binary binary(40), _smallint smallint, _tinyint tinyint, _bool bool, _nchar nchar(40));');
for (let i = 0; i < 10000; i++) {
// Shell Test : The following uses the cursor to imitate the taos shell
// Insert
for (let i = 0; i < 5000; i++) {
stime.setMilliseconds(stime.getMilliseconds() + interval);
let insertData = [convertDateToTS(stime), // Timestamp
parseInt( R(-Math.pow(2,31) + 1 , Math.pow(2,31) - 1) ), // Int
......@@ -36,19 +40,41 @@ for (let i = 0; i < 10000; i++) {
parseInt( R(-127, 127) ), // Tiny Int
randomBool(),
"\"Nchars 一些中文字幕\""]; // Bool
c1.execute('insert into td_connector_test.all_types values(' + insertData.join(',') + ' );');
c1.execute('insert into td_connector_test.all_types values(' + insertData.join(',') + ' );', {quiet:true});
if (i % 1000 == 0) {
console.log("Insert # " , i);
}
}
// Select
c1.execute('select * from td_connector_test.all_types limit 10 offset 1000;');
var d = c1.fetchall();
console.log(c1.fieldNames);
console.log(c1.fields);
console.log(d);
// Functions
c1.execute('select count(*), avg(_int), sum(_float), max(_bigint), min(_double) from td_connector_test.all_types;');
var d = c1.fetchall();
console.log(c1.fieldNames);
console.log(c1.fields);
console.log(d);
//Immediate Execution like the Shell
c1.query('select count(*), stddev(_double), min(_tinyint) from all_types where _tinyint > 50 and _int < 0', true).then(function(result){
result.pretty();
})
c1.query('select _tinyint, _bool from all_types where _tinyint > 50 and _int < 0 limit 50;', true).then(function(result){
result.pretty();
})
c1.query('select stddev(_double), stddev(_bigint), stddev(_float) from all_types', true).then(function(result){
result.pretty();
})
var q = c1.query('select * from td_connector_test.all_types where ts >= ? and _int > ? limit 100 offset 40;').bind(new Date(1231), 100).execute().then(function(r) {
r.pretty();
});
console.log(q._query);
c1.execute('drop database td_connector_test;')
conn.close();
/* This example is to show how to use the td-connector through the cursor only and is a bit more raw.
* No promises, object wrappers around data, functions that prettify the data, or anything.
* The cursor will generally use callback functions over promises, and return and store the raw data from the C Interface.
* It is advised to use the td-connector through the cursor and the TaosQuery class amongst other higher level APIs.
*/
// Get the td-connector package
const taos = require('td-connector');
/* We will connect to TDengine by passing an object comprised of connection options to taos.connect and store the
* connection to the variable conn
*/
/*
* Connection Options
* host: the host to connect to
* user: the use to login as
* password: the password for the above user to login
* config: the location of the taos.cfg file, by default it is in /etc/taos
* port: the port we connect through
*/
var conn = taos.connect({host:"127.0.0.1", user:"root", password:"taosdata", config:"/etc/taos",port:0});
// Initialize our TDengineCursor, which we use to interact with TDengine
var c1 = conn.cursor();
// c1.execute(query) will execute the query
// Let's create a database named db
try {
c1.execute('create database db;');
}
catch(err) {
conn.close();
throw err;
}
// Now we will use database db
try {
c1.execute('use db;');
}
catch (err) {
conn.close();
throw err;
}
// Let's create a table called weather
// which stores some weather data like humidity, AQI (air quality index), temperature, and some notes as text
try {
c1.execute('create table if not exists weather (ts timestamp, humidity smallint, aqi int, temperature float, notes binary(30));');
}
catch (err) {
conn.close();
throw err;
}
// Let's get the description of the table weather
try {
c1.execute('describe db.weather');
}
catch (err) {
conn.close();
throw err;
}
// To get results, we run the function c1.fetchall()
// It only returns the query results as an array of result rows, but also stores the latest results in c1.data
try {
var tableDesc = c1.fetchall(); // The description variable here is equal to c1.data;
console.log(tableDesc);
}
catch (err) {
conn.close();
throw err;
}
// Let's try to insert some random generated data to test with
let stime = new Date();
let interval = 1000;
// Timestamps must be in the form of "YYYY-MM-DD HH:MM:SS.MMM" if they are in milliseconds
// "YYYY-MM-DD HH:MM:SS.MMMMMM" if they are in microseconds
// Thus, we create the following function to convert a javascript Date object to the correct formatting
function convertDateToTS(date) {
let tsArr = date.toISOString().split("T")
return "\"" + tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1) + "\"";
}
try {
for (let i = 0; i < 10000; i++) {
stime.setMilliseconds(stime.getMilliseconds() + interval);
let insertData = [convertDateToTS(stime),
parseInt(Math.random()*100),
parseInt(Math.random()*300),
parseFloat(Math.random()*10 + 30),
"\"random note!\""];
c1.execute('insert into db.weather values(' + insertData.join(',') + ' );');
}
}
catch (err) {
conn.close();
throw err;
}
// Now let's look at our newly inserted data
var retrievedData;
try {
c1.execute('select * from db.weather;')
retrievedData = c1.fetchall();
// c1.fields stores the names of each column retrieved
console.log(c1.fields);
console.log(retrievedData);
// timestamps retrieved are always JS Date Objects
// Numbers are numbers, big ints are big ints, and strings are strings
}
catch (err) {
conn.close();
throw err;
}
// Let's try running some basic functions
try {
c1.execute('select count(*), avg(temperature), max(temperature), min(temperature), stddev(temperature) from db.weather;')
c1.fetchall();
console.log(c1.fields);
console.log(c1.data);
}
catch(err) {
conn.close();
throw err;
}
conn.close();
// Feel free to fork this repository or copy this code and start developing your own apps and backends with NodeJS and TDengine!
/* This example is to show the preferred way to use the td-connector */
/* To run, enter node path/to/node-example.js */
// Get the td-connector package
const taos = require('td-connector');
......@@ -17,17 +19,19 @@ var conn = taos.connect({host:"127.0.0.1", user:"root", password:"taosdata", con
// Initialize our TDengineCursor, which we use to interact with TDengine
var c1 = conn.cursor();
// c1.execute(query) will execute the query
// c1.query(query) will return a TaosQuery object, of which then we can execute. The execute function then returns a promise
// Let's create a database named db
try {
c1.execute('create database db;');
var query = c1.query('create database db;');
query.execute();
}
catch(err) {
conn.close();
throw err;
}
// Now we will use database db
// Now we will use database db. As this query won't return any results,
// we can simplify the code and directly use the c1.execute() function. No need for a TaosQuery object to wrap around the query
try {
c1.execute('use db;');
}
......@@ -38,8 +42,15 @@ catch (err) {
// Let's create a table called weather
// which stores some weather data like humidity, AQI (air quality index), temperature, and some notes as text
// We can also immedietely execute a TaosQuery object by passing true as the secodn argument
// This will then return a promise that we can then attach a callback function to
try {
c1.execute('create table if not exists weather (ts timestamp, humidity smallint, aqi int, temperature float, notes binary(30));');
var promise = c1.query('create table if not exists weather (ts timestamp, humidity smallint, aqi int, temperature float, notes binary(30));', true);
promise.then(function(){
console.log("Table created!");
}).catch(function() {
console.log("Table couldn't be created.")
});
}
catch (err) {
conn.close();
......@@ -47,19 +58,13 @@ catch (err) {
}
// Let's get the description of the table weather
// When using a TaosQuery object and then executing it, upon success it returns a TaosResult object, which is a wrapper around the
// retrieved data and allows us to easily access data and manipulate or display it.
try {
c1.execute('describe db.weather');
}
catch (err) {
conn.close();
throw err;
}
// To get results, we run the function c1.fetchall()
// It only returns the query results as an array of result rows, but also stores the latest results in c1.data
try {
var tableDesc = c1.fetchall(); // The description variable here is equal to c1.data;
console.log(tableDesc);
c1.query('describe db.weather;').execute().then(function(result){
// Result is an instance of TaosResult and has the function pretty() which instantly logs a prettified version to the console
result.pretty();
});
}
catch (err) {
conn.close();
......@@ -67,27 +72,21 @@ catch (err) {
}
// Let's try to insert some random generated data to test with
// We will use the bind function of the TaosQuery object to easily bind values to question marks in the query
// For Timestamps, a normal Datetime object or TaosTimestamp or milliseconds can be passed in through the bind function
let stime = new Date();
let interval = 1000;
// Timestamps must be in the form of "YYYY-MM-DD HH:MM:SS.MMM" if they are in milliseconds
// "YYYY-MM-DD HH:MM:SS.MMMMMM" if they are in microseconds
// Thus, we create the following function to convert a javascript Date object to the correct formatting
function convertDateToTS(date) {
let tsArr = date.toISOString().split("T")
return "\"" + tsArr[0] + " " + tsArr[1].substring(0, tsArr[1].length-1) + "\"";
}
try {
for (let i = 0; i < 10000; i++) {
for (let i = 0; i < 1000; i++) {
stime.setMilliseconds(stime.getMilliseconds() + interval);
let insertData = [convertDateToTS(stime),
let insertData = [stime,
parseInt(Math.random()*100),
parseInt(Math.random()*300),
parseFloat(Math.random()*10 + 30),
"\"random note!\""];
c1.execute('insert into db.weather values(' + insertData.join(',') + ' );');
//c1.execute('insert into db.weather values(' + insertData.join(',') + ' );');
var query = c1.query('insert into db.weather values(?, ?, ?, ?, ?);').bind(insertData);
query.execute();
}
}
catch (err) {
......@@ -98,14 +97,11 @@ catch (err) {
// Now let's look at our newly inserted data
var retrievedData;
try {
c1.execute('select * from db.weather;')
retrievedData = c1.fetchall();
c1.query('select * from db.weather limit 5 offset 100;', true).then(function(result){
result.pretty();
// Neat!
});
// c1.fieldNames stores the names of each column retrieved
console.log(c1.fieldNames);
console.log(retrievedData);
// timestamps retrieved are always JS Date Objects
// Numbers are numbers, big ints are big ints, and strings are strings
}
catch (err) {
conn.close();
......@@ -114,10 +110,10 @@ catch (err) {
// Let's try running some basic functions
try {
c1.execute('select count(*), avg(temperature), max(temperature), min(temperature), stddev(temperature) from db.weather;')
c1.fetchall();
console.log(c1.fieldNames);
console.log(c1.data);
c1.query('select count(*), avg(temperature), max(temperature), min(temperature), stddev(temperature) from db.weather;', true)
.then(function(result) {
result.pretty();
})
}
catch(err) {
conn.close();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册