From 5a1a1d82d535bbaedbb2c56d180db52d02cbcd6b Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 18 Aug 2025 10:32:02 +0800 Subject: [PATCH 01/23] feat: add stmt2 encode --- nodejs/index.ts | 4 +- nodejs/src/common/constant.ts | 7 + nodejs/src/common/utils.ts | 24 + nodejs/src/sql/wsSql.ts | 5 +- nodejs/src/stmt/wsParams.ts | 464 ------------------ nodejs/src/stmt/wsProto.ts | 17 +- nodejs/src/stmt/wsStmt.ts | 234 +-------- nodejs/test/bulkPulling/stmt.func.test.ts | 4 +- nodejs/test/bulkPulling/stmt.type.test.ts | 4 +- nodejs/test/bulkPulling/wsConnectPool.test.ts | 3 +- 10 files changed, 70 insertions(+), 696 deletions(-) delete mode 100644 nodejs/src/stmt/wsParams.ts diff --git a/nodejs/index.ts b/nodejs/index.ts index 847bc7b4..d1fe9629 100644 --- a/nodejs/index.ts +++ b/nodejs/index.ts @@ -16,9 +16,9 @@ export * from "./src/index" export * from "./src/sql/wsProto" export * from "./src/sql/wsRows" export * from "./src/sql/wsSql" -export * from "./src/stmt/wsParams" +export * from "./src/stmt/wsParamsBase" export * from "./src/stmt/wsProto" -export * from "./src/stmt/wsStmt" +export * from "./src/stmt/wsStmt1" export * from "./src/tmq/config" export * from "./src/tmq/constant" export * from "./src/tmq/tmqResponse" diff --git a/nodejs/src/common/constant.ts b/nodejs/src/common/constant.ts index 700ffd65..4fe0f248 100644 --- a/nodejs/src/common/constant.ts +++ b/nodejs/src/common/constant.ts @@ -70,6 +70,13 @@ export enum TSDB_OPTION_CONNECTION { TSDB_OPTION_CONNECTION_USER_APP, // user app } +export enum FieldBindType { + TAOS_FIELD_COL = 1, + TAOS_FIELD_TAG = 2, + TAOS_FIELD_QUERY = 3, + TAOS_FIELD_TBNAME = 4, +} + export const TDenginePrecision: IndexableString = { 0: 'MILLISECOND', 1: "MICROSECOND", diff --git a/nodejs/src/common/utils.ts b/nodejs/src/common/utils.ts index cbea474f..e483f703 100644 --- a/nodejs/src/common/utils.ts +++ b/nodejs/src/common/utils.ts @@ -177,3 +177,27 @@ export function decimalToString(valueStr: string, fields_scale: bigint | null): return decimalStr; } +export const shotToBytes = (value: number): number[] => { + const buffer = new ArrayBuffer(2); + const view = new DataView(buffer); + view.setUint16(0, value, true); + return [...new Uint8Array(buffer)]; +}; + +export const bigintToBytes = (value: bigint): number[] => { + const buffer = new ArrayBuffer(8); + const view = new DataView(buffer); + view.setBigUint64(0, value, true); + return [...new Uint8Array(buffer)]; +}; + +export const intToBytes = (value: number, bSymbol = true): number[] => { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + if (bSymbol) { + view.setInt32(0, value, true); + } else { + view.setUint32(0, value, true); + } + return [...new Uint8Array(buffer)]; +}; \ No newline at end of file diff --git a/nodejs/src/sql/wsSql.ts b/nodejs/src/sql/wsSql.ts index f7a7e0a9..caffc9a1 100644 --- a/nodejs/src/sql/wsSql.ts +++ b/nodejs/src/sql/wsSql.ts @@ -6,10 +6,11 @@ import { WSConfig } from '../common/config' import { getBinarySql, getUrl } from '../common/utils' import { WSFetchBlockResponse, WSQueryResponse } from '../client/wsResponse' import { Precision, SchemalessMessageInfo, SchemalessProto } from './wsProto' -import { WsStmt } from '../stmt/wsStmt' +import { WsStmt1 } from '../stmt/wsStmt1' import { ReqId } from '../common/reqid' import { BinaryQueryMessage, FetchRawBlockMessage, PrecisionLength, TSDB_OPTION_CONNECTION } from '../common/constant' import logger from '../common/log' +import { WsStmt } from '../stmt/wsStmt' export class WsSql{ private wsConfig:WSConfig; @@ -106,7 +107,7 @@ export class WsSql{ precision = PrecisionLength[data[0][0]] } } - return await WsStmt.newStmt(this._wsClient, precision, reqId); + return await WsStmt1.newStmt(this._wsClient, precision, reqId); } catch (e: any) { logger.error(`stmtInit failed, code: ${e.code}, message: ${e.message}`); throw(e); diff --git a/nodejs/src/stmt/wsParams.ts b/nodejs/src/stmt/wsParams.ts deleted file mode 100644 index bc237add..00000000 --- a/nodejs/src/stmt/wsParams.ts +++ /dev/null @@ -1,464 +0,0 @@ -import { PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; -import { ErrorCode, TaosError } from "../common/wsError"; -import { getCharOffset, setBitmapNull, bitmapLen} from "../common/taosResult" -import { isEmpty } from "../common/utils"; - -export class ColumnInfo { - data:ArrayBuffer; - length:number; - type:number; - typeLen:number; - constructor([length,data]:[number, ArrayBuffer], type:number, typeLen:number) { - this.data = data; - this.type = type; - this.length = length; - this.typeLen = typeLen; - } -} - -export class StmtBindParams { - private readonly precisionLength:number = PrecisionLength['ms'] - private readonly _params: ColumnInfo[]; - private _dataTotalLen:number = 0; - private _rows = 0; - constructor(precision?:number) { - if (precision) { - this.precisionLength = precision - } - this._params = []; - - } - - public getDataRows(): number { - return this._rows; - } - - public getDataTotalLen(): number { - return this._dataTotalLen; - } - - public getParams(): ColumnInfo[] { - return this._params; - } - - setBoolean(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBooleanColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "boolean", TDengineTypeLength['BOOL'], TDengineTypeCode.BOOL) - - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.BOOL, TDengineTypeLength['BOOL'])) ; - } - - setTinyInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['TINYINT'], TDengineTypeCode.TINYINT) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.TINYINT, TDengineTypeLength['TINYINT'])); - } - - setUTinyInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUTinyIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['TINYINT UNSIGNED'], TDengineTypeCode.TINYINT_UNSIGNED) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.TINYINT_UNSIGNED, TDengineTypeLength['TINYINT UNSIGNED'])); - } - - setSmallInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['SMALLINT'], TDengineTypeCode.SMALLINT) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.SMALLINT, TDengineTypeLength['SMALLINT'])); - - } - - setUSmallInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['SMALLINT UNSIGNED'], TDengineTypeCode.SMALLINT_UNSIGNED) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.SMALLINT_UNSIGNED, TDengineTypeLength['SMALLINT UNSIGNED'])); - } - - setInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['INT'], TDengineTypeCode.INT) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.INT, TDengineTypeLength['INT'])); - } - - setUInt(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['INT UNSIGNED'], TDengineTypeCode.INT_UNSIGNED) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.INT_UNSIGNED, TDengineTypeLength['INT UNSIGNED'])); - } - - setBigint(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBigIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "bigint", TDengineTypeLength['BIGINT'], TDengineTypeCode.BIGINT) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.INT, TDengineTypeLength['BIGINT'])); - } - - setUBigint(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUBigIntColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "bigint", TDengineTypeLength['BIGINT UNSIGNED'], TDengineTypeCode.BIGINT_UNSIGNED) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.BIGINT_UNSIGNED, TDengineTypeLength['BIGINT UNSIGNED'])); - } - - setFloat(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetFloatColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['FLOAT'], TDengineTypeCode.FLOAT) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.FLOAT, TDengineTypeLength['FLOAT'])); - } - - setDouble(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetDoubleColumn params is invalid!"); - } - let arrayBuffer = this.encodeDigitColumns(params, "number", TDengineTypeLength['DOUBLE'], TDengineTypeCode.DOUBLE) - this._params.push(new ColumnInfo(arrayBuffer, TDengineTypeCode.DOUBLE, TDengineTypeLength['DOUBLE'])); - } - - setVarchar(params :any[]) { - let data = this.encodeVarLengthColumn(params) - this._params.push(new ColumnInfo(data, TDengineTypeCode.VARCHAR, 0)); - } - - setBinary(params :any[]) { - this._params.push(new ColumnInfo(this.encodeVarLengthColumn(params), TDengineTypeCode.BINARY, 0)); - } - - setNchar(params :any[]) { - this._params.push(new ColumnInfo(this.encodeNcharColumn(params), TDengineTypeCode.NCHAR, 0)); - } - - setJson(params :any[]) { - this._params.push(new ColumnInfo(this.encodeVarLengthColumn(params), TDengineTypeCode.JSON, 0)); - } - - setVarBinary(params :any[]) { - this._params.push(new ColumnInfo(this.encodeVarLengthColumn(params), TDengineTypeCode.VARBINARY, 0)); - } - - setGeometry(params :any[]) { - this._params.push(new ColumnInfo(this.encodeVarLengthColumn(params), TDengineTypeCode.GEOMETRY, 0)); - } - - setTimestamp(params :any[]) { - if (!params || params.length == 0) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid!"); - } - - //computing bitmap length - let bitMapLen:number = bitmapLen(params.length) - //Computing the length of data - let arrayBuffer = new ArrayBuffer(bitMapLen + TDengineTypeLength['TIMESTAMP'] * params.length); - //bitmap get data range - let bitmapBuffer = new DataView(arrayBuffer) - //skip bitmap get data range - let dataBuffer = new DataView(arrayBuffer, bitMapLen) - if (this._rows > 0) { - if (this._rows !== params.length) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") - } - }else { - this._rows = params.length; - } - - for (let i = 0; i < params.length; i++) { - if (!isEmpty(params[i])) { - if (params[i] instanceof Date) { - let date:Date = params[i] - //node only support milliseconds, need fill 0 - if (this.precisionLength == PrecisionLength['us']) { - let ms = date.getMilliseconds() * 1000 - dataBuffer.setBigInt64(i * 8, BigInt(ms), true); - }else if (this.precisionLength == PrecisionLength['ns']) { - let ns = date.getMilliseconds() * 1000 * 1000 - dataBuffer.setBigInt64(i * 8, BigInt(ns), true); - }else { - dataBuffer.setBigInt64(i * 8, BigInt(date.getMilliseconds()), true); - } - - } else if (typeof params[i] == 'bigint' || typeof params[i] == 'number') { - - let data:bigint - if (typeof params[i] == 'number') { - data = BigInt(params[i]) - }else { - data = params[i] - } - //statistical bits of digit - let digit = this.countBigintDigits(data) - //check digit same table Precision - if (this.precisionLength == PrecisionLength['ns']) { - if (this.precisionLength <= digit) { - dataBuffer.setBigInt64(i * 8, data, true); - } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params precisionLength is invalid! param:=" + params[i]) - } - } else if (this.precisionLength == digit) { - dataBuffer.setBigInt64(i * 8, data, true); - } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid! param:=" + params[i]) - } - } - }else{ - //set bitmap bit is null - let charOffset = getCharOffset(i); - let nullVal = setBitmapNull(dataBuffer.getInt8(charOffset), i); - bitmapBuffer.setInt8(charOffset, nullVal); - } - } - - this._dataTotalLen += arrayBuffer.byteLength; - this._params.push(new ColumnInfo([TDengineTypeLength['TIMESTAMP'] * params.length, arrayBuffer], TDengineTypeCode.TIMESTAMP, TDengineTypeLength['TIMESTAMP'])); - } - - - private encodeDigitColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):[number, ArrayBuffer] { - let bitMapLen:number = bitmapLen(params.length) - let arrayBuffer = new ArrayBuffer(typeLen * params.length + bitMapLen); - let bitmapBuffer = new DataView(arrayBuffer) - let dataBuffer = new DataView(arrayBuffer, bitMapLen) - if (this._rows > 0) { - if (this._rows !== params.length) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") - } - }else { - this._rows = params.length; - } - - for (let i = 0; i < params.length; i++) { - if (!isEmpty(params[i])) { - if (typeof params[i] == dataType) { - switch (columnType) { - case TDengineTypeCode.BOOL: { - if (params[i]) { - dataBuffer.setInt8(i, 1); - } else { - dataBuffer.setInt8(i, 0); - } - break; - } - case TDengineTypeCode.TINYINT: { - dataBuffer.setInt8(i, params[i]); - break; - } - case TDengineTypeCode.TINYINT_UNSIGNED: { - dataBuffer.setUint8(i, params[i]); - break; - } - case TDengineTypeCode.SMALLINT: { - dataBuffer.setInt16(i * 2, params[i], true); - break; - } - case TDengineTypeCode.SMALLINT_UNSIGNED: { - dataBuffer.setUint16(i * 2, params[i], true); - break; - } - - case TDengineTypeCode.INT: { - dataBuffer.setInt32(i * 4, params[i], true); - break; - } - - case TDengineTypeCode.INT_UNSIGNED: { - dataBuffer.setUint32(i * 4, params[i], true); - break; - } - - case TDengineTypeCode.BIGINT: { - dataBuffer.setBigInt64(i * 8, params[i], true); - break; - } - - case TDengineTypeCode.BIGINT_UNSIGNED: { - dataBuffer.setBigUint64(i * 8, params[i], true); - break; - } - - case TDengineTypeCode.FLOAT: { - dataBuffer.setFloat32(i * 4, params[i], true); - break; - } - case TDengineTypeCode.DOUBLE: { - dataBuffer.setFloat64(i * 8, params[i], true); - break; - } - default: { - throw new TaosError(ErrorCode.ERR_UNSUPPORTED_TDENGINE_TYPE, "unsupported type for column" + columnType) - } - } - - } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid! param:=" + params[i]) - } - } else { - //set bitmap bit is null - let charOffset = getCharOffset(i); - let nullVal = setBitmapNull(bitmapBuffer.getUint8(charOffset), i); - bitmapBuffer.setInt8(charOffset, nullVal); - } - } - this._dataTotalLen += dataBuffer.buffer.byteLength; - return [typeLen * params.length, dataBuffer.buffer]; - } - - private encodeVarLengthColumn(params:any[]):[number, ArrayBuffer] { - let data:ArrayBuffer[] = [] - let dataLength = 0; - //create params length buffer - let paramsLenBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) - let paramsLenView = new DataView(paramsLenBuffer) - if (this._rows > 0) { - if (this._rows !== params.length) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") - } - }else { - this._rows = params.length; - } - for (let i = 0; i < params.length; i++) { - //get param length offset 4byte - let offset = TDengineTypeLength['INT'] * i; - if (!isEmpty(params[i])) { - //save param length offset 4byte - paramsLenView.setInt32(offset, dataLength, true); - if (typeof params[i] == 'string' ) { - //string TextEncoder - let encode = new TextEncoder(); - let value = encode.encode(params[i]).buffer; - data.push(value); - //add offset length - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; - } else if (params[i] instanceof ArrayBuffer) { - //input arraybuffer, save not need encode - let value:ArrayBuffer = params[i]; - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; - data.push(value); - } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, - "getColumString params is invalid! param_type:=" + typeof params[i]); - } - - }else{ - //set length -1, param is null - for (let j = 0; j < TDengineTypeLength['INT']; j++) { - paramsLenView.setInt8(offset+j, 255); - } - - } - } - - this._dataTotalLen += paramsLenBuffer.byteLength + dataLength; - return [dataLength, this.getBinaryColumnArrayBuffer(data, paramsLenView.buffer, dataLength)]; - } - //splicing encode params to arraybuffer - private getBinaryColumnArrayBuffer(data:ArrayBuffer[], paramsLenBuffer: ArrayBuffer, dataLength:number):ArrayBuffer { - //create arraybuffer - let paramsBuffer = new ArrayBuffer(paramsLenBuffer.byteLength + dataLength) - //get length data range - const paramsUint8 = new Uint8Array(paramsBuffer); - const paramsLenView = new Uint8Array(paramsLenBuffer); - paramsUint8.set(paramsLenView, 0); - //get data range - const paramsView = new DataView(paramsBuffer, paramsLenBuffer.byteLength); - - let offset = 0; - for (let i = 0; i < data.length; i++) { - //save param field length - paramsView.setInt16(offset, data[i].byteLength, true) - const dataView = new DataView(data[i]); - //save data - for (let j = 0; j < data[i].byteLength; j++) { - paramsView.setUint8(offset + 2 + j, dataView.getUint8(j)) - } - offset += data[i].byteLength + 2; - } - - return paramsBuffer - } - //encode nchar type params - private encodeNcharColumn(params:any[]):[number, ArrayBuffer] { - let data:ArrayBuffer[] = [] - let dataLength = 0; - let indexBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) - let indexView = new DataView(indexBuffer) - if (this._rows > 0) { - if (this._rows !== params.length) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") - } - }else { - this._rows = params.length; - } - - for (let i = 0; i < params.length; i++) { - let offset = TDengineTypeLength['INT'] * i; - if (!isEmpty(params[i])) { - indexView.setInt32(offset, dataLength, true); - if (typeof params[i] == 'string' ) { - let codes:number[] = []; - let strNcharParams:string = params[i]; - for (let j = 0; j < params[i].length; j++) { - //get char, cn char need 3~4 byte - codes.push(strNcharParams.charCodeAt(j)); - } - - let ncharBuffer:ArrayBuffer = new ArrayBuffer(codes.length * 4); - let ncharView = new DataView(ncharBuffer); - for (let j = 0; j < codes.length; j++) { - //1char, save into uint32 - ncharView.setUint32(j*4, codes[j], true); - } - data.push(ncharBuffer); - dataLength += codes.length * 4 + TDengineTypeLength['SMALLINT']; - - } else if (params[i] instanceof ArrayBuffer) { - let value:ArrayBuffer = params[i] - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; - data.push(value); - } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "getColumString params is invalid! param_type:=" + typeof params[i]) - } - - }else{ - //set length -1, param is null - for (let j = 0; j < TDengineTypeLength['INT']; j++) { - indexView.setInt8(offset+j, 255) - } - - } - } - - this._dataTotalLen += indexBuffer.byteLength + dataLength; - return [dataLength, this.getBinaryColumnArrayBuffer(data, indexView.buffer, dataLength)]; - } - - private countBigintDigits(numeral: bigint): number { - if (numeral === 0n) { - return 1; - } - let count = 0; - let temp = numeral; - while (temp !== 0n) { - temp /= 10n; - count++; - } - return count; - } - -} - - diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index be51f982..18bd34da 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -1,8 +1,8 @@ +import exp from "constants"; import { WSQueryResponse } from "../client/wsResponse"; import { TDengineTypeLength } from "../common/constant"; import { MessageResp } from "../common/taosResult"; -import { StmtBindParams } from "./wsParams"; - +import { StmtBindParams } from "./wsParamsBase"; export interface StmtMessageInfo { action: string; args: StmtParamsInfo; @@ -18,13 +18,26 @@ interface StmtParamsInfo { } +export interface StmtFieldInfo { + name: string | undefined | null; + field_type: number | undefined | null; + precision: number | undefined | null; + scale: number | undefined | null; + bytes: number | undefined | null; + bind_type: number | undefined | null; +} + export class WsStmtQueryResponse extends WSQueryResponse { affected:number | undefined | null; stmt_id?: bigint | undefined | null; + is_insert?: boolean | undefined | null; + fields?: Array | undefined | null; constructor(resp:MessageResp) { super(resp); this.stmt_id = BigInt(resp.msg.stmt_id) this.affected = resp.msg.affected + this.is_insert = resp.msg.is_insert; + this.fields = resp.msg.fields; } } diff --git a/nodejs/src/stmt/wsStmt.ts b/nodejs/src/stmt/wsStmt.ts index a3ed8fae..e19efdd6 100644 --- a/nodejs/src/stmt/wsStmt.ts +++ b/nodejs/src/stmt/wsStmt.ts @@ -1,221 +1,13 @@ -import JSONBig from 'json-bigint'; -import { WsClient } from '../client/wsClient'; -import { ErrorCode, TDWebSocketClientError, TaosError, TaosResultError } from '../common/wsError'; -import { WsStmtQueryResponse, StmtMessageInfo, binaryBlockEncode, StmtBindType } from './wsProto'; -import { ReqId } from '../common/reqid'; -import { PrecisionLength } from '../common/constant'; -import { StmtBindParams } from './wsParams'; -import logger from '../common/log'; - -export class WsStmt { - private _wsClient: WsClient; - private _stmt_id: bigint | undefined | null; - private _precision:number = PrecisionLength['ms']; - - private lastAffected: number | undefined | null; - private constructor(wsClient: WsClient, precision?:number) { - this._wsClient = wsClient; - if (precision) { - this._precision = precision; - } - } - - static async newStmt(wsClient: WsClient, precision?:number, reqId?:number): Promise { - try { - let wsStmt = new WsStmt(wsClient, precision) - return await wsStmt.init(reqId); - } catch(e: any) { - logger.error(`WsStmt init is failed, ${e.code}, ${e.message}`); - throw(e); - } - - } - - async prepare(sql: string): Promise { - let queryMsg = { - action: 'prepare', - args: { - req_id: ReqId.getReqID(), - sql: sql, - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - async setTableName(tableName: string): Promise { - let queryMsg = { - action: 'set_table_name', - args: { - req_id: ReqId.getReqID(), - name: tableName, - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - async setJsonTags(tags: Array): Promise { - let queryMsg = { - action: 'set_tags', - args: { - req_id: ReqId.getReqID(), - tags: tags, - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - newStmtParam():StmtBindParams { - return new StmtBindParams(this._precision); - } - - async setTags(paramsArray:StmtBindParams): Promise { - if (!paramsArray || !this._stmt_id) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!") - } - - let columnInfos = paramsArray.getParams(); - if (!columnInfos) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!") - } - let reqId = BigInt(ReqId.getReqID()) - let dataBlock = binaryBlockEncode(paramsArray, StmtBindType.STMT_TYPE_TAG, this._stmt_id, reqId, paramsArray.getDataRows()) - return await this.sendBinaryMsg(reqId, 'set_tags', dataBlock); - } - - async bind(paramsArray:StmtBindParams): Promise { - if (!paramsArray || !this._stmt_id) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "BinaryBind paramArray is invalid!") - } - - let columnInfos = paramsArray.getParams(); - if (!columnInfos) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "BinaryBind paramArray is invalid!") - } - let reqId = BigInt(ReqId.getReqID()) - let dataBlock = binaryBlockEncode(paramsArray, StmtBindType.STMT_TYPE_BIND, this._stmt_id, reqId, paramsArray.getDataRows()); - return await this.sendBinaryMsg(reqId, 'bind', dataBlock); - } - - async jsonBind(paramArray: Array>): Promise { - let queryMsg = { - action: 'bind', - args: { - req_id: ReqId.getReqID(), - columns: paramArray, - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - async batch(): Promise { - let queryMsg = { - action: 'add_batch', - args: { - req_id: ReqId.getReqID(), - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - async exec(): Promise { - let queryMsg = { - action: 'exec', - args: { - req_id: ReqId.getReqID(), - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg); - } - - getLastAffected() { - return this.lastAffected; - } - - async close(): Promise { - let queryMsg = { - action: 'close', - args: { - req_id: ReqId.getReqID(), - stmt_id: this._stmt_id, - }, - }; - return await this.execute(queryMsg, false); - } - - public getStmtId(): bigint | undefined | null { - return this._stmt_id; - } - - private async execute(queryMsg: StmtMessageInfo, register: Boolean = true): Promise { - try { - if (this._wsClient.getState() <= 0) { - throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); - } - let reqMsg = JSONBig.stringify(queryMsg); - if (register) { - let result = await this._wsClient.exec(reqMsg, false); - let resp = new WsStmtQueryResponse(result) - if (resp.stmt_id) { - this._stmt_id = resp.stmt_id; - } - - if (resp.affected) { - this.lastAffected = resp.affected - } - }else{ - await this._wsClient.execNoResp(reqMsg); - this._stmt_id = null - this.lastAffected = null - } - return - } catch (e:any) { - throw new TaosResultError(e.code, e.message); - } - } - - private async sendBinaryMsg(reqId: bigint, action:string, message: ArrayBuffer): Promise { - if (this._wsClient.getState() <= 0) { - throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); - } - let result = await this._wsClient.sendBinaryMsg(reqId, action, message, false); - let resp = new WsStmtQueryResponse(result) - if (resp.stmt_id) { - this._stmt_id = resp.stmt_id; - } - - if (resp.affected) { - this.lastAffected = resp.affected - } - } - - private async init(reqId?: number):Promise { - if (this._wsClient) { - try { - if (this._wsClient.getState() <= 0) { - await this._wsClient.connect(); - await this._wsClient.checkVersion(); - } - let queryMsg = { - action: 'init', - args: { - req_id: ReqId.getReqID(reqId), - }, - }; - await this.execute(queryMsg); - return this; - } catch (e: any) { - logger.error(`stmt init filed, ${e.code}, ${e.message}`); - throw(e); - } - - } - throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); - } - -} +import { StmtBindParams } from "./wsParamsBase"; + +export interface WsStmt { + prepare(sql: string): Promise; + setTableName(tableName: string): Promise; + setTags(paramsArray:StmtBindParams): Promise; + newStmtParam():StmtBindParams + bind(paramsArray:StmtBindParams): Promise; + batch(): Promise; + exec(): Promise; + getLastAffected(): number | null | undefined; + close(): Promise +} \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt.func.test.ts b/nodejs/test/bulkPulling/stmt.func.test.ts index c5b5c2cf..5da64f4e 100644 --- a/nodejs/test/bulkPulling/stmt.func.test.ts +++ b/nodejs/test/bulkPulling/stmt.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { Sleep } from "../utils"; -let dns = 'ws://localhost:6041' +let dns = 'ws://192.168.2.156:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); @@ -76,7 +76,7 @@ describe('TDWebSocket.Stmt()', () => { let params = stmt.newStmtParam() params.setVarchar([tags[0]]); params.setInt([tags[1]]); - await stmt.setTags(params) + await stmt.setTags(params); await stmt.close() await connector.close(); }); diff --git a/nodejs/test/bulkPulling/stmt.type.test.ts b/nodejs/test/bulkPulling/stmt.type.test.ts index 86fb4578..10381d3b 100644 --- a/nodejs/test/bulkPulling/stmt.type.test.ts +++ b/nodejs/test/bulkPulling/stmt.type.test.ts @@ -82,7 +82,7 @@ const selectTableCN = `select * from ${tableCN}` const selectJsonTable = `select * from ${jsonTable}` const selectJsonTableCN = `select * from ${jsonTableCN}` -let dsn = 'ws://root:taosdata@localhost:6041'; +let dsn = 'ws://root:taosdata@192.168.2.156:6041'; beforeAll(async () => { let conf :WSConfig = new WSConfig(dsn) let ws = await WsSql.open(conf); @@ -100,7 +100,7 @@ describe('TDWebSocket.Stmt()', () => { let wsConf = new WSConfig(dsn); wsConf.setDb(db) let connector = await WsSql.open(wsConf) - let stmt = await (await connector).stmtInit() + let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() expect(connector.state()).toBeGreaterThan(0) await stmt.prepare(getInsertBind(tableValues.length + 2, stableTags.length, db, stable)); diff --git a/nodejs/test/bulkPulling/wsConnectPool.test.ts b/nodejs/test/bulkPulling/wsConnectPool.test.ts index 2d60483b..82644039 100644 --- a/nodejs/test/bulkPulling/wsConnectPool.test.ts +++ b/nodejs/test/bulkPulling/wsConnectPool.test.ts @@ -6,6 +6,7 @@ import { TMQConstants } from "../../src/tmq/constant"; import { WsConsumer } from "../../src/tmq/wsTmq"; import { Sleep } from "../utils"; import { setLevel } from "../../src/common/log" +import { WsStmt1 } from "../../src/stmt/wsStmt1"; let dsn = 'ws://root:taosdata@localhost:6041'; let tags = ['California.SanFrancisco', 3]; @@ -52,7 +53,7 @@ async function stmtConnect() { // let connector = WsStmtConnect.NewConnector(wsConf) // let stmt = await connector.Init() let connector = await WsSql.open(wsConf) - let stmt = await connector.stmtInit() + let stmt = (await connector.stmtInit()) as WsStmt1 let id = stmt.getStmtId() if (id) { stmtIds.push(id) From 1672c1801f69e2f35b552d7320a898a34f2891f9 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 18 Aug 2025 10:34:03 +0800 Subject: [PATCH 02/23] feat: stmt interface abstraction --- nodejs/src/stmt/wsColumnInfo.ts | 23 ++ nodejs/src/stmt/wsParams1.ts | 271 ++++++++++++++++++++++ nodejs/src/stmt/wsParams2.ts | 122 ++++++++++ nodejs/src/stmt/wsParamsBase.ts | 252 ++++++++++++++++++++ nodejs/src/stmt/wsStmt1.ts | 223 ++++++++++++++++++ nodejs/src/stmt/wsStmt2.ts | 399 ++++++++++++++++++++++++++++++++ nodejs/src/stmt/wsTableInfo.ts | 47 ++++ 7 files changed, 1337 insertions(+) create mode 100644 nodejs/src/stmt/wsColumnInfo.ts create mode 100644 nodejs/src/stmt/wsParams1.ts create mode 100644 nodejs/src/stmt/wsParams2.ts create mode 100644 nodejs/src/stmt/wsParamsBase.ts create mode 100644 nodejs/src/stmt/wsStmt1.ts create mode 100644 nodejs/src/stmt/wsStmt2.ts create mode 100644 nodejs/src/stmt/wsTableInfo.ts diff --git a/nodejs/src/stmt/wsColumnInfo.ts b/nodejs/src/stmt/wsColumnInfo.ts new file mode 100644 index 00000000..c3f6e08d --- /dev/null +++ b/nodejs/src/stmt/wsColumnInfo.ts @@ -0,0 +1,23 @@ + +export class ColumnInfo { + data:ArrayBuffer; + length:number; + type:number; + typeLen:number; + isNull?: number[]; + _rows: number; + _haveLength: number = 0; + _dataLengths?: number[]; + constructor([length,data]:[number, ArrayBuffer], type:number, typeLen:number, rows:number, + isNull?: number[], dataLengths?: number[], haveLength: number = 0) { + this.data = data; + this.type = type; + this.length = length; + this.typeLen = typeLen; + this._rows = rows; + this.isNull = isNull; + this._dataLengths = dataLengths; + this._haveLength = haveLength; + } + +} \ No newline at end of file diff --git a/nodejs/src/stmt/wsParams1.ts b/nodejs/src/stmt/wsParams1.ts new file mode 100644 index 00000000..93c38ace --- /dev/null +++ b/nodejs/src/stmt/wsParams1.ts @@ -0,0 +1,271 @@ +import { PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; +import { ErrorCode, TaosError } from "../common/wsError"; +import { getCharOffset, setBitmapNull, bitmapLen} from "../common/taosResult" +import { isEmpty } from "../common/utils"; +import { ColumnInfo } from "./wsColumnInfo"; +import { IDataEncoder, StmtBindParams } from "./wsParamsBase"; + +export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ + constructor(precision?:number) { + super(precision); + } + + encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); + } + if (dataType === "number" || dataType === "bigint" || dataType === "boolean") { + return this.encodeDigitColumns(params, dataType, typeLen, columnType); + } else { + + if (columnType === TDengineTypeCode.NCHAR) { + return this.encodeNcharColumn(params); + } else { + return this.encodeVarLengthColumn(params, columnType); + } + } + } + + setTimestamp(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid!"); + } + + //computing bitmap length + let bitMapLen:number = bitmapLen(params.length) + //Computing the length of data + let arrayBuffer = new ArrayBuffer(bitMapLen + TDengineTypeLength['TIMESTAMP'] * params.length); + //bitmap get data range + let bitmapBuffer = new DataView(arrayBuffer) + //skip bitmap get data range + let dataBuffer = new DataView(arrayBuffer, bitMapLen) + if (this._rows > 0) { + if (this._rows !== params.length) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") + } + }else { + this._rows = params.length; + } + + for (let i = 0; i < params.length; i++) { + if (!isEmpty(params[i])) { + if (params[i] instanceof Date) { + let date:Date = params[i] + //node only support milliseconds, need fill 0 + if (this.precisionLength == PrecisionLength['us']) { + let ms = date.getMilliseconds() * 1000 + dataBuffer.setBigInt64(i * 8, BigInt(ms), true); + }else if (this.precisionLength == PrecisionLength['ns']) { + let ns = date.getMilliseconds() * 1000 * 1000 + dataBuffer.setBigInt64(i * 8, BigInt(ns), true); + }else { + dataBuffer.setBigInt64(i * 8, BigInt(date.getMilliseconds()), true); + } + + } else if (typeof params[i] == 'bigint' || typeof params[i] == 'number') { + + let data:bigint + if (typeof params[i] == 'number') { + data = BigInt(params[i]) + }else { + data = params[i] + } + //statistical bits of digit + let digit = this.countBigintDigits(data) + //check digit same table Precision + if (this.precisionLength == PrecisionLength['ns']) { + if (this.precisionLength <= digit) { + dataBuffer.setBigInt64(i * 8, data, true); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params precisionLength is invalid! param:=" + params[i]) + } + } else if (this.precisionLength == digit) { + dataBuffer.setBigInt64(i * 8, data, true); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid! param:=" + params[i]) + } + } + }else{ + //set bitmap bit is null + let charOffset = getCharOffset(i); + let nullVal = setBitmapNull(dataBuffer.getInt8(charOffset), i); + bitmapBuffer.setInt8(charOffset, nullVal); + } + } + + this._dataTotalLen += arrayBuffer.byteLength; + this._params.push(new ColumnInfo([TDengineTypeLength['TIMESTAMP'] * params.length, arrayBuffer], TDengineTypeCode.TIMESTAMP, TDengineTypeLength['TIMESTAMP'], this._rows)); + } + + + private encodeDigitColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):ColumnInfo { + let bitMapLen:number = bitmapLen(params.length) + let arrayBuffer = new ArrayBuffer(typeLen * params.length + bitMapLen); + let bitmapBuffer = new DataView(arrayBuffer) + let dataBuffer = new DataView(arrayBuffer, bitMapLen) + if (this._rows > 0) { + if (this._rows !== params.length) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") + } + }else { + this._rows = params.length; + } + + for (let i = 0; i < params.length; i++) { + if (!isEmpty(params[i])) { + this.writeDataToBuffer(dataBuffer, params[i], dataType, typeLen,columnType, i); + } else { + //set bitmap bit is null + let charOffset = getCharOffset(i); + let nullVal = setBitmapNull(bitmapBuffer.getUint8(charOffset), i); + bitmapBuffer.setInt8(charOffset, nullVal); + } + } + this._dataTotalLen += dataBuffer.buffer.byteLength; + return new ColumnInfo([typeLen * params.length, dataBuffer.buffer], columnType, typeLen, this._rows); + } + + private encodeVarLengthColumn(params:any[], columnType:number):ColumnInfo { + let data:ArrayBuffer[] = [] + let dataLength = 0; + //create params length buffer + let paramsLenBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) + let paramsLenView = new DataView(paramsLenBuffer) + if (this._rows > 0) { + if (this._rows !== params.length) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") + } + }else { + this._rows = params.length; + } + for (let i = 0; i < params.length; i++) { + //get param length offset 4byte + let offset = TDengineTypeLength['INT'] * i; + if (!isEmpty(params[i])) { + //save param length offset 4byte + paramsLenView.setInt32(offset, dataLength, true); + if (typeof params[i] == 'string' ) { + //string TextEncoder + let encode = new TextEncoder(); + let value = encode.encode(params[i]).buffer; + data.push(value); + //add offset length + dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + } else if (params[i] instanceof ArrayBuffer) { + //input arraybuffer, save not need encode + let value:ArrayBuffer = params[i]; + dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + data.push(value); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, + "getColumString params is invalid! param_type:=" + typeof params[i]); + } + + }else{ + //set length -1, param is null + for (let j = 0; j < TDengineTypeLength['INT']; j++) { + paramsLenView.setInt8(offset+j, 255); + } + + } + } + + this._dataTotalLen += paramsLenBuffer.byteLength + dataLength; + return new ColumnInfo([dataLength, this.getBinaryColumnArrayBuffer(data, paramsLenView.buffer, dataLength)], columnType, 0, this._rows); + } + //splicing encode params to arraybuffer + private getBinaryColumnArrayBuffer(data:ArrayBuffer[], paramsLenBuffer: ArrayBuffer, dataLength:number):ArrayBuffer { + //create arraybuffer + let paramsBuffer = new ArrayBuffer(paramsLenBuffer.byteLength + dataLength) + //get length data range + const paramsUint8 = new Uint8Array(paramsBuffer); + const paramsLenView = new Uint8Array(paramsLenBuffer); + paramsUint8.set(paramsLenView, 0); + //get data range + const paramsView = new DataView(paramsBuffer, paramsLenBuffer.byteLength); + + let offset = 0; + for (let i = 0; i < data.length; i++) { + //save param field length + paramsView.setInt16(offset, data[i].byteLength, true) + const dataView = new DataView(data[i]); + //save data + for (let j = 0; j < data[i].byteLength; j++) { + paramsView.setUint8(offset + 2 + j, dataView.getUint8(j)) + } + offset += data[i].byteLength + 2; + } + + return paramsBuffer + } + //encode nchar type params + private encodeNcharColumn(params:any[]):ColumnInfo { + let data:ArrayBuffer[] = [] + let dataLength = 0; + let indexBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) + let indexView = new DataView(indexBuffer) + if (this._rows > 0) { + if (this._rows !== params.length) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") + } + }else { + this._rows = params.length; + } + + for (let i = 0; i < params.length; i++) { + let offset = TDengineTypeLength['INT'] * i; + if (!isEmpty(params[i])) { + indexView.setInt32(offset, dataLength, true); + if (typeof params[i] == 'string' ) { + let codes:number[] = []; + let strNcharParams:string = params[i]; + for (let j = 0; j < params[i].length; j++) { + //get char, cn char need 3~4 byte + codes.push(strNcharParams.charCodeAt(j)); + } + + let ncharBuffer:ArrayBuffer = new ArrayBuffer(codes.length * 4); + let ncharView = new DataView(ncharBuffer); + for (let j = 0; j < codes.length; j++) { + //1char, save into uint32 + ncharView.setUint32(j*4, codes[j], true); + } + data.push(ncharBuffer); + dataLength += codes.length * 4 + TDengineTypeLength['SMALLINT']; + + } else if (params[i] instanceof ArrayBuffer) { + let value:ArrayBuffer = params[i] + dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + data.push(value); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "getColumString params is invalid! param_type:=" + typeof params[i]) + } + + }else{ + //set length -1, param is null + for (let j = 0; j < TDengineTypeLength['INT']; j++) { + indexView.setInt8(offset+j, 255) + } + + } + } + + this._dataTotalLen += indexBuffer.byteLength + dataLength; + return new ColumnInfo([dataLength, this.getBinaryColumnArrayBuffer(data, indexView.buffer, dataLength)], TDengineTypeCode.NCHAR, TDengineTypeLength['NCHAR'], this._rows); + } + + private countBigintDigits(numeral: bigint): number { + if (numeral === 0n) { + return 1; + } + let count = 0; + let temp = numeral; + while (temp !== 0n) { + temp /= 10n; + count++; + } + return count; + } + +} + diff --git a/nodejs/src/stmt/wsParams2.ts b/nodejs/src/stmt/wsParams2.ts new file mode 100644 index 00000000..f43d7be4 --- /dev/null +++ b/nodejs/src/stmt/wsParams2.ts @@ -0,0 +1,122 @@ +import { ColumnsBlockType, PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; +import { ErrorCode, TaosError } from "../common/wsError"; +import { getCharOffset, setBitmapNull, bitmapLen, _isVarType} from "../common/taosResult" +import { isEmpty } from "../common/utils"; +import { StmtFieldInfo } from "./wsProto"; +import { ColumnInfo } from "./wsColumnInfo"; +import { IDataEncoder, StmtBindParams } from "./wsParamsBase"; +import { timeStamp } from "console"; + + +export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { + constructor(precision?:number) { + super(precision); + } + + encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo{ + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); + } + + if (this._rows > 0) { + if (this._rows !== params.length) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") + } + }else { + this._rows = params.length; + } + + let isVarType = _isVarType(columnType) + if (isVarType == ColumnsBlockType.SOLID) { + if (dataType === "TIMESTAMP") { + return this.encodeTimestampColumn(params, typeLen, columnType); + } + return this.encodeDigitColumns(params, dataType, typeLen, columnType); + } + + return this.encodeVarColumns(params, dataType, typeLen, columnType); + + } + + private encodeVarColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):ColumnInfo { + let isNull: number[] = []; + let dataLengths: number[] = []; + // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + 4 * v.length + totalLength + // 17 + (5 * params.length) + totalLength; + const bytes: number[] = []; + for (let i = 0; i < params.length; i++) { + if (!isEmpty(params[i])) { + isNull.push(0); + if (typeof params[i] == 'string' ) { + let encoder = new TextEncoder().encode(params[i]); + let length = encoder.length; + dataLengths.push(length); + bytes.push(...encoder); + } + } else { + isNull.push(1); + } + } + + } + + private encodeDigitColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):ColumnInfo { + let isNull: number[] = []; + let dataLength = 17 + (typeLen + 1) * params.length; + let arrayBuffer = new ArrayBuffer(typeLen * params.length); + let dataBuffer = new DataView(arrayBuffer); + // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + size * dataLen + for (let i = 0; i < params.length; i++) { + if (!isEmpty(params[i])) { + isNull.push(0); + this.writeDataToBuffer(dataBuffer, params[i], dataType, typeLen,columnType, i); + } else { + isNull.push(1); + if (dataType === 'bigint') { + this.writeDataToBuffer(dataBuffer, BigInt(0), dataType, typeLen,columnType, i); + }else { + this.writeDataToBuffer(dataBuffer, 0, dataType, typeLen,columnType, i); + } + } + } + + this._dataTotalLen += dataLength; + return new ColumnInfo([dataLength, dataBuffer.buffer], columnType, typeLen, this._rows, isNull); + } + + + private encodeTimestampColumn(params:any[], typeLen:number, columnType:number):ColumnInfo { + let timeStamps = []; + for (let i = 0; i < params.length; i++) { + if (!isEmpty(params[i])) { + let timeStamp:bigint = BigInt(0); + if (params[i] instanceof Date) { + let date:Date = params[i] + //node only support milliseconds, need fill 0 + + if (this.precisionLength == PrecisionLength['us']) { + timeStamp = BigInt(date.getMilliseconds() * 1000); + }else if (this.precisionLength == PrecisionLength['ns']) { + timeStamp = BigInt(date.getMilliseconds() * 1000 * 1000); + }else { + timeStamp = BigInt(date.getMilliseconds()); + } + + } else if (typeof params[i] == 'bigint' || typeof params[i] == 'number') { + if (typeof params[i] == 'number') { + timeStamp = BigInt(params[i]) + }else { + timeStamp = params[i] + } + } + timeStamps.push(timeStamp); + }else{ + //set bitmap bit is null + timeStamps.push(null); + } + } + return this.encodeDigitColumns(timeStamps, 'bigint', typeLen, columnType); + } +} + + diff --git a/nodejs/src/stmt/wsParamsBase.ts b/nodejs/src/stmt/wsParamsBase.ts new file mode 100644 index 00000000..d6c2ad34 --- /dev/null +++ b/nodejs/src/stmt/wsParamsBase.ts @@ -0,0 +1,252 @@ +import { TDengineTypeCode, TDengineTypeLength, TDengineTypeName, PrecisionLength } from "../common/constant"; +import { bitmapLen } from "../common/taosResult"; +import { ErrorCode, TaosError } from "../common/wsError"; +import { ColumnInfo } from "./wsColumnInfo"; + + +export interface IDataEncoder { + encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo; +} + +export abstract class StmtBindParams { + protected readonly precisionLength:number = PrecisionLength['ms'] + protected readonly _params: ColumnInfo[]; + protected _dataTotalLen:number = 0; + protected _rows = 0; + + constructor(precision?:number) { + if (precision) { + this.precisionLength = precision + } + this._params = []; + } + + + + abstract encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo; + + getDataRows(): number { + return this._rows; + } + + getDataTotalLen(): number { + return this._dataTotalLen; + } + + + public getParams(): ColumnInfo[] { + return this._params; + } + + setBoolean(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBooleanColumn params is invalid!"); + } + let columnInfo = this.encode(params, "boolean", TDengineTypeLength['BOOL'], TDengineTypeCode.BOOL); + this._params.push(columnInfo); + } + + setTinyInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['TINYINT'], TDengineTypeCode.TINYINT) + this._params.push(columnInfo); + } + + setUTinyInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUTinyIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['TINYINT UNSIGNED'], TDengineTypeCode.TINYINT_UNSIGNED) + this._params.push(columnInfo); + } + + setSmallInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['SMALLINT'], TDengineTypeCode.SMALLINT) + this._params.push(columnInfo); + + } + + setUSmallInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['SMALLINT UNSIGNED'], TDengineTypeCode.SMALLINT_UNSIGNED) + this._params.push(columnInfo); + } + + setInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['INT'], TDengineTypeCode.INT) + this._params.push(columnInfo); + } + + setUInt(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['INT UNSIGNED'], TDengineTypeCode.INT_UNSIGNED) + this._params.push(columnInfo); + } + + setBigint(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBigIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "bigint", TDengineTypeLength['BIGINT'], TDengineTypeCode.BIGINT) + this._params.push(columnInfo); + } + + setUBigint(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUBigIntColumn params is invalid!"); + } + let columnInfo = this.encode(params, "bigint", TDengineTypeLength['BIGINT UNSIGNED'], TDengineTypeCode.BIGINT_UNSIGNED) + this._params.push(columnInfo); + } + + setFloat(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetFloatColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['FLOAT'], TDengineTypeCode.FLOAT) + this._params.push(columnInfo); + } + + setDouble(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetDoubleColumn params is invalid!"); + } + let columnInfo = this.encode(params, "number", TDengineTypeLength['DOUBLE'], TDengineTypeCode.DOUBLE) + this._params.push(columnInfo); + } + + setVarchar(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetVarcharColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[8], 0, TDengineTypeCode.VARCHAR); + this._params.push(columnInfo); + } + + setBinary(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[8], 0, TDengineTypeCode.BINARY); + this._params.push(columnInfo); + } + + setNchar(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetNcharColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[10], 0, TDengineTypeCode.NCHAR); + this._params.push(columnInfo); + } + + setJson(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetJsonColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[15], 0, TDengineTypeCode.JSON); + this._params.push(columnInfo); + } + + setVarBinary(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetVarBinaryColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[16], 0, TDengineTypeCode.VARBINARY); + this._params.push(columnInfo); + } + + setGeometry(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetGeometryColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[16], 0, TDengineTypeCode.GEOMETRY); + this._params.push(columnInfo); + } + + setTimestamp(params :any[]) { + if (!params || params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid!"); + } + let columnInfo = this.encode(params, TDengineTypeName[9], TDengineTypeLength['TIMESTAMP'], TDengineTypeCode.TIMESTAMP); + this._params.push(columnInfo); + } + + protected writeDataToBuffer(dataBuffer: DataView, params: any, dataType: string = 'number', typeLen: number, columnType: number, i:number): void { + if (typeof params == dataType) { + switch (columnType) { + case TDengineTypeCode.BOOL: { + if (params) { + dataBuffer.setInt8(i, 1); + } else { + dataBuffer.setInt8(i, 0); + } + break; + } + case TDengineTypeCode.TINYINT: { + dataBuffer.setInt8(i, params); + break; + } + case TDengineTypeCode.TINYINT_UNSIGNED: { + dataBuffer.setUint8(i, params); + break; + } + case TDengineTypeCode.SMALLINT: { + dataBuffer.setInt16(i * 2, params, true); + break; + } + case TDengineTypeCode.SMALLINT_UNSIGNED: { + dataBuffer.setUint16(i * 2, params, true); + break; + } + + case TDengineTypeCode.INT: { + dataBuffer.setInt32(i * 4, params, true); + break; + } + + case TDengineTypeCode.INT_UNSIGNED: { + dataBuffer.setUint32(i * 4, params, true); + break; + } + + case TDengineTypeCode.BIGINT: { + dataBuffer.setBigInt64(i * 8, params, true); + break; + } + + case TDengineTypeCode.BIGINT_UNSIGNED: { + dataBuffer.setBigUint64(i * 8, params, true); + break; + } + + case TDengineTypeCode.FLOAT: { + dataBuffer.setFloat32(i * 4, params, true); + break; + } + case TDengineTypeCode.DOUBLE: { + dataBuffer.setFloat64(i * 8, params, true); + break; + } + default: { + throw new TaosError(ErrorCode.ERR_UNSUPPORTED_TDENGINE_TYPE, "unsupported type for column" + columnType) + } + } + + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid! param:=" + params[i]) + } + } + +} \ No newline at end of file diff --git a/nodejs/src/stmt/wsStmt1.ts b/nodejs/src/stmt/wsStmt1.ts new file mode 100644 index 00000000..3d4b6089 --- /dev/null +++ b/nodejs/src/stmt/wsStmt1.ts @@ -0,0 +1,223 @@ +import JSONBig from 'json-bigint'; +import { WsClient } from '../client/wsClient'; +import { ErrorCode, TDWebSocketClientError, TaosError, TaosResultError } from '../common/wsError'; +import { WsStmtQueryResponse, StmtMessageInfo, binaryBlockEncode, StmtBindType } from './wsProto'; +import { ReqId } from '../common/reqid'; +import { PrecisionLength } from '../common/constant'; +import { StmtBindParams } from './wsParamsBase'; +import { WsStmt } from './wsStmt'; +import logger from '../common/log'; +import { Stmt1BindParams } from './wsParams1'; + +export class WsStmt1 implements WsStmt{ + private _wsClient: WsClient; + private _stmt_id: bigint | undefined | null; + private _precision:number = PrecisionLength['ms']; + + private lastAffected: number | undefined | null; + private constructor(wsClient: WsClient, precision?:number) { + this._wsClient = wsClient; + if (precision) { + this._precision = precision; + } + } + + static async newStmt(wsClient: WsClient, precision?:number, reqId?:number): Promise { + try { + let wsStmt = new WsStmt1(wsClient, precision) + return await wsStmt.init(reqId); + } catch(e: any) { + logger.error(`WsStmt init is failed, ${e.code}, ${e.message}`); + throw(e); + } + + } + + async prepare(sql: string): Promise { + let queryMsg = { + action: 'prepare', + args: { + req_id: ReqId.getReqID(), + sql: sql, + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + async setTableName(tableName: string): Promise { + let queryMsg = { + action: 'set_table_name', + args: { + req_id: ReqId.getReqID(), + name: tableName, + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + async setJsonTags(tags: Array): Promise { + let queryMsg = { + action: 'set_tags', + args: { + req_id: ReqId.getReqID(), + tags: tags, + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + newStmtParam():Stmt1BindParams { + return new Stmt1BindParams(this._precision); + } + + async setTags(paramsArray:StmtBindParams): Promise { + if (!paramsArray || !this._stmt_id) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!") + } + + let columnInfos = paramsArray.getParams(); + if (!columnInfos) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!") + } + let reqId = BigInt(ReqId.getReqID()) + let dataBlock = binaryBlockEncode(paramsArray, StmtBindType.STMT_TYPE_TAG, this._stmt_id, reqId, paramsArray.getDataRows()) + return await this.sendBinaryMsg(reqId, 'set_tags', dataBlock); + } + + async bind(paramsArray:StmtBindParams): Promise { + if (!paramsArray || !this._stmt_id) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "BinaryBind paramArray is invalid!") + } + + let columnInfos = paramsArray.getParams(); + if (!columnInfos) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "BinaryBind paramArray is invalid!") + } + let reqId = BigInt(ReqId.getReqID()) + let dataBlock = binaryBlockEncode(paramsArray, StmtBindType.STMT_TYPE_BIND, this._stmt_id, reqId, paramsArray.getDataRows()); + return await this.sendBinaryMsg(reqId, 'bind', dataBlock); + } + + async jsonBind(paramArray: Array>): Promise { + let queryMsg = { + action: 'bind', + args: { + req_id: ReqId.getReqID(), + columns: paramArray, + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + async batch(): Promise { + let queryMsg = { + action: 'add_batch', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + async exec(): Promise { + let queryMsg = { + action: 'exec', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg); + } + + getLastAffected() { + return this.lastAffected; + } + + async close(): Promise { + let queryMsg = { + action: 'close', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + return await this.execute(queryMsg, false); + } + + public getStmtId(): bigint | undefined | null { + return this._stmt_id; + } + + private async execute(queryMsg: StmtMessageInfo, register: Boolean = true): Promise { + try { + if (this._wsClient.getState() <= 0) { + throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); + } + let reqMsg = JSONBig.stringify(queryMsg); + if (register) { + let result = await this._wsClient.exec(reqMsg, false); + let resp = new WsStmtQueryResponse(result) + if (resp.stmt_id) { + this._stmt_id = resp.stmt_id; + } + + if (resp.affected) { + this.lastAffected = resp.affected + } + }else{ + await this._wsClient.execNoResp(reqMsg); + this._stmt_id = null + this.lastAffected = null + } + return + } catch (e:any) { + throw new TaosResultError(e.code, e.message); + } + } + + private async sendBinaryMsg(reqId: bigint, action:string, message: ArrayBuffer): Promise { + if (this._wsClient.getState() <= 0) { + throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); + } + let result = await this._wsClient.sendBinaryMsg(reqId, action, message, false); + let resp = new WsStmtQueryResponse(result) + if (resp.stmt_id) { + this._stmt_id = resp.stmt_id; + } + + if (resp.affected) { + this.lastAffected = resp.affected + } + } + + private async init(reqId?: number):Promise { + if (this._wsClient) { + try { + if (this._wsClient.getState() <= 0) { + await this._wsClient.connect(); + await this._wsClient.checkVersion(); + } + let queryMsg = { + action: 'init', + args: { + req_id: ReqId.getReqID(reqId), + }, + }; + await this.execute(queryMsg); + return this; + } catch (e: any) { + logger.error(`stmt init filed, ${e.code}, ${e.message}`); + throw(e); + } + + } + throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); + } + +} diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts new file mode 100644 index 00000000..94b56dff --- /dev/null +++ b/nodejs/src/stmt/wsStmt2.ts @@ -0,0 +1,399 @@ +// import JSONBig from 'json-bigint'; +// import { WsClient } from "../client/wsClient"; +// import { ColumnsBlockType, FieldBindType, PrecisionLength } from "../common/constant"; +// import logger from "../common/log"; +// import { ReqId } from "../common/reqid"; +// import { ErrorCode, TaosResultError, TDWebSocketClientError } from "../common/wsError"; +// import { ColumnInfo, StmtBindParams, TableInfo } from "./wsParamsBase"; +// import { StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; +// import { WsStmt } from "./wsStmt"; +// import { _isVarType } from '../common/taosResult'; +// import { bigintToBytes, intToBytes, shotToBytes } from '../common/utils'; + + + +// export class WsStmt2 implements WsStmt{ +// private _wsClient: WsClient; +// private _stmt_id: bigint | undefined | null; +// private _precision:number = PrecisionLength['ms']; +// private fields?: Array | undefined | null; +// private lastAffected: number | undefined | null; +// private _stmtTableInfo: Map; +// private _currentTableInfo: TableInfo; +// private _stmtTableInfoList: TableInfo[]; +// private _toBeBindTagCount: number; +// private _toBeBindColCount: number; +// private _toBeBindTableNameIndex: number | undefined | null; + +// private constructor(wsClient: WsClient, precision?:number) { +// this._wsClient = wsClient; +// if (precision) { +// this._precision = precision; +// } +// this._stmtTableInfo = new Map(); +// this._currentTableInfo = new TableInfo(); +// this._stmtTableInfoList = []; +// this._toBeBindColCount = 0; +// this._toBeBindTagCount = 0; +// } + +// static async newStmt(wsClient: WsClient, precision?:number, reqId?:number): Promise { +// try { +// let wsStmt = new WsStmt2(wsClient, precision) +// return await wsStmt.init(reqId); +// } catch(e: any) { +// logger.error(`WsStmt init is failed, ${e.code}, ${e.message}`); +// throw(e); +// } + +// } + +// private async init(reqId: number | undefined): Promise { +// if (this._wsClient) { +// try { +// if (this._wsClient.getState() <= 0) { +// await this._wsClient.connect(); +// await this._wsClient.checkVersion(); +// } +// let queryMsg = { +// action: 'stmt2_init', +// args: { +// req_id: ReqId.getReqID(reqId), +// single_stb_insert: true, +// single_table_bind_once: true +// }, +// }; +// await this.execute(queryMsg); +// return this; +// } catch (e: any) { +// logger.error(`stmt init filed, ${e.code}, ${e.message}`); +// throw(e); +// } + +// } +// throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); +// } + +// async prepare(sql: string): Promise { +// let queryMsg = { +// action: 'stmt2_prepare', +// args: { +// req_id: ReqId.getReqID(), +// sql: sql, +// stmt_id: this._stmt_id, +// get_fields: true, +// }, +// }; +// await this.execute(queryMsg); +// if (this.fields && this.fields[0].precision) { +// this._precision = this.fields[0].precision; +// } + +// this._toBeBindColCount = 0; +// this._toBeBindTagCount = 0; +// this.fields?.forEach((field, index) => { +// if (field.bind_type == FieldBindType.TAOS_FIELD_TBNAME) { +// this._toBeBindTableNameIndex = index; +// } else if (field.bind_type == FieldBindType.TAOS_FIELD_TAG) { +// this._toBeBindTagCount++; +// } else if (field.bind_type == FieldBindType.TAOS_FIELD_COL) { +// this._toBeBindColCount++; +// } +// }); + +// } + +// async setTableName(tableName: string): Promise { +// if (!tableName || tableName.length === 0) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, 'Table name cannot be empty'); +// } +// let tableInfo = this._stmtTableInfo.get(tableName); +// if (!tableInfo) { +// this._currentTableInfo = new TableInfo(tableName); +// this._stmtTableInfo.set(tableName, this._currentTableInfo); +// this._stmtTableInfoList.push(this._currentTableInfo); +// console.log(`New table info created for table: ${this._currentTableInfo.getTableName()}`); +// } else { +// this._currentTableInfo = tableInfo; +// } +// return Promise.resolve(); +// } + +// async setTags(paramsArray: StmtBindParams): Promise { +// if (!paramsArray || !this._stmt_id) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!"); +// } +// for (let i = 0; i < paramsArray.getParams().length; i++) { +// let param = paramsArray.getParams()[i]; +// console.log(`Tag param[${i}]: ${JSONBig.stringify(param.data)}`); +// } +// if (!this._currentTableInfo) { +// this._currentTableInfo = new TableInfo(); +// this._stmtTableInfoList.push(this._currentTableInfo); +// } +// await this._currentTableInfo.setTags(paramsArray); +// console.log(`Set tags for table: ${this._currentTableInfo.getTableName()}, tags: ${JSONBig.stringify(paramsArray.getParams())}`); +// return Promise.resolve(); +// } + +// newStmtParam(): StmtBindParams { +// return new StmtBindParams(this._precision); +// } + +// async bind(paramsArray: StmtBindParams): Promise { +// if (!paramsArray || !this._stmt_id) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind paramArray is invalid!"); +// } +// if (!this._currentTableInfo) { +// this._currentTableInfo = new TableInfo(); +// this._stmtTableInfoList.push(this._currentTableInfo); +// } +// await this._currentTableInfo.setParams(paramsArray); +// return Promise.resolve(); +// } + +// async batch(): Promise { +// Promise.resolve(); +// } + +// async exec(): Promise { +// if (!this._currentTableInfo || this._currentTableInfo.getParams.length === 0) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); +// } +// let reqId = BigInt(ReqId.getReqID()); +// for (let tableInfo of this._stmtTableInfoList) { +// console.log(`tableInfo: ${tableInfo.getParams()}, tags: ${tableInfo.getTags()}`); +// } + +// } +// getLastAffected(): number | null | undefined { +// throw new Error("Method not implemented."); +// } +// async close(): Promise { +// throw new Error("Method not implemented."); +// } + +// private async execute(queryMsg: StmtMessageInfo, register: Boolean = true): Promise { +// try { +// if (this._wsClient.getState() <= 0) { +// throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); +// } +// let reqMsg = JSONBig.stringify(queryMsg); +// if (register) { +// let result = await this._wsClient.exec(reqMsg, false); +// let resp = new WsStmtQueryResponse(result) +// if (resp.stmt_id) { +// this._stmt_id = resp.stmt_id; +// } + +// if (resp.affected) { +// this.lastAffected = resp.affected +// } + +// if (resp.fields) { +// this.fields = resp.fields; +// console.log(`Fields: ${JSONBig.stringify(resp.fields)}`); +// } +// }else{ +// await this._wsClient.execNoResp(reqMsg); +// this._stmt_id = null +// this.lastAffected = null +// } +// return +// } catch (e:any) { +// throw new TaosResultError(e.code, e.message); +// } +// } + +// private binaryBlockEncode() { +// // cloc totol size +// let totalTableNameSize = 0; +// let tableNameSizeList:Array = []; +// if (this._toBeBindTableNameIndex && this._toBeBindTableNameIndex > 0) { +// this._stmtTableInfo.forEach((tableInfo) => { +// let tableName = tableInfo.getTableName(); +// if (tableName) { +// let size = new TextEncoder().encode(tableName).length; +// totalTableNameSize += size; +// tableNameSizeList.push(size); +// } else { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); +// } +// }); +// } + +// let totalTagSize = 0; +// let tagSizeList:Array = []; +// if (this._toBeBindColCount > 0) { +// this._stmtTableInfoList.forEach((tableInfo) => { +// let params = tableInfo.getTags(); +// if (params) { +// let tagSize = params.getDataTotalLen(); +// totalTagSize += tagSize; +// tagSizeList.push(tagSize); +// } +// }); +// } +// let totalColSize = 0; +// let colSizeList:Array = []; +// if (this._toBeBindColCount > 0) { +// this._stmtTableInfoList.forEach((tableInfo) => { +// let params = tableInfo.getParams(); +// if (!params || params.length === 0) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); +// } + +// params.forEach((param) => { +// let colSize = param.getDataTotalLen(); +// totalColSize += colSize; +// colSizeList.push(colSize); +// }); + +// }); +// } + + +// let totalSize = totalTableNameSize + totalTagSize + totalColSize; +// let toBeBindTableNameCount = (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex >= 0) ? 1 : 0; +// totalSize += this._stmtTableInfoList.length * ( +// toBeBindTableNameCount * 2 +// + (this._toBeBindTagCount > 0 ? 1 : 0) * 4 +// + (this._toBeBindColCount > 0 ? 1 : 0) * 4); + + +// const bytes: number[] = []; + + + +// // 写入 req_id +// bytes.push(...bigintToBytes(BigInt(ReqId.getReqID()))); + +// // 写入 stmt_id +// if (this._stmt_id) { +// bytes.push(...bigintToBytes(this._stmt_id)); +// } + +// bytes.push(...bigintToBytes(9n)); +// bytes.push(...shotToBytes(1)); +// bytes.push(...intToBytes(-1)); +// bytes.push(...intToBytes(totalSize + 28, false)); +// bytes.push(...intToBytes(this._stmtTableInfoList.length)); +// bytes.push(...intToBytes(this._toBeBindTagCount)); +// bytes.push(...intToBytes(this._toBeBindColCount)); +// if (toBeBindTableNameCount > 0) { +// bytes.push(...intToBytes(0x1C)); +// } else { +// bytes.push(...intToBytes(0)); +// } + +// if (this._toBeBindTagCount) { +// if (toBeBindTableNameCount > 0) { +// bytes.push(...intToBytes(28 + totalTableNameSize * 2 * this._stmtTableInfoList.length)); +// } else { +// bytes.push(...intToBytes(28)); +// } +// } else { +// bytes.push(...intToBytes(0)); +// } + +// if (toBeBindTableNameCount > 0) { +// for (let size of tableNameSizeList) { +// if (size === 0) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); +// } +// bytes.push(...shotToBytes(size)); +// } +// for (let tableInfo of this._stmtTableInfoList) { +// let tableName = tableInfo.getTableName(); +// if (tableName && tableName.length > 0) { +// let encoder = new TextEncoder().encode(tableName); +// bytes.push(...encoder); +// bytes.push(0); // null terminator +// } else { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); +// } +// } +// } + +// if (this._toBeBindTagCount > 0) { +// for (let size of tagSizeList) { +// bytes.push(...shotToBytes(size)); +// } + +// for (let tableInfo of this._stmtTableInfoList) { +// let tags = tableInfo.getTags(); +// if (tags && tags.getParams().length > 0) { +// for (let tagColumnInfo of tags.getParams()) { +// let isVarType = _isVarType(tagColumnInfo.type) +// if (isVarType == ColumnsBlockType.SOLID) { +// if (tagColumnInfo.data === null || tagColumnInfo.data === undefined) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tag value is null"); +// } + +// bytes.push(...tag.getData()); +// } +// } +// } else { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); +// } +// } +// } +// // // TagsDataLength +// // if (toBebindTagCount > 0) { +// // for (Integer tagsize : tagSizeList) { +// // buf.writeIntLE(tagsize); +// // } + +// // for (TableInfo tableInfo : tableInfoMap.values()) { +// // for (ColumnInfo tag : tableInfo.getTagInfo()) { +// // if (tag.getDataList().isEmpty()) { +// // throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_INVALID_VARIABLE, "tag value is null, tbname: " + tableInfo.getTableName().toString()); +// // } +// // serializeColumn(tag, buf, precision); +// // } +// // } +// // } + +// } + +// private serializeColumn(column: ColumnInfo, bytes: number[], precision: number) { +// let isVarType = _isVarType(column.type) +// if (isVarType == ColumnsBlockType.SOLID) { +// if (column.data === null || column.data === undefined) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); +// } +// // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + size * dataLen +// let dataLen = 17 + (dataLen + 1) * +// bytes.push(...column.getData()); +// } else if (isVarType == ColumnsBlockType.VAR) { +// let dataList = column.getDataList(); +// if (!dataList || dataList.length === 0) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); +// } +// dataList.forEach((data) => { +// if (data === null || data === undefined) { +// bytes.push(...intToBytes(-1)); +// } else { +// let encoder = new TextEncoder().encode(data); +// bytes.push(...intToBytes(encoder.length)); +// bytes.push(...encoder); +// } +// }); +// } else if (isVarType == ColumnsBlockType.TIME) { +// if (column.data === null || column.data === undefined) { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); +// } +// let timeValue = BigInt(column.data); +// if (precision < 9) { +// let factor = BigInt(10 ** (9 - precision)); +// timeValue = timeValue * factor; +// } else if (precision > 9) { +// let factor = BigInt(10 ** (precision - 9)); +// timeValue = timeValue / factor; +// } +// bytes.push(...bigintToBytes(timeValue)); +// } else { +// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, `Unsupported column type: ${column.type}`); +// } +// } +// } \ No newline at end of file diff --git a/nodejs/src/stmt/wsTableInfo.ts b/nodejs/src/stmt/wsTableInfo.ts new file mode 100644 index 00000000..8fc48606 --- /dev/null +++ b/nodejs/src/stmt/wsTableInfo.ts @@ -0,0 +1,47 @@ +import { ErrorCode, TaosError } from "../common/wsError"; +import { StmtBindParams } from "./wsParamsBase"; + +export class TableInfo { + name: string | undefined | null; + tags: StmtBindParams | undefined | null; + params: Array; + + constructor(name?: string) { + this.name = name; + this.params = []; + } + + public getTableName(): string | undefined | null { + return this.name; + } + + public getTags(): StmtBindParams | undefined | null { + return this.tags; + } + + public getParams(): Array | undefined | null { + return this.params; + } + + public setTableName(name: string): void { + if (name && name.length > 0) { + this.name = name; + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table name is invalid!"); + } + } + async setTags(paramsArray:StmtBindParams): Promise { + if (paramsArray) { + this.tags = paramsArray; + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table tags is invalid!"); + } + } + async setParams(params: StmtBindParams): Promise { + if (params) { + this.params.push(params); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table params is invalid!"); + } + } +} \ No newline at end of file From 35de22ad3b2f3c5d91cbe5b0137dc71246803355 Mon Sep 17 00:00:00 2001 From: menshibin Date: Wed, 10 Sep 2025 21:26:57 +0800 Subject: [PATCH 03/23] feat: support stmt2 writing --- nodejs/src/sql/wsSql.ts | 3 +- nodejs/src/stmt/FieldBindParams.ts | 13 + nodejs/src/stmt/wsParams1.ts | 16 +- nodejs/src/stmt/wsParams2.ts | 97 ++- nodejs/src/stmt/wsParamsBase.ts | 92 +-- nodejs/src/stmt/wsStmt2.ts | 837 ++++++++++++---------- nodejs/src/stmt/wsTableInfo.ts | 17 +- nodejs/test/bulkPulling/sql.test.ts | 4 +- nodejs/test/bulkPulling/stmt.func.test.ts | 23 +- nodejs/test/bulkPulling/stmt.type.test.ts | 10 +- nodejs/test/utils.ts | 1 + 11 files changed, 628 insertions(+), 485 deletions(-) create mode 100644 nodejs/src/stmt/FieldBindParams.ts diff --git a/nodejs/src/sql/wsSql.ts b/nodejs/src/sql/wsSql.ts index caffc9a1..2ff3bb55 100644 --- a/nodejs/src/sql/wsSql.ts +++ b/nodejs/src/sql/wsSql.ts @@ -11,6 +11,7 @@ import { ReqId } from '../common/reqid' import { BinaryQueryMessage, FetchRawBlockMessage, PrecisionLength, TSDB_OPTION_CONNECTION } from '../common/constant' import logger from '../common/log' import { WsStmt } from '../stmt/wsStmt' +import { WsStmt2 } from '../stmt/wsStmt2' export class WsSql{ private wsConfig:WSConfig; @@ -107,7 +108,7 @@ export class WsSql{ precision = PrecisionLength[data[0][0]] } } - return await WsStmt1.newStmt(this._wsClient, precision, reqId); + return await WsStmt2.newStmt(this._wsClient, precision, reqId); } catch (e: any) { logger.error(`stmtInit failed, code: ${e.code}, message: ${e.message}`); throw(e); diff --git a/nodejs/src/stmt/FieldBindParams.ts b/nodejs/src/stmt/FieldBindParams.ts new file mode 100644 index 00000000..d085c73c --- /dev/null +++ b/nodejs/src/stmt/FieldBindParams.ts @@ -0,0 +1,13 @@ + +export class FieldBindParams { + params: any[]; + dataType: string; + typeLen: number; + columnType: number; + constructor(params:any[], dataType:string, typeLen:number, columnType:number) { + this.params = [...params]; + this.dataType = dataType; + this.typeLen = typeLen; + this.columnType = columnType; + } +} \ No newline at end of file diff --git a/nodejs/src/stmt/wsParams1.ts b/nodejs/src/stmt/wsParams1.ts index 93c38ace..a5792aff 100644 --- a/nodejs/src/stmt/wsParams1.ts +++ b/nodejs/src/stmt/wsParams1.ts @@ -10,18 +10,26 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ super(precision); } - encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo { + encode(): void { + return; + } + + mergeParams(bindParams: StmtBindParams): void { + return; + } + + addParams(params: any[], dataType: string, typeLen: number, columnType: number): void { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); } if (dataType === "number" || dataType === "bigint" || dataType === "boolean") { - return this.encodeDigitColumns(params, dataType, typeLen, columnType); + this._params.push(this.encodeDigitColumns(params, dataType, typeLen, columnType)); } else { if (columnType === TDengineTypeCode.NCHAR) { - return this.encodeNcharColumn(params); + this._params.push(this.encodeNcharColumn(params)); } else { - return this.encodeVarLengthColumn(params, columnType); + this._params.push(this.encodeVarLengthColumn(params, columnType)); } } } diff --git a/nodejs/src/stmt/wsParams2.ts b/nodejs/src/stmt/wsParams2.ts index f43d7be4..d678a543 100644 --- a/nodejs/src/stmt/wsParams2.ts +++ b/nodejs/src/stmt/wsParams2.ts @@ -1,41 +1,83 @@ import { ColumnsBlockType, PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; import { ErrorCode, TaosError } from "../common/wsError"; -import { getCharOffset, setBitmapNull, bitmapLen, _isVarType} from "../common/taosResult" import { isEmpty } from "../common/utils"; -import { StmtFieldInfo } from "./wsProto"; import { ColumnInfo } from "./wsColumnInfo"; import { IDataEncoder, StmtBindParams } from "./wsParamsBase"; -import { timeStamp } from "console"; - +import { _isVarType } from "../common/taosResult"; +import { FieldBindParams } from "./FieldBindParams"; +import JSONBig from 'json-bigint'; export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { - constructor(precision?:number) { - super(precision); + protected paramIndex:number = 0; + constructor(paramsCount: number, precision?:number) { + super(precision, paramsCount); } - encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo{ + addParams(params: any[], dataType: string, typeLen: number, columnType: number): void { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); } + if (this._fieldParams){ + if (this._fieldParams[this.paramIndex]) { + if (this._fieldParams[this.paramIndex].dataType !== dataType || this._fieldParams[this.paramIndex].columnType !== columnType) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, `StmtBindParams params type is not match! ${this.paramIndex} ${this.paramsCount} ${JSONBig.stringify({ dataType, columnType } )} vs ${JSONBig.stringify({ dataType: this._fieldParams[this.paramIndex].dataType, columnType: this._fieldParams[this.paramIndex].columnType})}`); + } + this._fieldParams[this.paramIndex].params.push(...params); + + } else { + this._fieldParams[this.paramIndex] = new FieldBindParams(params, dataType, typeLen, columnType); + } + } + this.paramIndex++; + if (this.paramIndex >= this.paramsCount) { + this.paramIndex = 0; + } + } + + mergeParams(bindParams: StmtBindParams): void { + if (!bindParams || !bindParams._fieldParams || bindParams._fieldParams.length === 0 || !this._fieldParams) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); + } + this.paramIndex = 0; + for (let i = 0; i < bindParams._fieldParams.length; i++) { + let fieldParam = bindParams._fieldParams[i]; + if (fieldParam) { + this.addParams(fieldParam.params, fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType); + } + } + console.log(`StmtBindParams merge params, field params size: ${this._fieldParams[0].params.length}`); + } + + encode(): void{ + this.paramIndex = 0; + if (!this._fieldParams || this._fieldParams.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); + } if (this._rows > 0) { - if (this._rows !== params.length) { + if (this._rows !== this._fieldParams[0].params.length) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "wrong row length!") } }else { - this._rows = params.length; + this._rows = this._fieldParams[0].params.length; } - - let isVarType = _isVarType(columnType) - if (isVarType == ColumnsBlockType.SOLID) { - if (dataType === "TIMESTAMP") { - return this.encodeTimestampColumn(params, typeLen, columnType); + for (let i = 0; i < this._fieldParams.length; i++) { + let fieldParam = this._fieldParams[i]; + if (!fieldParam) { + continue; } - return this.encodeDigitColumns(params, dataType, typeLen, columnType); - } - - return this.encodeVarColumns(params, dataType, typeLen, columnType); + let isVarType = _isVarType(fieldParam.columnType); + if (isVarType == ColumnsBlockType.SOLID) { + if (fieldParam.dataType === "TIMESTAMP") { + this._params.push(this.encodeTimestampColumn(fieldParam.params, fieldParam.typeLen, fieldParam.columnType)); + } else { + this._params.push(this.encodeDigitColumns(fieldParam.params, fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType)); + } + } else { + this._params.push(this.encodeVarColumns(fieldParam.params, fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType)); + } + } } private encodeVarColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):ColumnInfo { @@ -43,6 +85,7 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { let dataLengths: number[] = []; // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + 4 * v.length + totalLength // 17 + (5 * params.length) + totalLength; + let totalLength = 17 + (5 * params.length); const bytes: number[] = []; for (let i = 0; i < params.length; i++) { if (!isEmpty(params[i])) { @@ -50,22 +93,34 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { if (typeof params[i] == 'string' ) { let encoder = new TextEncoder().encode(params[i]); let length = encoder.length; + totalLength += length; dataLengths.push(length); bytes.push(...encoder); - } + } else if (params[i] instanceof ArrayBuffer) { + //input arraybuffer, save not need encode + let value:ArrayBuffer = params[i]; + totalLength += value.byteLength; + dataLengths.push(value.byteLength); + bytes.push(...new Uint8Array(value)); + } else { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, + "getColumString params is invalid! param_type:=" + typeof params[i]); + } } else { isNull.push(1); } } - + this._dataTotalLen += totalLength; + const dataBuffer = new Uint8Array(bytes).buffer; + return new ColumnInfo([totalLength, dataBuffer], columnType, typeLen, this._rows, isNull, dataLengths, 1); } private encodeDigitColumns(params:any[], dataType:string = 'number', typeLen:number, columnType:number):ColumnInfo { let isNull: number[] = []; + // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + size * dataLen let dataLength = 17 + (typeLen + 1) * params.length; let arrayBuffer = new ArrayBuffer(typeLen * params.length); let dataBuffer = new DataView(arrayBuffer); - // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + size * dataLen for (let i = 0; i < params.length; i++) { if (!isEmpty(params[i])) { isNull.push(0); diff --git a/nodejs/src/stmt/wsParamsBase.ts b/nodejs/src/stmt/wsParamsBase.ts index d6c2ad34..2604dea4 100644 --- a/nodejs/src/stmt/wsParamsBase.ts +++ b/nodejs/src/stmt/wsParamsBase.ts @@ -1,29 +1,40 @@ import { TDengineTypeCode, TDengineTypeLength, TDengineTypeName, PrecisionLength } from "../common/constant"; import { bitmapLen } from "../common/taosResult"; import { ErrorCode, TaosError } from "../common/wsError"; +import { FieldBindParams } from "./FieldBindParams"; import { ColumnInfo } from "./wsColumnInfo"; - +import JSONBig from 'json-bigint'; export interface IDataEncoder { - encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo; + encode(): void; + addParams(params: any[], dataType: string, typeLen: number, columnType: number): void; + mergeParams(bindParams: StmtBindParams): void; } export abstract class StmtBindParams { protected readonly precisionLength:number = PrecisionLength['ms'] protected readonly _params: ColumnInfo[]; + _fieldParams?: FieldBindParams[]; protected _dataTotalLen:number = 0; + protected paramsCount: number = 0; protected _rows = 0; - constructor(precision?:number) { + constructor(precision?:number, paramsCount?: number) { if (precision) { this.precisionLength = precision } this._params = []; + if (paramsCount) { + this.paramsCount = paramsCount; + this._fieldParams = new Array(paramsCount); + } } + abstract encode(): void; + abstract addParams(params: any[], dataType: string, typeLen: number, columnType: number): void; - abstract encode(params: any[], dataType: string, typeLen: number, columnType: number): ColumnInfo; + abstract mergeParams(bindParams: StmtBindParams): void; getDataRows(): number { return this._rows; @@ -42,32 +53,30 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBooleanColumn params is invalid!"); } - let columnInfo = this.encode(params, "boolean", TDengineTypeLength['BOOL'], TDengineTypeCode.BOOL); - this._params.push(columnInfo); + this.addParams(params, "boolean", TDengineTypeLength['BOOL'], TDengineTypeCode.BOOL); } setTinyInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['TINYINT'], TDengineTypeCode.TINYINT) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['TINYINT'], TDengineTypeCode.TINYINT) } setUTinyInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUTinyIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['TINYINT UNSIGNED'], TDengineTypeCode.TINYINT_UNSIGNED) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['TINYINT UNSIGNED'], TDengineTypeCode.TINYINT_UNSIGNED) + } setSmallInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['SMALLINT'], TDengineTypeCode.SMALLINT) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['SMALLINT'], TDengineTypeCode.SMALLINT) + } @@ -75,112 +84,110 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['SMALLINT UNSIGNED'], TDengineTypeCode.SMALLINT_UNSIGNED) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['SMALLINT UNSIGNED'], TDengineTypeCode.SMALLINT_UNSIGNED) + } setInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['INT'], TDengineTypeCode.INT) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['INT'], TDengineTypeCode.INT) + } setUInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['INT UNSIGNED'], TDengineTypeCode.INT_UNSIGNED) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['INT UNSIGNED'], TDengineTypeCode.INT_UNSIGNED) + } setBigint(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBigIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "bigint", TDengineTypeLength['BIGINT'], TDengineTypeCode.BIGINT) - this._params.push(columnInfo); + this.addParams(params, "bigint", TDengineTypeLength['BIGINT'], TDengineTypeCode.BIGINT) + } setUBigint(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUBigIntColumn params is invalid!"); } - let columnInfo = this.encode(params, "bigint", TDengineTypeLength['BIGINT UNSIGNED'], TDengineTypeCode.BIGINT_UNSIGNED) - this._params.push(columnInfo); + this.addParams(params, "bigint", TDengineTypeLength['BIGINT UNSIGNED'], TDengineTypeCode.BIGINT_UNSIGNED) + } setFloat(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetFloatColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['FLOAT'], TDengineTypeCode.FLOAT) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['FLOAT'], TDengineTypeCode.FLOAT) + } setDouble(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetDoubleColumn params is invalid!"); } - let columnInfo = this.encode(params, "number", TDengineTypeLength['DOUBLE'], TDengineTypeCode.DOUBLE) - this._params.push(columnInfo); + this.addParams(params, "number", TDengineTypeLength['DOUBLE'], TDengineTypeCode.DOUBLE) + } setVarchar(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetVarcharColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[8], 0, TDengineTypeCode.VARCHAR); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[8], 0, TDengineTypeCode.VARCHAR); + } setBinary(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[8], 0, TDengineTypeCode.BINARY); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[8], 0, TDengineTypeCode.BINARY); + } setNchar(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetNcharColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[10], 0, TDengineTypeCode.NCHAR); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[10], 0, TDengineTypeCode.NCHAR); } setJson(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetJsonColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[15], 0, TDengineTypeCode.JSON); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[15], 0, TDengineTypeCode.JSON); + } setVarBinary(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetVarBinaryColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[16], 0, TDengineTypeCode.VARBINARY); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[16], 0, TDengineTypeCode.VARBINARY); } setGeometry(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetGeometryColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[16], 0, TDengineTypeCode.GEOMETRY); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[20], 0, TDengineTypeCode.GEOMETRY); + } setTimestamp(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid!"); } - let columnInfo = this.encode(params, TDengineTypeName[9], TDengineTypeLength['TIMESTAMP'], TDengineTypeCode.TIMESTAMP); - this._params.push(columnInfo); + this.addParams(params, TDengineTypeName[9], TDengineTypeLength['TIMESTAMP'], TDengineTypeCode.TIMESTAMP); + } protected writeDataToBuffer(dataBuffer: DataView, params: any, dataType: string = 'number', typeLen: number, columnType: number, i:number): void { @@ -221,7 +228,8 @@ export abstract class StmtBindParams { break; } - case TDengineTypeCode.BIGINT: { + case TDengineTypeCode.BIGINT: + case TDengineTypeCode.TIMESTAMP: { dataBuffer.setBigInt64(i * 8, params, true); break; } @@ -245,7 +253,7 @@ export abstract class StmtBindParams { } } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid! param:=" + params[i]) + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid! param:=" + params) } } diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 94b56dff..9c2d9781 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -1,399 +1,448 @@ -// import JSONBig from 'json-bigint'; -// import { WsClient } from "../client/wsClient"; -// import { ColumnsBlockType, FieldBindType, PrecisionLength } from "../common/constant"; -// import logger from "../common/log"; -// import { ReqId } from "../common/reqid"; -// import { ErrorCode, TaosResultError, TDWebSocketClientError } from "../common/wsError"; -// import { ColumnInfo, StmtBindParams, TableInfo } from "./wsParamsBase"; -// import { StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; -// import { WsStmt } from "./wsStmt"; -// import { _isVarType } from '../common/taosResult'; -// import { bigintToBytes, intToBytes, shotToBytes } from '../common/utils'; - - - -// export class WsStmt2 implements WsStmt{ -// private _wsClient: WsClient; -// private _stmt_id: bigint | undefined | null; -// private _precision:number = PrecisionLength['ms']; -// private fields?: Array | undefined | null; -// private lastAffected: number | undefined | null; -// private _stmtTableInfo: Map; -// private _currentTableInfo: TableInfo; -// private _stmtTableInfoList: TableInfo[]; -// private _toBeBindTagCount: number; -// private _toBeBindColCount: number; -// private _toBeBindTableNameIndex: number | undefined | null; - -// private constructor(wsClient: WsClient, precision?:number) { -// this._wsClient = wsClient; -// if (precision) { -// this._precision = precision; -// } -// this._stmtTableInfo = new Map(); -// this._currentTableInfo = new TableInfo(); -// this._stmtTableInfoList = []; -// this._toBeBindColCount = 0; -// this._toBeBindTagCount = 0; -// } +import JSONBig from 'json-bigint'; +import { WsClient } from "../client/wsClient"; +import { ColumnsBlockType, FieldBindType, PrecisionLength } from "../common/constant"; +import logger from "../common/log"; +import { ReqId } from "../common/reqid"; +import { ErrorCode, TaosResultError, TDWebSocketClientError } from "../common/wsError"; +import { StmtBindParams } from "./wsParamsBase"; +import { StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; +import { WsStmt } from "./wsStmt"; +import { _isVarType } from '../common/taosResult'; +import { bigintToBytes, intToBytes, shotToBytes } from '../common/utils'; +import { Stmt2BindParams } from './wsParams2'; +import { TableInfo } from './wsTableInfo'; +import { ColumnInfo } from './wsColumnInfo'; +import { WSRows } from '../sql/wsRows'; + + + +export class WsStmt2 implements WsStmt{ + private _wsClient: WsClient; + private _stmt_id: bigint | undefined | null; + private _query_id: bigint | undefined | null; + private _precision:number = PrecisionLength['ms']; + private fields?: Array | undefined | null; + private lastAffected: number | undefined | null; + private _stmtTableInfo: Map; + private _currentTableInfo: TableInfo; + private _stmtTableInfoList: TableInfo[]; + private _toBeBindTagCount: number; + private _toBeBindColCount: number; + private _toBeBindTableNameIndex: number | undefined | null; + + private constructor(wsClient: WsClient, precision?:number) { + this._wsClient = wsClient; + if (precision) { + this._precision = precision; + } + this._stmtTableInfo = new Map(); + this._currentTableInfo = new TableInfo(); + this._stmtTableInfoList = []; + this._toBeBindColCount = 0; + this._toBeBindTagCount = 0; + } -// static async newStmt(wsClient: WsClient, precision?:number, reqId?:number): Promise { -// try { -// let wsStmt = new WsStmt2(wsClient, precision) -// return await wsStmt.init(reqId); -// } catch(e: any) { -// logger.error(`WsStmt init is failed, ${e.code}, ${e.message}`); -// throw(e); -// } - -// } - -// private async init(reqId: number | undefined): Promise { -// if (this._wsClient) { -// try { -// if (this._wsClient.getState() <= 0) { -// await this._wsClient.connect(); -// await this._wsClient.checkVersion(); -// } -// let queryMsg = { -// action: 'stmt2_init', -// args: { -// req_id: ReqId.getReqID(reqId), -// single_stb_insert: true, -// single_table_bind_once: true -// }, -// }; -// await this.execute(queryMsg); -// return this; -// } catch (e: any) { -// logger.error(`stmt init filed, ${e.code}, ${e.message}`); -// throw(e); -// } + static async newStmt(wsClient: WsClient, precision?:number, reqId?:number): Promise { + try { + let wsStmt = new WsStmt2(wsClient, precision) + return await wsStmt.init(reqId); + } catch(e: any) { + logger.error(`WsStmt init is failed, ${e.code}, ${e.message}`); + throw(e); + } + + } + + private async init(reqId: number | undefined): Promise { + if (this._wsClient) { + try { + if (this._wsClient.getState() <= 0) { + await this._wsClient.connect(); + await this._wsClient.checkVersion(); + } + let queryMsg = { + action: 'stmt2_init', + args: { + req_id: ReqId.getReqID(reqId), + single_stb_insert: true, + single_table_bind_once: true + }, + }; + await this.execute(queryMsg); + return this; + } catch (e: any) { + logger.error(`stmt init filed, ${e.code}, ${e.message}`); + throw(e); + } -// } -// throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); -// } - -// async prepare(sql: string): Promise { -// let queryMsg = { -// action: 'stmt2_prepare', -// args: { -// req_id: ReqId.getReqID(), -// sql: sql, -// stmt_id: this._stmt_id, -// get_fields: true, -// }, -// }; -// await this.execute(queryMsg); -// if (this.fields && this.fields[0].precision) { -// this._precision = this.fields[0].precision; -// } - -// this._toBeBindColCount = 0; -// this._toBeBindTagCount = 0; -// this.fields?.forEach((field, index) => { -// if (field.bind_type == FieldBindType.TAOS_FIELD_TBNAME) { -// this._toBeBindTableNameIndex = index; -// } else if (field.bind_type == FieldBindType.TAOS_FIELD_TAG) { -// this._toBeBindTagCount++; -// } else if (field.bind_type == FieldBindType.TAOS_FIELD_COL) { -// this._toBeBindColCount++; -// } -// }); - -// } - -// async setTableName(tableName: string): Promise { -// if (!tableName || tableName.length === 0) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, 'Table name cannot be empty'); -// } -// let tableInfo = this._stmtTableInfo.get(tableName); -// if (!tableInfo) { -// this._currentTableInfo = new TableInfo(tableName); -// this._stmtTableInfo.set(tableName, this._currentTableInfo); -// this._stmtTableInfoList.push(this._currentTableInfo); -// console.log(`New table info created for table: ${this._currentTableInfo.getTableName()}`); -// } else { -// this._currentTableInfo = tableInfo; -// } -// return Promise.resolve(); -// } - -// async setTags(paramsArray: StmtBindParams): Promise { -// if (!paramsArray || !this._stmt_id) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!"); -// } -// for (let i = 0; i < paramsArray.getParams().length; i++) { -// let param = paramsArray.getParams()[i]; -// console.log(`Tag param[${i}]: ${JSONBig.stringify(param.data)}`); -// } -// if (!this._currentTableInfo) { -// this._currentTableInfo = new TableInfo(); -// this._stmtTableInfoList.push(this._currentTableInfo); -// } -// await this._currentTableInfo.setTags(paramsArray); -// console.log(`Set tags for table: ${this._currentTableInfo.getTableName()}, tags: ${JSONBig.stringify(paramsArray.getParams())}`); -// return Promise.resolve(); -// } - -// newStmtParam(): StmtBindParams { -// return new StmtBindParams(this._precision); -// } - -// async bind(paramsArray: StmtBindParams): Promise { -// if (!paramsArray || !this._stmt_id) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind paramArray is invalid!"); -// } -// if (!this._currentTableInfo) { -// this._currentTableInfo = new TableInfo(); -// this._stmtTableInfoList.push(this._currentTableInfo); -// } -// await this._currentTableInfo.setParams(paramsArray); -// return Promise.resolve(); -// } - -// async batch(): Promise { -// Promise.resolve(); -// } - -// async exec(): Promise { -// if (!this._currentTableInfo || this._currentTableInfo.getParams.length === 0) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); -// } -// let reqId = BigInt(ReqId.getReqID()); -// for (let tableInfo of this._stmtTableInfoList) { -// console.log(`tableInfo: ${tableInfo.getParams()}, tags: ${tableInfo.getTags()}`); -// } - -// } -// getLastAffected(): number | null | undefined { -// throw new Error("Method not implemented."); -// } -// async close(): Promise { -// throw new Error("Method not implemented."); -// } - -// private async execute(queryMsg: StmtMessageInfo, register: Boolean = true): Promise { -// try { -// if (this._wsClient.getState() <= 0) { -// throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); -// } -// let reqMsg = JSONBig.stringify(queryMsg); -// if (register) { -// let result = await this._wsClient.exec(reqMsg, false); -// let resp = new WsStmtQueryResponse(result) -// if (resp.stmt_id) { -// this._stmt_id = resp.stmt_id; -// } - -// if (resp.affected) { -// this.lastAffected = resp.affected -// } - -// if (resp.fields) { -// this.fields = resp.fields; -// console.log(`Fields: ${JSONBig.stringify(resp.fields)}`); -// } -// }else{ -// await this._wsClient.execNoResp(reqMsg); -// this._stmt_id = null -// this.lastAffected = null -// } -// return -// } catch (e:any) { -// throw new TaosResultError(e.code, e.message); -// } -// } - -// private binaryBlockEncode() { -// // cloc totol size -// let totalTableNameSize = 0; -// let tableNameSizeList:Array = []; -// if (this._toBeBindTableNameIndex && this._toBeBindTableNameIndex > 0) { -// this._stmtTableInfo.forEach((tableInfo) => { -// let tableName = tableInfo.getTableName(); -// if (tableName) { -// let size = new TextEncoder().encode(tableName).length; -// totalTableNameSize += size; -// tableNameSizeList.push(size); -// } else { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); -// } -// }); -// } - -// let totalTagSize = 0; -// let tagSizeList:Array = []; -// if (this._toBeBindColCount > 0) { -// this._stmtTableInfoList.forEach((tableInfo) => { -// let params = tableInfo.getTags(); -// if (params) { -// let tagSize = params.getDataTotalLen(); -// totalTagSize += tagSize; -// tagSizeList.push(tagSize); -// } -// }); -// } -// let totalColSize = 0; -// let colSizeList:Array = []; -// if (this._toBeBindColCount > 0) { -// this._stmtTableInfoList.forEach((tableInfo) => { -// let params = tableInfo.getParams(); -// if (!params || params.length === 0) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); -// } - -// params.forEach((param) => { -// let colSize = param.getDataTotalLen(); -// totalColSize += colSize; -// colSizeList.push(colSize); -// }); - -// }); -// } - - -// let totalSize = totalTableNameSize + totalTagSize + totalColSize; -// let toBeBindTableNameCount = (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex >= 0) ? 1 : 0; -// totalSize += this._stmtTableInfoList.length * ( -// toBeBindTableNameCount * 2 -// + (this._toBeBindTagCount > 0 ? 1 : 0) * 4 -// + (this._toBeBindColCount > 0 ? 1 : 0) * 4); + } + throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); + } + + async prepare(sql: string): Promise { + let queryMsg = { + action: 'stmt2_prepare', + args: { + req_id: ReqId.getReqID(), + sql: sql, + stmt_id: this._stmt_id, + get_fields: true, + }, + }; + await this.execute(queryMsg); + if (this.fields && this.fields[0].precision) { + this._precision = this.fields[0].precision; + } + + this._toBeBindColCount = 0; + this._toBeBindTagCount = 0; + this.fields?.forEach((field, index) => { + if (field.bind_type == FieldBindType.TAOS_FIELD_TBNAME) { + this._toBeBindTableNameIndex = index; + } else if (field.bind_type == FieldBindType.TAOS_FIELD_TAG) { + this._toBeBindTagCount++; + } else if (field.bind_type == FieldBindType.TAOS_FIELD_COL) { + this._toBeBindColCount++; + } + }); + + } + + async setTableName(tableName: string): Promise { + if (!tableName || tableName.length === 0) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, 'Table name cannot be empty'); + } + let tableInfo = this._stmtTableInfo.get(tableName); + if (!tableInfo) { + this._currentTableInfo = new TableInfo(tableName); + this._stmtTableInfo.set(tableName, this._currentTableInfo); + this._stmtTableInfoList.push(this._currentTableInfo); + } else { + this._currentTableInfo = tableInfo; + } + return Promise.resolve(); + } + + async setTags(paramsArray: StmtBindParams): Promise { + if (!paramsArray || !this._stmt_id) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!"); + } + for (let i = 0; i < paramsArray.getParams().length; i++) { + let param = paramsArray.getParams()[i]; + } + + if (!this._currentTableInfo) { + this._currentTableInfo = new TableInfo(); + this._stmtTableInfoList.push(this._currentTableInfo); + } + await this._currentTableInfo.setTags(paramsArray); + return Promise.resolve(); + } + + newStmtParam(): StmtBindParams { + if (!this.fields || this.fields.length === 0) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "No columns to bind!"); + } + console.log(`Creating new Stmt2BindParams with precision: ${this._precision}, field count: ${this.fields.length}`); + return new Stmt2BindParams(this.fields.length, this._precision); + } + + async bind(paramsArray: StmtBindParams): Promise { + if (!paramsArray || !this._stmt_id) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind paramArray is invalid!"); + } + if (!this._currentTableInfo) { + this._currentTableInfo = new TableInfo(); + this._stmtTableInfoList.push(this._currentTableInfo); + console.log(`New table info created for table: ${this._currentTableInfo.getTableName()}`); + } + + await this._currentTableInfo.setParams(paramsArray); + return Promise.resolve(); + } + + async batch(): Promise { + Promise.resolve(); + } + + async exec(): Promise { + if (!this._currentTableInfo ) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "table info is empty!"); + } + + let params = this._currentTableInfo.getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + + let reqId = BigInt(ReqId.getReqID()); + let bytes = this.binaryBlockEncode(reqId) + await this.sendBinaryMsg(reqId, 'stmt2_bind', new Uint8Array(bytes)); + + let execMsg = { + action: 'stmt2_exec', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + await this.execute(execMsg); + } + + getLastAffected(): number | null | undefined { + return this.lastAffected; + } + + async resultSet(): Promise { + let execMsg = { + action: 'stmt2_result', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + let resp = await this.execute(execMsg); + if (!resp) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "ResultSet response is empty!"); + } + return new WSRows(this._wsClient, resp); + + } + + async close(): Promise { + let queryMsg = { + action: 'stmt2_close', + args: { + req_id: ReqId.getReqID(), + stmt_id: this._stmt_id, + }, + }; + await this.execute(queryMsg); + } + + private async execute(stmtMsg: StmtMessageInfo|ArrayBuffer, register: Boolean = true): Promise { + try { + if (this._wsClient.getState() <= 0) { + throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); + } + + let reqMsg = JSONBig.stringify(stmtMsg); + + if (register) { + let result = await this._wsClient.exec(reqMsg, false); + let resp = new WsStmtQueryResponse(result) + if (resp.stmt_id) { + this._stmt_id = resp.stmt_id; + } + + if (resp.affected) { + this.lastAffected = resp.affected + } + + if (resp.fields) { + this.fields = resp.fields; + } + return resp; + } + + await this._wsClient.execNoResp(reqMsg); + this._stmt_id = null + this.lastAffected = null + } catch (e:any) { + throw new TaosResultError(e.code, e.message); + } + } + + private async sendBinaryMsg(reqId: bigint, action:string, message: ArrayBuffer): Promise { + if (this._wsClient.getState() <= 0) { + throw new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "websocket connect has closed!"); + } + let result = await this._wsClient.sendBinaryMsg(reqId, action, message, false); + let resp = new WsStmtQueryResponse(result) + if (resp.stmt_id) { + this._stmt_id = resp.stmt_id; + } + + if (resp.affected) { + this.lastAffected = resp.affected + } + } + + private binaryBlockEncode(reqId: bigint):number[] { + // cloc totol size + let totalTableNameSize = 0; + let tableNameSizeList:Array = []; + if (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex != undefined) { + this._stmtTableInfo.forEach((tableInfo) => { + let tableName = tableInfo.getTableName(); + if (tableName) { + let size = new TextEncoder().encode(tableName).length; + totalTableNameSize += size + 1; + tableNameSizeList.push(size + 1); + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + }); + } + + let totalTagSize = 0; + let tagSizeList:Array = []; + if (this._toBeBindTagCount > 0) { + this._stmtTableInfoList.forEach((tableInfo) => { + let params = tableInfo.getTags(); + if (params) { + params.encode(); + let tagSize = params.getDataTotalLen(); + totalTagSize += tagSize; + tagSizeList.push(tagSize); + } + }); + } + + let totalColSize = 0; + let colSizeList:Array = []; + if (this._toBeBindColCount > 0) { + this._stmtTableInfoList.forEach((tableInfo) => { + let params = tableInfo.getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + params.encode(); + let colSize = params.getDataTotalLen(); + totalColSize += colSize; + colSizeList.push(colSize); + + }); + } + + let totalSize = totalTableNameSize + totalTagSize + totalColSize; + let toBeBindTableNameCount = (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex >= 0) ? 1 : 0; + totalSize += this._stmtTableInfoList.length * ( + toBeBindTableNameCount * 2 + + (this._toBeBindTagCount > 0 ? 1 : 0) * 4 + + (this._toBeBindColCount > 0 ? 1 : 0) * 4); + + const bytes: number[] = []; + // 写入 req_id + bytes.push(...bigintToBytes(reqId)); + + // 写入 stmt_id + if (this._stmt_id) { + bytes.push(...bigintToBytes(this._stmt_id)); + } + + bytes.push(...bigintToBytes(9n)); + bytes.push(...shotToBytes(1)); + bytes.push(...intToBytes(-1)); + bytes.push(...intToBytes(totalSize + 28, false)); + + bytes.push(...intToBytes(this._stmtTableInfoList.length)); + bytes.push(...intToBytes(this._toBeBindTagCount)); + bytes.push(...intToBytes(this._toBeBindColCount)); + if (toBeBindTableNameCount > 0) { + bytes.push(...intToBytes(0x1C)); + } else { + bytes.push(...intToBytes(0)); + } + + if (this._toBeBindTagCount) { + if (toBeBindTableNameCount > 0) { + bytes.push(...intToBytes(28 + totalTableNameSize + 2 * this._stmtTableInfoList.length)); + } else { + bytes.push(...intToBytes(28)); + } + } else { + bytes.push(...intToBytes(0)); + } + + if (this._toBeBindColCount > 0) { + let skipSize = 0; + if (toBeBindTableNameCount > 0) { + skipSize += totalTableNameSize + 2 * this._stmtTableInfoList.length; + } + + if (this._toBeBindTagCount > 0) { + skipSize += totalTagSize + 4 * this._stmtTableInfoList.length; + } + + // colOffset = 28(固定头) + skipSize + bytes.push(...intToBytes(28 + skipSize)); + } else { + bytes.push(...intToBytes(0)); + } + + + if (toBeBindTableNameCount > 0) { + for (let size of tableNameSizeList) { + if (size === 0) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + bytes.push(...shotToBytes(size)); + } + + for (let tableInfo of this._stmtTableInfoList) { + let tableName = tableInfo.getTableName(); + if (tableName && tableName.length > 0) { + let encoder = new TextEncoder().encode(tableName); + bytes.push(...encoder); + bytes.push(0); // null terminator + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + } + } -// const bytes: number[] = []; - - - -// // 写入 req_id -// bytes.push(...bigintToBytes(BigInt(ReqId.getReqID()))); - -// // 写入 stmt_id -// if (this._stmt_id) { -// bytes.push(...bigintToBytes(this._stmt_id)); -// } - -// bytes.push(...bigintToBytes(9n)); -// bytes.push(...shotToBytes(1)); -// bytes.push(...intToBytes(-1)); -// bytes.push(...intToBytes(totalSize + 28, false)); -// bytes.push(...intToBytes(this._stmtTableInfoList.length)); -// bytes.push(...intToBytes(this._toBeBindTagCount)); -// bytes.push(...intToBytes(this._toBeBindColCount)); -// if (toBeBindTableNameCount > 0) { -// bytes.push(...intToBytes(0x1C)); -// } else { -// bytes.push(...intToBytes(0)); -// } - -// if (this._toBeBindTagCount) { -// if (toBeBindTableNameCount > 0) { -// bytes.push(...intToBytes(28 + totalTableNameSize * 2 * this._stmtTableInfoList.length)); -// } else { -// bytes.push(...intToBytes(28)); -// } -// } else { -// bytes.push(...intToBytes(0)); -// } - -// if (toBeBindTableNameCount > 0) { -// for (let size of tableNameSizeList) { -// if (size === 0) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); -// } -// bytes.push(...shotToBytes(size)); -// } -// for (let tableInfo of this._stmtTableInfoList) { -// let tableName = tableInfo.getTableName(); -// if (tableName && tableName.length > 0) { -// let encoder = new TextEncoder().encode(tableName); -// bytes.push(...encoder); -// bytes.push(0); // null terminator -// } else { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); -// } -// } -// } - -// if (this._toBeBindTagCount > 0) { -// for (let size of tagSizeList) { -// bytes.push(...shotToBytes(size)); -// } - -// for (let tableInfo of this._stmtTableInfoList) { -// let tags = tableInfo.getTags(); -// if (tags && tags.getParams().length > 0) { -// for (let tagColumnInfo of tags.getParams()) { -// let isVarType = _isVarType(tagColumnInfo.type) -// if (isVarType == ColumnsBlockType.SOLID) { -// if (tagColumnInfo.data === null || tagColumnInfo.data === undefined) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tag value is null"); -// } - -// bytes.push(...tag.getData()); -// } -// } -// } else { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); -// } -// } -// } -// // // TagsDataLength -// // if (toBebindTagCount > 0) { -// // for (Integer tagsize : tagSizeList) { -// // buf.writeIntLE(tagsize); -// // } - -// // for (TableInfo tableInfo : tableInfoMap.values()) { -// // for (ColumnInfo tag : tableInfo.getTagInfo()) { -// // if (tag.getDataList().isEmpty()) { -// // throw TSDBError.createSQLException(TSDBErrorNumbers.ERROR_INVALID_VARIABLE, "tag value is null, tbname: " + tableInfo.getTableName().toString()); -// // } -// // serializeColumn(tag, buf, precision); -// // } -// // } -// // } - -// } + if (this._toBeBindTagCount > 0) { + for (let size of tagSizeList) { + bytes.push(...intToBytes(size)); + } + for (let tableInfo of this._stmtTableInfoList) { + let tags = tableInfo.getTags(); + if (tags && tags.getParams().length > 0) { + for (let tagColumnInfo of tags.getParams()) { + this.serializeColumn(tagColumnInfo, bytes); + } + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); + } + } + } + + // TagsDataLength + if (this._toBeBindColCount > 0) { + for (let colSize of colSizeList) { + bytes.push(...intToBytes(colSize, false)); + } + for (let tableInfo of this._stmtTableInfoList) { + let params = tableInfo.getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + + let colColumnInfos:ColumnInfo[] = params.getParams() + colColumnInfos.forEach(colColumnInfo => { + this.serializeColumn(colColumnInfo, bytes); + }); + } + } + return bytes + + } + + private serializeColumn(column: ColumnInfo, bytes: number[]) { + bytes.push(...intToBytes(column.length, false)); + bytes.push(...intToBytes(column.type)); + bytes.push(...intToBytes(column._rows)); + bytes.push(...column.isNull ? column.isNull : []); + bytes.push(column._haveLength); -// private serializeColumn(column: ColumnInfo, bytes: number[], precision: number) { -// let isVarType = _isVarType(column.type) -// if (isVarType == ColumnsBlockType.SOLID) { -// if (column.data === null || column.data === undefined) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); -// } -// // TotalLength(4) + Type (4) + Num(4) + IsNull(1) * size + haveLength(1) + BufferLength(4) + size * dataLen -// let dataLen = 17 + (dataLen + 1) * -// bytes.push(...column.getData()); -// } else if (isVarType == ColumnsBlockType.VAR) { -// let dataList = column.getDataList(); -// if (!dataList || dataList.length === 0) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); -// } -// dataList.forEach((data) => { -// if (data === null || data === undefined) { -// bytes.push(...intToBytes(-1)); -// } else { -// let encoder = new TextEncoder().encode(data); -// bytes.push(...intToBytes(encoder.length)); -// bytes.push(...encoder); -// } -// }); -// } else if (isVarType == ColumnsBlockType.TIME) { -// if (column.data === null || column.data === undefined) { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind value is null"); -// } -// let timeValue = BigInt(column.data); -// if (precision < 9) { -// let factor = BigInt(10 ** (9 - precision)); -// timeValue = timeValue * factor; -// } else if (precision > 9) { -// let factor = BigInt(10 ** (precision - 9)); -// timeValue = timeValue / factor; -// } -// bytes.push(...bigintToBytes(timeValue)); -// } else { -// throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, `Unsupported column type: ${column.type}`); -// } -// } -// } \ No newline at end of file + if (column._haveLength == 1 && column._dataLengths) { + column._dataLengths.forEach(length => { + bytes.push(...intToBytes(length)); + }); + } + bytes.push(...intToBytes(column.data.byteLength, false)); + bytes.push(...column.data.byteLength ? new Uint8Array(column.data) : []); + } +} \ No newline at end of file diff --git a/nodejs/src/stmt/wsTableInfo.ts b/nodejs/src/stmt/wsTableInfo.ts index 8fc48606..04207a8f 100644 --- a/nodejs/src/stmt/wsTableInfo.ts +++ b/nodejs/src/stmt/wsTableInfo.ts @@ -1,14 +1,14 @@ import { ErrorCode, TaosError } from "../common/wsError"; import { StmtBindParams } from "./wsParamsBase"; +import JSONBig from 'json-bigint'; export class TableInfo { name: string | undefined | null; tags: StmtBindParams | undefined | null; - params: Array; + params?: StmtBindParams; constructor(name?: string) { this.name = name; - this.params = []; } public getTableName(): string | undefined | null { @@ -19,7 +19,7 @@ export class TableInfo { return this.tags; } - public getParams(): Array | undefined | null { + public getParams(): StmtBindParams | undefined | null { return this.params; } @@ -37,11 +37,14 @@ export class TableInfo { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table tags is invalid!"); } } - async setParams(params: StmtBindParams): Promise { - if (params) { - this.params.push(params); + async setParams(bindParams: StmtBindParams): Promise { + if (!this.params) { + this.params = bindParams; } else { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table params is invalid!"); + if (bindParams._fieldParams) { + this.params.mergeParams(bindParams); + console.log(`setParams params merged, total rows: ${bindParams._fieldParams[0].params.length}`); + } } } } \ No newline at end of file diff --git a/nodejs/test/bulkPulling/sql.test.ts b/nodejs/test/bulkPulling/sql.test.ts index d667d7ed..230b0bd9 100644 --- a/nodejs/test/bulkPulling/sql.test.ts +++ b/nodejs/test/bulkPulling/sql.test.ts @@ -4,7 +4,7 @@ import { WsSql } from "../../src/sql/wsSql"; import { Sleep } from "../utils"; import logger, { setLevel } from "../../src/common/log" -let dns = 'ws://localhost:6041' +let dns = 'ws://192.168.2.156:6041' let password1 = 'Ab1!@#$%,.:?<>;~' let password2 = 'Bc%^&*()-_+=[]{}' setLevel("debug") @@ -90,7 +90,7 @@ describe('TDWebSocket.WsSql()', () => { }) test('connect url', async() => { - let url = 'ws://root:taosdata@localhost:6041/information_schema?timezone=Asia/Shanghai' + let url = 'ws://root:taosdata@192.1682.156:6041/information_schema?timezone=Asia/Shanghai' let conf :WSConfig = new WSConfig(url) let wsSql = await WsSql.open(conf) let version = await wsSql.version() diff --git a/nodejs/test/bulkPulling/stmt.func.test.ts b/nodejs/test/bulkPulling/stmt.func.test.ts index 5da64f4e..34ba8686 100644 --- a/nodejs/test/bulkPulling/stmt.func.test.ts +++ b/nodejs/test/bulkPulling/stmt.func.test.ts @@ -117,7 +117,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.setTableName('d1001'); }catch(e) { let err:any = e - expect(err.message).toMatch("syntax error near '? into ? using meters tags (?, ?) values (?, ?, ?, ?)' (keyword INTO is expected)") + expect(err.message).toMatch(/keyword INTO is expected|Syntax error in SQL/) } await stmt.close() await connector.close(); @@ -151,16 +151,15 @@ describe('TDWebSocket.Stmt()', () => { let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() - await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); - await stmt.setTableName('d1001'); + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('power_stmt.d1001'); let params = stmt.newStmtParam() params.setVarchar(['SanFrancisco']); - params.setInt([7]); + params.setInt([1]); await stmt.setTags(params) let lastTs = 0 - const allp:any[] = [] for (let i = 0; i < 10; i++) { for (let j = 0; j < multi[0].length; j++) { multi[0][j] = multi[0][0] + j; @@ -172,11 +171,15 @@ describe('TDWebSocket.Stmt()', () => { dataParams.setFloat(multi[1]) dataParams.setInt(multi[2]) dataParams.setFloat(multi[3]) - allp.push(stmt.bind(dataParams)) - multi[0][0] = lastTs + 1 + if (dataParams._fieldParams) { + console.log(`bind ${dataParams._fieldParams[0].params.length} rows data ${multi[0].length}`) + } + stmt.bind(dataParams) + + multi[0][0] = lastTs + 1; } - await Promise.all(allp) + await stmt.batch() await stmt.exec() expect(stmt.getLastAffected()).toEqual(30) @@ -217,7 +220,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.exec() }catch(e) { let err:any = e - expect(err.message).toMatch("wrong row length") + expect(err.message).toMatch(/wrong row length|bind data length error/) } await stmt.close() await connector.close(); @@ -338,7 +341,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.exec() }catch(e) { let err:any = e - expect(err.message).toMatch("Retry needed"); + expect(err.message).toMatch(/Retry needed|Tags are empty/); } await stmt.close() await connector.close(); diff --git a/nodejs/test/bulkPulling/stmt.type.test.ts b/nodejs/test/bulkPulling/stmt.type.test.ts index 10381d3b..88fdb7a4 100644 --- a/nodejs/test/bulkPulling/stmt.type.test.ts +++ b/nodejs/test/bulkPulling/stmt.type.test.ts @@ -1,5 +1,6 @@ import { WebSocketConnectionPool } from "../../src/client/wsConnectorPool"; import { WSConfig } from "../../src/common/config"; +import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { createBaseSTable, createBaseSTableJSON, createSTableJSON, getInsertBind } from "../utils"; @@ -83,6 +84,7 @@ const selectJsonTable = `select * from ${jsonTable}` const selectJsonTableCN = `select * from ${jsonTableCN}` let dsn = 'ws://root:taosdata@192.168.2.156:6041'; +setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dsn) let ws = await WsSql.open(conf); @@ -347,9 +349,9 @@ test('test bind exception cases', async() => { afterAll(async () => { - let conf :WSConfig = new WSConfig(dsn) - let ws = await WsSql.open(conf); - await ws.exec(dropDB); - await ws.close(); + // let conf :WSConfig = new WSConfig(dsn) + // let ws = await WsSql.open(conf); + // await ws.exec(dropDB); + // await ws.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file diff --git a/nodejs/test/utils.ts b/nodejs/test/utils.ts index b32146f7..1d61cfce 100644 --- a/nodejs/test/utils.ts +++ b/nodejs/test/utils.ts @@ -15,6 +15,7 @@ export function getInsertBind(valuesLen: number, tagsLen: number, db: string, st sql += ', ?' } sql += ')' + logger.debug(`Insert bind sql: ${sql}`); return sql; } From a1132b220efc95dbd968d6453fb4fc542795d2fb Mon Sep 17 00:00:00 2001 From: menshibin Date: Thu, 11 Sep 2025 20:05:14 +0800 Subject: [PATCH 04/23] feat: support binding super tables --- nodejs/src/stmt/FieldBindParams.ts | 4 +- nodejs/src/stmt/wsParams2.ts | 37 ++++--- nodejs/src/stmt/wsParamsBase.ts | 21 +++- nodejs/src/stmt/wsStmt.ts | 3 + nodejs/src/stmt/wsStmt1.ts | 5 + nodejs/src/stmt/wsStmt2.ts | 112 +++++++++++++-------- nodejs/src/stmt/wsTableInfo.ts | 1 - nodejs/test/bulkPulling/stmt.func.test.ts | 116 ++++++++++++++++++++-- 8 files changed, 233 insertions(+), 66 deletions(-) diff --git a/nodejs/src/stmt/FieldBindParams.ts b/nodejs/src/stmt/FieldBindParams.ts index d085c73c..d1ade040 100644 --- a/nodejs/src/stmt/FieldBindParams.ts +++ b/nodejs/src/stmt/FieldBindParams.ts @@ -4,10 +4,12 @@ export class FieldBindParams { dataType: string; typeLen: number; columnType: number; - constructor(params:any[], dataType:string, typeLen:number, columnType:number) { + bindType: number; // 0: normal, 1: table name, 2: tag + constructor(params:any[], dataType:string, typeLen:number, columnType:number, bindType:number) { this.params = [...params]; this.dataType = dataType; this.typeLen = typeLen; this.columnType = columnType; + this.bindType = bindType; } } \ No newline at end of file diff --git a/nodejs/src/stmt/wsParams2.ts b/nodejs/src/stmt/wsParams2.ts index d678a543..c2add6e9 100644 --- a/nodejs/src/stmt/wsParams2.ts +++ b/nodejs/src/stmt/wsParams2.ts @@ -1,4 +1,4 @@ -import { ColumnsBlockType, PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; +import { ColumnsBlockType, FieldBindType, PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; import { ErrorCode, TaosError } from "../common/wsError"; import { isEmpty } from "../common/utils"; import { ColumnInfo } from "./wsColumnInfo"; @@ -6,32 +6,42 @@ import { IDataEncoder, StmtBindParams } from "./wsParamsBase"; import { _isVarType } from "../common/taosResult"; import { FieldBindParams } from "./FieldBindParams"; import JSONBig from 'json-bigint'; +import { StmtFieldInfo } from "./wsProto"; export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { + private _fields: Array; protected paramIndex:number = 0; - constructor(paramsCount: number, precision?:number) { + constructor(paramsCount?: number, precision?:number, fields?: Array) { super(precision, paramsCount); + this._fields = fields || []; } addParams(params: any[], dataType: string, typeLen: number, columnType: number): void { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); } - if (this._fieldParams){ - if (this._fieldParams[this.paramIndex]) { - if (this._fieldParams[this.paramIndex].dataType !== dataType || this._fieldParams[this.paramIndex].columnType !== columnType) { - throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, `StmtBindParams params type is not match! ${this.paramIndex} ${this.paramsCount} ${JSONBig.stringify({ dataType, columnType } )} vs ${JSONBig.stringify({ dataType: this._fieldParams[this.paramIndex].dataType, columnType: this._fieldParams[this.paramIndex].columnType})}`); + if (this._fieldParams) { + if (this.paramsCount > 0) { + if (this._fieldParams[this.paramIndex]) { + if (this._fieldParams[this.paramIndex].dataType !== dataType || this._fieldParams[this.paramIndex].columnType !== columnType) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, `StmtBindParams params type is not match! ${this.paramIndex} ${this.paramsCount} ${JSONBig.stringify({ dataType, columnType } )} vs ${JSONBig.stringify({ dataType: this._fieldParams[this.paramIndex].dataType, columnType: this._fieldParams[this.paramIndex].columnType})}`); + } + this._fieldParams[this.paramIndex].params.push(...params); + + } else { + let bindType = this._fields[this.paramIndex].bind_type || 0; + this._fieldParams[this.paramIndex] = new FieldBindParams(params, dataType, typeLen, columnType, bindType); + this._bindCount++ } - this._fieldParams[this.paramIndex].params.push(...params); - + this.paramIndex++; + if (this.paramIndex >= this.paramsCount) { + this.paramIndex = 0; + } } else { - this._fieldParams[this.paramIndex] = new FieldBindParams(params, dataType, typeLen, columnType); + this._fieldParams.push(new FieldBindParams(params, dataType, typeLen, columnType, FieldBindType.TAOS_FIELD_COL)); } } - this.paramIndex++; - if (this.paramIndex >= this.paramsCount) { - this.paramIndex = 0; - } + } mergeParams(bindParams: StmtBindParams): void { @@ -45,7 +55,6 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { this.addParams(fieldParam.params, fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType); } } - console.log(`StmtBindParams merge params, field params size: ${this._fieldParams[0].params.length}`); } encode(): void{ diff --git a/nodejs/src/stmt/wsParamsBase.ts b/nodejs/src/stmt/wsParamsBase.ts index 2604dea4..2fa1c96e 100644 --- a/nodejs/src/stmt/wsParamsBase.ts +++ b/nodejs/src/stmt/wsParamsBase.ts @@ -18,24 +18,43 @@ export abstract class StmtBindParams { protected _dataTotalLen:number = 0; protected paramsCount: number = 0; protected _rows = 0; + protected _bindCount = 0; constructor(precision?:number, paramsCount?: number) { if (precision) { this.precisionLength = precision } this._params = []; - if (paramsCount) { + if (paramsCount && paramsCount > 0) { this.paramsCount = paramsCount; this._fieldParams = new Array(paramsCount); + } else { + this.paramsCount = 0; + this._fieldParams = []; } } abstract encode(): void; abstract addParams(params: any[], dataType: string, typeLen: number, columnType: number): void; + + addBindFieldParams(fieldParams: FieldBindParams): void { + if (!fieldParams || !fieldParams.params || fieldParams.params.length == 0) { + throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "StmtBindParams params is invalid!"); + } + if (!this._fieldParams) { + this._fieldParams = []; + } + this.addParams(fieldParams.params, fieldParams.dataType, fieldParams.typeLen, fieldParams.columnType); + } abstract mergeParams(bindParams: StmtBindParams): void; + + getBindCount(): number { + return this._bindCount; + } + getDataRows(): number { return this._rows; } diff --git a/nodejs/src/stmt/wsStmt.ts b/nodejs/src/stmt/wsStmt.ts index e19efdd6..c1401bbe 100644 --- a/nodejs/src/stmt/wsStmt.ts +++ b/nodejs/src/stmt/wsStmt.ts @@ -1,3 +1,4 @@ +import { WSRows } from "../sql/wsRows"; import { StmtBindParams } from "./wsParamsBase"; export interface WsStmt { @@ -8,6 +9,8 @@ export interface WsStmt { bind(paramsArray:StmtBindParams): Promise; batch(): Promise; exec(): Promise; + resultSet(): Promise; getLastAffected(): number | null | undefined; close(): Promise + } \ No newline at end of file diff --git a/nodejs/src/stmt/wsStmt1.ts b/nodejs/src/stmt/wsStmt1.ts index 3d4b6089..8948ef3e 100644 --- a/nodejs/src/stmt/wsStmt1.ts +++ b/nodejs/src/stmt/wsStmt1.ts @@ -8,6 +8,7 @@ import { StmtBindParams } from './wsParamsBase'; import { WsStmt } from './wsStmt'; import logger from '../common/log'; import { Stmt1BindParams } from './wsParams1'; +import { WSRows } from '../sql/wsRows'; export class WsStmt1 implements WsStmt{ private _wsClient: WsClient; @@ -135,6 +136,10 @@ export class WsStmt1 implements WsStmt{ return await this.execute(queryMsg); } + async resultSet(): Promise { + throw new Error("Method not implemented."); + } + getLastAffected() { return this.lastAffected; } diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 9c2d9781..7559602d 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -13,8 +13,8 @@ import { Stmt2BindParams } from './wsParams2'; import { TableInfo } from './wsTableInfo'; import { ColumnInfo } from './wsColumnInfo'; import { WSRows } from '../sql/wsRows'; - - +import { FieldBindParams } from './FieldBindParams'; +import { log } from 'console'; export class WsStmt2 implements WsStmt{ private _wsClient: WsClient; @@ -29,7 +29,8 @@ export class WsStmt2 implements WsStmt{ private _toBeBindTagCount: number; private _toBeBindColCount: number; private _toBeBindTableNameIndex: number | undefined | null; - + private _toBeBindTagIndexes: number[]; + private _isInsert: boolean = false; private constructor(wsClient: WsClient, precision?:number) { this._wsClient = wsClient; if (precision) { @@ -38,6 +39,7 @@ export class WsStmt2 implements WsStmt{ this._stmtTableInfo = new Map(); this._currentTableInfo = new TableInfo(); this._stmtTableInfoList = []; + this._toBeBindTagIndexes = []; this._toBeBindColCount = 0; this._toBeBindTagCount = 0; } @@ -90,21 +92,24 @@ export class WsStmt2 implements WsStmt{ }, }; await this.execute(queryMsg); - if (this.fields && this.fields[0].precision) { - this._precision = this.fields[0].precision; + + if (this._isInsert && this.fields) { + this._precision = this.fields[0].precision ? this.fields[0].precision : 0; + this._toBeBindColCount = 0; + this._toBeBindTagCount = 0; + this.fields?.forEach((field, index) => { + if (field.bind_type == FieldBindType.TAOS_FIELD_TBNAME) { + this._toBeBindTableNameIndex = index; + } else if (field.bind_type == FieldBindType.TAOS_FIELD_TAG) { + this._toBeBindTagCount++; + } else if (field.bind_type == FieldBindType.TAOS_FIELD_COL) { + this._toBeBindColCount++; + } + }); + } else { + this._stmtTableInfoList = [this._currentTableInfo]; } - this._toBeBindColCount = 0; - this._toBeBindTagCount = 0; - this.fields?.forEach((field, index) => { - if (field.bind_type == FieldBindType.TAOS_FIELD_TBNAME) { - this._toBeBindTableNameIndex = index; - } else if (field.bind_type == FieldBindType.TAOS_FIELD_TAG) { - this._toBeBindTagCount++; - } else if (field.bind_type == FieldBindType.TAOS_FIELD_COL) { - this._toBeBindColCount++; - } - }); } @@ -127,9 +132,6 @@ export class WsStmt2 implements WsStmt{ if (!paramsArray || !this._stmt_id) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "SetBinaryTags paramArray is invalid!"); } - for (let i = 0; i < paramsArray.getParams().length; i++) { - let param = paramsArray.getParams()[i]; - } if (!this._currentTableInfo) { this._currentTableInfo = new TableInfo(); @@ -141,24 +143,53 @@ export class WsStmt2 implements WsStmt{ } newStmtParam(): StmtBindParams { - if (!this.fields || this.fields.length === 0) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "No columns to bind!"); + if (this._isInsert) { + if (!this.fields || this.fields.length === 0) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "No columns to bind!"); + } + return new Stmt2BindParams(this.fields.length, this._precision, this.fields); } - console.log(`Creating new Stmt2BindParams with precision: ${this._precision}, field count: ${this.fields.length}`); - return new Stmt2BindParams(this.fields.length, this._precision); + return new Stmt2BindParams(); } async bind(paramsArray: StmtBindParams): Promise { - if (!paramsArray || !this._stmt_id) { + if (!paramsArray || !this._stmt_id || !paramsArray._fieldParams) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind paramArray is invalid!"); } - if (!this._currentTableInfo) { - this._currentTableInfo = new TableInfo(); - this._stmtTableInfoList.push(this._currentTableInfo); - console.log(`New table info created for table: ${this._currentTableInfo.getTableName()}`); - } - await this._currentTableInfo.setParams(paramsArray); + if (this._isInsert && this.fields && paramsArray.getBindCount() == this.fields.length) { + const tableNameIndex = this._toBeBindTableNameIndex; + if (tableNameIndex === null || tableNameIndex === undefined) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name index is null or undefined!"); + } + + const paramsCount = paramsArray._fieldParams[0].params.length; + for (let i = 0; i < paramsCount; i++) { + let tableName = paramsArray._fieldParams[tableNameIndex].params[i]; + await this.setTableName(tableName); + for (let j = 0; j < paramsArray._fieldParams.length; j++) { + if (j == tableNameIndex) { + continue; + } + let fieldParam = paramsArray._fieldParams[j]; + if (this.fields[j].bind_type == FieldBindType.TAOS_FIELD_TAG) { + if (!this._currentTableInfo.tags) { + this._currentTableInfo.tags = new Stmt2BindParams(this._toBeBindTagCount, this._precision, this.fields); + } + this._currentTableInfo.tags.addBindFieldParams(new FieldBindParams([fieldParam.params[i]], fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType, fieldParam.bindType)); + }else if (this.fields[j].bind_type == FieldBindType.TAOS_FIELD_COL) { + if (!this._currentTableInfo.params) { + this._currentTableInfo.params = new Stmt2BindParams(this._toBeBindColCount, this._precision, this.fields); + } + this._currentTableInfo.params.addBindFieldParams(new FieldBindParams([fieldParam.params[i]], fieldParam.dataType, fieldParam.typeLen, fieldParam.columnType, fieldParam.bindType)); + } + } + + } + } else { + await this._currentTableInfo.setParams(paramsArray); + } + return Promise.resolve(); } @@ -170,8 +201,8 @@ export class WsStmt2 implements WsStmt{ if (!this._currentTableInfo ) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "table info is empty!"); } - let params = this._currentTableInfo.getParams(); + if (!params) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); } @@ -243,6 +274,11 @@ export class WsStmt2 implements WsStmt{ if (resp.fields) { this.fields = resp.fields; } + if (resp.is_insert) { + this._isInsert = resp.is_insert; + } else { + this._toBeBindColCount = resp.fields_count ? resp.fields_count : 0; + } return resp; } @@ -285,7 +321,6 @@ export class WsStmt2 implements WsStmt{ } }); } - let totalTagSize = 0; let tagSizeList:Array = []; if (this._toBeBindTagCount > 0) { @@ -299,7 +334,7 @@ export class WsStmt2 implements WsStmt{ } }); } - + let totalColSize = 0; let colSizeList:Array = []; if (this._toBeBindColCount > 0) { @@ -308,6 +343,7 @@ export class WsStmt2 implements WsStmt{ if (!params) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); } + params.encode(); let colSize = params.getDataTotalLen(); totalColSize += colSize; @@ -315,7 +351,6 @@ export class WsStmt2 implements WsStmt{ }); } - let totalSize = totalTableNameSize + totalTagSize + totalColSize; let toBeBindTableNameCount = (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex >= 0) ? 1 : 0; totalSize += this._stmtTableInfoList.length * ( @@ -326,17 +361,16 @@ export class WsStmt2 implements WsStmt{ const bytes: number[] = []; // 写入 req_id bytes.push(...bigintToBytes(reqId)); - + // 写入 stmt_id if (this._stmt_id) { bytes.push(...bigintToBytes(this._stmt_id)); } - bytes.push(...bigintToBytes(9n)); bytes.push(...shotToBytes(1)); bytes.push(...intToBytes(-1)); bytes.push(...intToBytes(totalSize + 28, false)); - + bytes.push(...intToBytes(this._stmtTableInfoList.length)); bytes.push(...intToBytes(this._toBeBindTagCount)); bytes.push(...intToBytes(this._toBeBindColCount)); @@ -345,7 +379,7 @@ export class WsStmt2 implements WsStmt{ } else { bytes.push(...intToBytes(0)); } - + if (this._toBeBindTagCount) { if (toBeBindTableNameCount > 0) { bytes.push(...intToBytes(28 + totalTableNameSize + 2 * this._stmtTableInfoList.length)); @@ -409,7 +443,7 @@ export class WsStmt2 implements WsStmt{ } } - // TagsDataLength + // ColumnDataLength if (this._toBeBindColCount > 0) { for (let colSize of colSizeList) { bytes.push(...intToBytes(colSize, false)); diff --git a/nodejs/src/stmt/wsTableInfo.ts b/nodejs/src/stmt/wsTableInfo.ts index 04207a8f..d74ea904 100644 --- a/nodejs/src/stmt/wsTableInfo.ts +++ b/nodejs/src/stmt/wsTableInfo.ts @@ -43,7 +43,6 @@ export class TableInfo { } else { if (bindParams._fieldParams) { this.params.mergeParams(bindParams); - console.log(`setParams params merged, total rows: ${bindParams._fieldParams[0].params.length}`); } } } diff --git a/nodejs/test/bulkPulling/stmt.func.test.ts b/nodejs/test/bulkPulling/stmt.func.test.ts index 34ba8686..85e3f7d1 100644 --- a/nodejs/test/bulkPulling/stmt.func.test.ts +++ b/nodejs/test/bulkPulling/stmt.func.test.ts @@ -143,7 +143,43 @@ describe('TDWebSocket.Stmt()', () => { await connector.close(); }); - test('normal BindParam', async() => { + test('Bind supper table', async() => { + let conf = new WSConfig(dns); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + await stmt.prepare('INSERT INTO meters (ts, tbname, current, voltage, phase, location, groupId) VALUES (?, ?, ?, ?, ?, ?, ?)'); + let lastTs = 0 + for (let i = 0; i < 10; i++) { + for (let j = 0; j < multi[0].length; j++) { + multi[0][j] = multi[0][0] + j; + lastTs = multi[0][j] + } + + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setVarchar([`d1001`, `d1002`, `d1003`]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + dataParams.setVarchar(['SanFrancisco_1', 'SanFrancisco_2', 'SanFrancisco_3']); + dataParams.setInt([1, 2, 3]); + await stmt.bind(dataParams) + multi[0][0] = lastTs + 1; + + } + + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(30) + await stmt.close() + await connector.close(); + }); + + test('Bind a single table', async() => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') @@ -171,10 +207,45 @@ describe('TDWebSocket.Stmt()', () => { dataParams.setFloat(multi[1]) dataParams.setInt(multi[2]) dataParams.setFloat(multi[3]) - if (dataParams._fieldParams) { - console.log(`bind ${dataParams._fieldParams[0].params.length} rows data ${multi[0].length}`) + await stmt.bind(dataParams) + + multi[0][0] = lastTs + 1; + } + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(30) + await stmt.close() + await connector.close(); + }); + + test('Bind multiple tables', async() => { + let conf = new WSConfig(dns); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); + let lastTs = 0 + for (let i = 0; i < 10; i++) { + for (let j = 0; j < multi[0].length; j++) { + multi[0][j] = multi[0][0] + j; + lastTs = multi[0][j] } - stmt.bind(dataParams) + await stmt.setTableName(`power_stmt.d100${i+1}`); + + let params = stmt.newStmtParam() + params.setVarchar([`SanFrancisco${i+1}`]); + params.setInt([i+1]); + await stmt.setTags(params) + + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) multi[0][0] = lastTs + 1; @@ -187,6 +258,31 @@ describe('TDWebSocket.Stmt()', () => { await connector.close(); }); + test('query bind', async() => { + let conf = new WSConfig(dns); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + await stmt.prepare('select * from meters where ts > ? and ts < ?'); + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp([1709183268565]) + dataParams.setTimestamp([1709183268569]) + await stmt.bind(dataParams) + await stmt.exec() + let wsRows = await stmt.resultSet() + while (await wsRows.next()) { + let result = await wsRows.getData(); + console.log(result) + expect(result).toBeTruthy() + } + await wsRows.close() + await stmt.close() + await connector.close(); + }); + test('error BindParam', async() => { let conf = new WSConfig(dns); @@ -414,11 +510,11 @@ describe('TDWebSocket.Stmt()', () => { }) afterAll(async () => { - let conf :WSConfig = new WSConfig(dns); - conf.setUser('root'); - conf.setPwd('taosdata'); - let wsSql = await WsSql.open(conf); - await wsSql.exec('drop database power_stmt'); - await wsSql.close(); + // let conf :WSConfig = new WSConfig(dns); + // conf.setUser('root'); + // conf.setPwd('taosdata'); + // let wsSql = await WsSql.open(conf); + // await wsSql.exec('drop database power_stmt'); + // await wsSql.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file From 80215ff8840f477a1955ef05c009ba74dde607a6 Mon Sep 17 00:00:00 2001 From: menshibin Date: Fri, 12 Sep 2025 14:18:41 +0800 Subject: [PATCH 05/23] feat: add stmt version adaptation --- nodejs/src/client/wsClient.ts | 13 +- nodejs/src/common/config.ts | 13 +- nodejs/src/common/constant.ts | 2 +- nodejs/src/sql/wsSql.ts | 11 +- nodejs/src/stmt/wsParams1.ts | 6 +- nodejs/src/stmt/wsParams2.ts | 6 +- nodejs/src/stmt/wsProto.ts | 189 ++++++++ nodejs/src/stmt/wsStmt2.ts | 187 +------- nodejs/test/bulkPulling/sql.test.ts | 2 +- nodejs/test/bulkPulling/stmt1.func.test.ts | 432 ++++++++++++++++++ nodejs/test/bulkPulling/stmt1.type.test.ts | 364 +++++++++++++++ .../{stmt.func.test.ts => stmt2.func.test.ts} | 97 ++-- .../{stmt.type.test.ts => stmt2.type.test.ts} | 27 +- 13 files changed, 1120 insertions(+), 229 deletions(-) create mode 100644 nodejs/test/bulkPulling/stmt1.func.test.ts create mode 100644 nodejs/test/bulkPulling/stmt1.type.test.ts rename nodejs/test/bulkPulling/{stmt.func.test.ts => stmt2.func.test.ts} (82%) rename nodejs/test/bulkPulling/{stmt.type.test.ts => stmt2.type.test.ts} (95%) diff --git a/nodejs/src/client/wsClient.ts b/nodejs/src/client/wsClient.ts index f0daa2f8..1373e2b4 100644 --- a/nodejs/src/client/wsClient.ts +++ b/nodejs/src/client/wsClient.ts @@ -18,6 +18,7 @@ export class WsClient { private _timezone?:string | undefined | null; private readonly _url:URL; private static readonly _minVersion = "3.3.2.0"; + private _version?: string | undefined | null; constructor(url: URL, timeout ?:number | undefined | null) { this.checkURL(url); @@ -203,6 +204,10 @@ export class WsClient { } async version(): Promise { + if (this._version) { + return this._version; + } + let versionMsg = { action: 'version', args: { @@ -246,11 +251,11 @@ export class WsClient { } async checkVersion() { - let version = await this.version(); - let result = compareVersions(version, WsClient._minVersion); + this._version = await this.version(); + let result = compareVersions(this._version, WsClient._minVersion); if (result < 0) { - logger.error(`TDengine version is too low, current version: ${version}, minimum required version: ${WsClient._minVersion}`); - throw(new WebSocketQueryError(ErrorCode.ERR_TDENIGNE_VERSION_IS_TOO_LOW, `Version mismatch. The minimum required TDengine version is ${WsClient._minVersion}`)); + logger.error(`TDengine version is too low, current version: ${this._version}, minimum required version: ${WsClient._minVersion}`); + throw(new WebSocketQueryError(ErrorCode.ERR_TDENIGNE_VERSION_IS_TOO_LOW, `Version mismatch. The minimum required TDengine version is ${WsClient._minVersion}`)); } } } diff --git a/nodejs/src/common/config.ts b/nodejs/src/common/config.ts index 239431fd..06951d1f 100644 --- a/nodejs/src/common/config.ts +++ b/nodejs/src/common/config.ts @@ -1,3 +1,5 @@ +import { MinStmt2Version } from "./constant"; + export class WSConfig { private _user: string | undefined | null; private _password: string | undefined | null; @@ -6,9 +8,15 @@ export class WSConfig { private _timeout:number| undefined | null; private _token:string | undefined | null; private _timezone:string | undefined | null; + private _minStmt2Version:string; - constructor(url:string) { + constructor(url:string, minStmt2Version?:string) { this._url = url; + if (!minStmt2Version){ + this._minStmt2Version = MinStmt2Version; + } else { + this._minStmt2Version = minStmt2Version; + } } public getToken(): string | undefined | null { @@ -59,4 +67,7 @@ export class WSConfig { public getTimezone(): string | undefined | null { return this._timezone; } + public getMinStmt2Version(){ + return this._minStmt2Version; + } } diff --git a/nodejs/src/common/constant.ts b/nodejs/src/common/constant.ts index 4fe0f248..8e446305 100644 --- a/nodejs/src/common/constant.ts +++ b/nodejs/src/common/constant.ts @@ -8,7 +8,7 @@ export interface StringIndexable { export const BinaryQueryMessage: bigint = BigInt(6); export const FetchRawBlockMessage: bigint = BigInt(7); - +export const MinStmt2Version:string = "3.3.6.0"; export const TDengineTypeName: IndexableString = { 0: 'NULL', 1: 'BOOL', diff --git a/nodejs/src/sql/wsSql.ts b/nodejs/src/sql/wsSql.ts index 2ff3bb55..14bd92a6 100644 --- a/nodejs/src/sql/wsSql.ts +++ b/nodejs/src/sql/wsSql.ts @@ -3,14 +3,14 @@ import { parseBlock, TaosResult } from '../common/taosResult' import { WsClient } from '../client/wsClient' import { ErrorCode, TDWebSocketClientError, TaosResultError, WebSocketInterfaceError } from '../common/wsError' import { WSConfig } from '../common/config' -import { getBinarySql, getUrl } from '../common/utils' +import { compareVersions, getBinarySql, getUrl } from '../common/utils' import { WSFetchBlockResponse, WSQueryResponse } from '../client/wsResponse' import { Precision, SchemalessMessageInfo, SchemalessProto } from './wsProto' -import { WsStmt1 } from '../stmt/wsStmt1' import { ReqId } from '../common/reqid' -import { BinaryQueryMessage, FetchRawBlockMessage, PrecisionLength, TSDB_OPTION_CONNECTION } from '../common/constant' +import { BinaryQueryMessage, FetchRawBlockMessage, MinStmt2Version, PrecisionLength, TSDB_OPTION_CONNECTION } from '../common/constant' import logger from '../common/log' import { WsStmt } from '../stmt/wsStmt' +import { WsStmt1 } from '../stmt/wsStmt1' import { WsStmt2 } from '../stmt/wsStmt2' export class WsSql{ @@ -108,6 +108,11 @@ export class WsSql{ precision = PrecisionLength[data[0][0]] } } + let version = await this.version(); + let result = compareVersions(version, this.wsConfig.getMinStmt2Version()); + if (result < 0) { + return await WsStmt1.newStmt(this._wsClient, precision, reqId); + } return await WsStmt2.newStmt(this._wsClient, precision, reqId); } catch (e: any) { logger.error(`stmtInit failed, code: ${e.code}, message: ${e.message}`); diff --git a/nodejs/src/stmt/wsParams1.ts b/nodejs/src/stmt/wsParams1.ts index a5792aff..327a461a 100644 --- a/nodejs/src/stmt/wsParams1.ts +++ b/nodejs/src/stmt/wsParams1.ts @@ -61,13 +61,13 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ let date:Date = params[i] //node only support milliseconds, need fill 0 if (this.precisionLength == PrecisionLength['us']) { - let ms = date.getMilliseconds() * 1000 + let ms = date.getTime() * 1000 dataBuffer.setBigInt64(i * 8, BigInt(ms), true); }else if (this.precisionLength == PrecisionLength['ns']) { - let ns = date.getMilliseconds() * 1000 * 1000 + let ns = date.getTime() * 1000 * 1000 dataBuffer.setBigInt64(i * 8, BigInt(ns), true); }else { - dataBuffer.setBigInt64(i * 8, BigInt(date.getMilliseconds()), true); + dataBuffer.setBigInt64(i * 8, BigInt(date.getTime()), true); } } else if (typeof params[i] == 'bigint' || typeof params[i] == 'number') { diff --git a/nodejs/src/stmt/wsParams2.ts b/nodejs/src/stmt/wsParams2.ts index c2add6e9..e8843c96 100644 --- a/nodejs/src/stmt/wsParams2.ts +++ b/nodejs/src/stmt/wsParams2.ts @@ -159,11 +159,11 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder { //node only support milliseconds, need fill 0 if (this.precisionLength == PrecisionLength['us']) { - timeStamp = BigInt(date.getMilliseconds() * 1000); + timeStamp = BigInt(date.getTime() * 1000); }else if (this.precisionLength == PrecisionLength['ns']) { - timeStamp = BigInt(date.getMilliseconds() * 1000 * 1000); + timeStamp = BigInt(date.getTime() * 1000 * 1000); }else { - timeStamp = BigInt(date.getMilliseconds()); + timeStamp = BigInt(date.getTime()); } } else if (typeof params[i] == 'bigint' || typeof params[i] == 'number') { diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 18bd34da..18ca3eb6 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -3,6 +3,10 @@ import { WSQueryResponse } from "../client/wsResponse"; import { TDengineTypeLength } from "../common/constant"; import { MessageResp } from "../common/taosResult"; import { StmtBindParams } from "./wsParamsBase"; +import { bigintToBytes, intToBytes, shotToBytes } from "../common/utils"; +import { ColumnInfo } from "./wsColumnInfo"; +import { ErrorCode, TaosResultError } from "../common/wsError"; +import { TableInfo } from "./wsTableInfo"; export interface StmtMessageInfo { action: string; args: StmtParamsInfo; @@ -104,3 +108,188 @@ export function binaryBlockEncode(bindParams :StmtBindParams, bindType:StmtBindT return arrayBuffer; } + +export function stmt2BinaryBlockEncode(reqId: bigint, + stmtTableInfoList: TableInfo[], + stmtTableInfo: Map, + stmt_id: bigint | undefined | null, + toBeBindTableNameIndex:number | undefined | null, + toBeBindTagCount:number, + toBeBindColCount:number ):number[] { + if(stmt_id == null || stmt_id == undefined) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "stmt_id is invalid"); + } + // cloc totol size + let totalTableNameSize = 0; + let tableNameSizeList:Array = []; + if (toBeBindTableNameIndex != null && toBeBindTableNameIndex != undefined) { + stmtTableInfo.forEach((tableInfo) => { + let tableName = tableInfo.getTableName(); + if (tableName) { + let size = new TextEncoder().encode(tableName).length; + totalTableNameSize += size + 1; + tableNameSizeList.push(size + 1); + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + }); + } + let totalTagSize = 0; + let tagSizeList:Array = []; + if (toBeBindTagCount > 0) { + stmtTableInfoList.forEach((tableInfo) => { + let params = tableInfo.getTags(); + if (params) { + params.encode(); + let tagSize = params.getDataTotalLen(); + totalTagSize += tagSize; + tagSizeList.push(tagSize); + } + }); + } + + let totalColSize = 0; + let colSizeList:Array = []; + if (toBeBindColCount > 0) { + stmtTableInfoList.forEach((tableInfo) => { + let params = tableInfo.getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + + params.encode(); + let colSize = params.getDataTotalLen(); + totalColSize += colSize; + colSizeList.push(colSize); + + }); + } + let totalSize = totalTableNameSize + totalTagSize + totalColSize; + let toBeBindTableNameCount = (toBeBindTableNameIndex != null && toBeBindTableNameIndex >= 0) ? 1 : 0; + totalSize += stmtTableInfoList.length * ( + toBeBindTableNameCount * 2 + + (toBeBindTagCount > 0 ? 1 : 0) * 4 + + (toBeBindColCount > 0 ? 1 : 0) * 4); + + const bytes: number[] = []; + // 写入 req_id + bytes.push(...bigintToBytes(reqId)); + + // 写入 stmt_id + if (stmt_id) { + bytes.push(...bigintToBytes(stmt_id)); + } + bytes.push(...bigintToBytes(9n)); + bytes.push(...shotToBytes(1)); + bytes.push(...intToBytes(-1)); + bytes.push(...intToBytes(totalSize + 28, false)); + + bytes.push(...intToBytes(stmtTableInfoList.length)); + bytes.push(...intToBytes(toBeBindTagCount)); + bytes.push(...intToBytes(toBeBindColCount)); + if (toBeBindTableNameCount > 0) { + bytes.push(...intToBytes(0x1C)); + } else { + bytes.push(...intToBytes(0)); + } + + if (toBeBindTagCount) { + if (toBeBindTableNameCount > 0) { + bytes.push(...intToBytes(28 + totalTableNameSize + 2 * stmtTableInfoList.length)); + } else { + bytes.push(...intToBytes(28)); + } + } else { + bytes.push(...intToBytes(0)); + } + + if (toBeBindColCount > 0) { + let skipSize = 0; + if (toBeBindTableNameCount > 0) { + skipSize += totalTableNameSize + 2 * stmtTableInfoList.length; + } + + if (toBeBindTagCount > 0) { + skipSize += totalTagSize + 4 * stmtTableInfoList.length; + } + + // colOffset = 28(固定头) + skipSize + bytes.push(...intToBytes(28 + skipSize)); + } else { + bytes.push(...intToBytes(0)); + } + + + if (toBeBindTableNameCount > 0) { + for (let size of tableNameSizeList) { + if (size === 0) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + bytes.push(...shotToBytes(size)); + } + + for (let tableInfo of stmtTableInfoList) { + let tableName = tableInfo.getTableName(); + if (tableName && tableName.length > 0) { + let encoder = new TextEncoder().encode(tableName); + bytes.push(...encoder); + bytes.push(0); // null terminator + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + } + } + } + + if (toBeBindTagCount > 0) { + for (let size of tagSizeList) { + bytes.push(...intToBytes(size)); + } + for (let tableInfo of stmtTableInfoList) { + let tags = tableInfo.getTags(); + if (tags && tags.getParams().length > 0) { + for (let tagColumnInfo of tags.getParams()) { + serializeColumn(tagColumnInfo, bytes); + } + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); + } + } + } + + // ColumnDataLength + if (toBeBindColCount > 0) { + for (let colSize of colSizeList) { + bytes.push(...intToBytes(colSize, false)); + } + for (let tableInfo of stmtTableInfoList) { + let params = tableInfo.getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + + let colColumnInfos:ColumnInfo[] = params.getParams() + colColumnInfos.forEach(colColumnInfo => { + serializeColumn(colColumnInfo, bytes); + }); + } + } + return bytes + + } + + function serializeColumn(column: ColumnInfo, bytes: number[]) { + bytes.push(...intToBytes(column.length, false)); + bytes.push(...intToBytes(column.type)); + bytes.push(...intToBytes(column._rows)); + bytes.push(...column.isNull ? column.isNull : []); + bytes.push(column._haveLength); + + if (column._haveLength == 1 && column._dataLengths) { + column._dataLengths.forEach(length => { + bytes.push(...intToBytes(length)); + }); + } + bytes.push(...intToBytes(column.data.byteLength, false)); + bytes.push(...column.data.byteLength ? new Uint8Array(column.data) : []); + } + diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 7559602d..73d726f6 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -5,7 +5,7 @@ import logger from "../common/log"; import { ReqId } from "../common/reqid"; import { ErrorCode, TaosResultError, TDWebSocketClientError } from "../common/wsError"; import { StmtBindParams } from "./wsParamsBase"; -import { StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; +import { stmt2BinaryBlockEncode, StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; import { WsStmt } from "./wsStmt"; import { _isVarType } from '../common/taosResult'; import { bigintToBytes, intToBytes, shotToBytes } from '../common/utils'; @@ -29,7 +29,6 @@ export class WsStmt2 implements WsStmt{ private _toBeBindTagCount: number; private _toBeBindColCount: number; private _toBeBindTableNameIndex: number | undefined | null; - private _toBeBindTagIndexes: number[]; private _isInsert: boolean = false; private constructor(wsClient: WsClient, precision?:number) { this._wsClient = wsClient; @@ -39,7 +38,6 @@ export class WsStmt2 implements WsStmt{ this._stmtTableInfo = new Map(); this._currentTableInfo = new TableInfo(); this._stmtTableInfoList = []; - this._toBeBindTagIndexes = []; this._toBeBindColCount = 0; this._toBeBindTagCount = 0; } @@ -208,7 +206,13 @@ export class WsStmt2 implements WsStmt{ } let reqId = BigInt(ReqId.getReqID()); - let bytes = this.binaryBlockEncode(reqId) + let bytes = stmt2BinaryBlockEncode(reqId, + this._stmtTableInfoList, + this._stmtTableInfo, + this._stmt_id, + this._toBeBindTableNameIndex, + this._toBeBindTagCount, + this._toBeBindColCount) await this.sendBinaryMsg(reqId, 'stmt2_bind', new Uint8Array(bytes)); let execMsg = { @@ -305,178 +309,5 @@ export class WsStmt2 implements WsStmt{ } } - private binaryBlockEncode(reqId: bigint):number[] { - // cloc totol size - let totalTableNameSize = 0; - let tableNameSizeList:Array = []; - if (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex != undefined) { - this._stmtTableInfo.forEach((tableInfo) => { - let tableName = tableInfo.getTableName(); - if (tableName) { - let size = new TextEncoder().encode(tableName).length; - totalTableNameSize += size + 1; - tableNameSizeList.push(size + 1); - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - }); - } - let totalTagSize = 0; - let tagSizeList:Array = []; - if (this._toBeBindTagCount > 0) { - this._stmtTableInfoList.forEach((tableInfo) => { - let params = tableInfo.getTags(); - if (params) { - params.encode(); - let tagSize = params.getDataTotalLen(); - totalTagSize += tagSize; - tagSizeList.push(tagSize); - } - }); - } - - let totalColSize = 0; - let colSizeList:Array = []; - if (this._toBeBindColCount > 0) { - this._stmtTableInfoList.forEach((tableInfo) => { - let params = tableInfo.getParams(); - if (!params) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); - } - - params.encode(); - let colSize = params.getDataTotalLen(); - totalColSize += colSize; - colSizeList.push(colSize); - - }); - } - let totalSize = totalTableNameSize + totalTagSize + totalColSize; - let toBeBindTableNameCount = (this._toBeBindTableNameIndex != null && this._toBeBindTableNameIndex >= 0) ? 1 : 0; - totalSize += this._stmtTableInfoList.length * ( - toBeBindTableNameCount * 2 - + (this._toBeBindTagCount > 0 ? 1 : 0) * 4 - + (this._toBeBindColCount > 0 ? 1 : 0) * 4); - - const bytes: number[] = []; - // 写入 req_id - bytes.push(...bigintToBytes(reqId)); - - // 写入 stmt_id - if (this._stmt_id) { - bytes.push(...bigintToBytes(this._stmt_id)); - } - bytes.push(...bigintToBytes(9n)); - bytes.push(...shotToBytes(1)); - bytes.push(...intToBytes(-1)); - bytes.push(...intToBytes(totalSize + 28, false)); - - bytes.push(...intToBytes(this._stmtTableInfoList.length)); - bytes.push(...intToBytes(this._toBeBindTagCount)); - bytes.push(...intToBytes(this._toBeBindColCount)); - if (toBeBindTableNameCount > 0) { - bytes.push(...intToBytes(0x1C)); - } else { - bytes.push(...intToBytes(0)); - } - - if (this._toBeBindTagCount) { - if (toBeBindTableNameCount > 0) { - bytes.push(...intToBytes(28 + totalTableNameSize + 2 * this._stmtTableInfoList.length)); - } else { - bytes.push(...intToBytes(28)); - } - } else { - bytes.push(...intToBytes(0)); - } - - if (this._toBeBindColCount > 0) { - let skipSize = 0; - if (toBeBindTableNameCount > 0) { - skipSize += totalTableNameSize + 2 * this._stmtTableInfoList.length; - } - - if (this._toBeBindTagCount > 0) { - skipSize += totalTagSize + 4 * this._stmtTableInfoList.length; - } - - // colOffset = 28(固定头) + skipSize - bytes.push(...intToBytes(28 + skipSize)); - } else { - bytes.push(...intToBytes(0)); - } - - - if (toBeBindTableNameCount > 0) { - for (let size of tableNameSizeList) { - if (size === 0) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - bytes.push(...shotToBytes(size)); - } - - for (let tableInfo of this._stmtTableInfoList) { - let tableName = tableInfo.getTableName(); - if (tableName && tableName.length > 0) { - let encoder = new TextEncoder().encode(tableName); - bytes.push(...encoder); - bytes.push(0); // null terminator - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - } - } - - if (this._toBeBindTagCount > 0) { - for (let size of tagSizeList) { - bytes.push(...intToBytes(size)); - } - for (let tableInfo of this._stmtTableInfoList) { - let tags = tableInfo.getTags(); - if (tags && tags.getParams().length > 0) { - for (let tagColumnInfo of tags.getParams()) { - this.serializeColumn(tagColumnInfo, bytes); - } - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); - } - } - } - - // ColumnDataLength - if (this._toBeBindColCount > 0) { - for (let colSize of colSizeList) { - bytes.push(...intToBytes(colSize, false)); - } - for (let tableInfo of this._stmtTableInfoList) { - let params = tableInfo.getParams(); - if (!params) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); - } - - let colColumnInfos:ColumnInfo[] = params.getParams() - colColumnInfos.forEach(colColumnInfo => { - this.serializeColumn(colColumnInfo, bytes); - }); - } - } - return bytes - - } - - private serializeColumn(column: ColumnInfo, bytes: number[]) { - bytes.push(...intToBytes(column.length, false)); - bytes.push(...intToBytes(column.type)); - bytes.push(...intToBytes(column._rows)); - bytes.push(...column.isNull ? column.isNull : []); - bytes.push(column._haveLength); - - if (column._haveLength == 1 && column._dataLengths) { - column._dataLengths.forEach(length => { - bytes.push(...intToBytes(length)); - }); - } - bytes.push(...intToBytes(column.data.byteLength, false)); - bytes.push(...column.data.byteLength ? new Uint8Array(column.data) : []); - } + } \ No newline at end of file diff --git a/nodejs/test/bulkPulling/sql.test.ts b/nodejs/test/bulkPulling/sql.test.ts index 230b0bd9..3fa8f930 100644 --- a/nodejs/test/bulkPulling/sql.test.ts +++ b/nodejs/test/bulkPulling/sql.test.ts @@ -4,7 +4,7 @@ import { WsSql } from "../../src/sql/wsSql"; import { Sleep } from "../utils"; import logger, { setLevel } from "../../src/common/log" -let dns = 'ws://192.168.2.156:6041' +let dns = 'ws://localhost:6041' let password1 = 'Ab1!@#$%,.:?<>;~' let password2 = 'Bc%^&*()-_+=[]{}' setLevel("debug") diff --git a/nodejs/test/bulkPulling/stmt1.func.test.ts b/nodejs/test/bulkPulling/stmt1.func.test.ts new file mode 100644 index 00000000..9d88c58c --- /dev/null +++ b/nodejs/test/bulkPulling/stmt1.func.test.ts @@ -0,0 +1,432 @@ + +import { WebSocketConnectionPool } from "../../src/client/wsConnectorPool"; +import { WSConfig } from "../../src/common/config"; +import { setLevel } from "../../src/common/log"; +import { WsSql } from "../../src/sql/wsSql"; +import { WsStmt1 } from "../../src/stmt/wsStmt1"; + +let dns = 'ws://localhost:6041' +setLevel("debug") +beforeAll(async () => { + let conf :WSConfig = new WSConfig(dns); + conf.setUser('root'); + conf.setPwd('taosdata'); + let wsSql = await WsSql.open(conf); + await wsSql.exec('create database if not exists power_stmt KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); + await wsSql.exec('CREATE STABLE if not exists power_stmt.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + await wsSql.close(); +}) +describe('TDWebSocket.Stmt()', () => { + jest.setTimeout(20 * 1000) + let tags = ['California', 3]; + let multi = [ + // [1709183268567], + // [10.2], + // [292], + // [0.32], + [1709183268567, 1709183268568, 1709183268569], + [10.2, 10.3, 10.4], + [292, 293, 294], + [0.32, 0.33, 0.34], + ]; + + test('normal connect', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.close() + await connector.close(); + }); + + test('connect db with error', async() => { + expect.assertions(1) + let connector = null; + try { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('jest') + connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + await stmt.close() + }catch(e){ + let err:any = e + expect(err.message).toMatch('Database not exist') + }finally{ + if(connector) { + await connector.close() + } + } + }) + + test('normal Prepare', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar([tags[0]]); + params.setInt([tags[1]]); + await stmt.setTags(params); + await stmt.close() + await connector.close(); + }); + + test('set tag error', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar([tags[0]]); + try { + await stmt.setTags(params) + } catch(err:any) { + expect(err.message).toMatch('stmt tags count not match') + } + await stmt.close() + await connector.close(); + }); + + test('error Prepare table', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + try{ + await stmt.prepare('INSERT ? INTO ? USING meters TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + }catch(e) { + let err:any = e + expect(err.message).toMatch(/keyword INTO is expected|Syntax error in SQL/) + } + await stmt.close() + await connector.close(); + }); + + test('error Prepare tag', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + try{ + await stmt.prepare('INSERT INTO ? USING meters TAGS (?, ?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + }catch(e) { + let err:any = e + expect(err.message).toMatch("Tags number not matched") + } + await stmt.close() + await connector.close(); + }); + + test('Bind a single table', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('power_stmt.d1001'); + + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([1]); + await stmt.setTags(params) + + let lastTs = 0 + for (let i = 0; i < 10; i++) { + for (let j = 0; j < multi[0].length; j++) { + multi[0][j] = multi[0][0] + j; + lastTs = multi[0][j] + } + + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) + + multi[0][0] = lastTs + 1; + } + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(30) + await stmt.close() + await connector.close(); + }); + + test('error BindParam', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([7]); + await stmt.setTags(params) + let multi = [ + [1709183268567, 1709183268568], + [10.2, 10.3, 10.4, 10.5], + [292, 293, 294], + [0.32, 0.33, 0.31], + ]; + try{ + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) + await stmt.batch() + await stmt.exec() + }catch(e) { + let err:any = e + expect(err.message).toMatch(/wrong row length|bind data length error/) + } + await stmt.close() + await connector.close(); + }); + + test('no Batch', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([7]); + await stmt.setTags(params) + let multi = [ + [1709183268567, 1709183268568], + [10.2, 10.3], + [292, 293], + [0.32, 0.33], + ]; + try{ + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) + await stmt.exec() + }catch(e) { + let err:any = e + expect(err.message).toMatch("Stmt API usage error") + } + await stmt.close() + await connector.close(); + }); + + test('Batch after BindParam', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([7]); + await stmt.setTags(params) + let multi1 = [ + [1709188881548, 1709188881549], + [10.2, 10.3], + [292, 293], + [0.32, 0.33], + ]; + let multi2 = [ + [1709188881550, 1709188881551], + [10.2, 10.3], + [292, 293], + [0.32, 0.33], + ]; + + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi1[0]) + dataParams.setFloat(multi1[1]) + dataParams.setInt(multi1[2]) + dataParams.setFloat(multi1[3]) + await stmt.bind(dataParams) + await stmt.batch() + + await stmt.setTableName('d1002'); + params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([5]); + await stmt.setTags(params) + + dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi2[0]) + dataParams.setFloat(multi2[1]) + dataParams.setInt(multi2[2]) + dataParams.setFloat(multi2[3]) + await stmt.bind(dataParams) + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(4) + await stmt.close() + await connector.close(); + }); + + test('no set tag', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + // await stmt.SetTags(tags) + try{ + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) + await stmt.batch() + await stmt.exec() + }catch(e) { + let err:any = e + expect(err.message).toMatch(/Retry needed|Tags are empty/); + } + await stmt.close() + await connector.close(); + }); + + test('normal binary BindParam', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1002'); + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([7]); + await stmt.setTags(params) + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi[0]) + dataParams.setFloat(multi[1]) + dataParams.setInt(multi[2]) + dataParams.setFloat(multi[3]) + await stmt.bind(dataParams) + + await stmt.batch() + await stmt.exec() + + let result = await connector.exec("select * from power_stmt.meters") + console.log(result) + await stmt.close() + await connector.close(); + + }); + + test('normal json BindParam', async() => { + let conf = new WSConfig(dns, "100.100.100.100"); + conf.setUser('root') + conf.setPwd('taosdata') + conf.setDb('power_stmt') + let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); + await stmt.setTableName('d1001'); + let params = stmt.newStmtParam() + params.setVarchar(['SanFrancisco']); + params.setInt([7]); + await stmt.setTags(params) + let multi1 = [ + [1709188881548, 1709188881549], + [10.2, 10.3], + [292, 293], + [0.32, 0.33], + ]; + let dataParams = stmt.newStmtParam() + dataParams.setTimestamp(multi1[0]) + dataParams.setFloat(multi1[1]) + dataParams.setInt(multi1[2]) + dataParams.setFloat(multi1[3]) + await stmt.bind(dataParams) + await stmt.batch() + await stmt.exec() + await stmt.close() + await connector.close(); + }); +}) + +afterAll(async () => { + let conf :WSConfig = new WSConfig(dns); + conf.setUser('root'); + conf.setPwd('taosdata'); + let wsSql = await WsSql.open(conf); + await wsSql.exec('drop database power_stmt'); + await wsSql.close(); + WebSocketConnectionPool.instance().destroyed() +}) \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt1.type.test.ts b/nodejs/test/bulkPulling/stmt1.type.test.ts new file mode 100644 index 00000000..f4958824 --- /dev/null +++ b/nodejs/test/bulkPulling/stmt1.type.test.ts @@ -0,0 +1,364 @@ +import { WebSocketConnectionPool } from "../../src/client/wsConnectorPool"; +import { WSConfig } from "../../src/common/config"; +import { setLevel } from "../../src/common/log"; +import { WsSql } from "../../src/sql/wsSql"; +import { WsStmt1 } from "../../src/stmt/wsStmt1"; +import { createBaseSTable, createBaseSTableJSON, createSTableJSON, getInsertBind } from "../utils"; + +const stable = 'ws_stmt_stb'; +const table = 'stmt_001'; +const db = 'ws_stmt' +const createDB = `create database if not exists ${db} keep 3650` +const useDB = `use ${db}` +const dropDB = `drop database if exists ${db}` + +const tableCN = 'stmt_cn'; +const stableCN = 'ws_stmt_stb_cn'; +const jsonTable = 'stmt_json'; +const jsonTableCN = 'stmt_json_cn'; + + +const stableTags = [true, -1, -2, -3, -4, 1, 2, 3, 4, parseFloat(3.1415.toFixed(5)), parseFloat(3.14159265.toFixed(15)), 'varchar_tag_1', 'nchar_tag_1'] +const stableCNTags = [false, -1 * 2, -2 * 2, -3 * 2, -4 * 2, 1 * 2, 2 * 2, 3 * 2, 4 * 2, parseFloat((3.1415 * 2).toFixed(5)), parseFloat((3.14159265 * 2).toFixed(15)), 'varchar_标签_壹', 'nchar_标签_贰'] + +const tableValues = [ + [1656677710000, 1656677720000, 1656677730000, 1656677740000, 1656677750000], + [0, -1, -2, -3, -4], + [-1, -2,-3, -4, -5], + [-3, -4,-5, -6, -7], + + // [0, 1, 2, 3, 4], + [BigInt(-2), BigInt(-3), BigInt(-4), BigInt(-5), BigInt(-6)], + + [0, 1, 2, 3, 4], + [1, 2, 3, 4,5], + [2, 3, 4,5, 6], + + // [0, 1, 2, 3, 4], + [BigInt(3), BigInt(4), BigInt(5), BigInt(6), BigInt(7)], + + [parseFloat(3.1415.toFixed(5)), parseFloat((3.1415 * 2).toFixed(5)), parseFloat((3.1415 * 3).toFixed(5)), parseFloat((3.1415 * 4).toFixed(5)), parseFloat((3.1415 * 5).toFixed(5))], + [parseFloat(3.14159265.toFixed(15)), parseFloat((3.14159265 * 2).toFixed(15)), parseFloat((3.14159265 * 3).toFixed(15)), parseFloat((3.14159265 * 4).toFixed(15)), parseFloat((3.14159265 * 5).toFixed(15))], + ['varchar_col_1', 'varchar_col_2', 'varchar_col_3', 'varchar_col_4', 'varchar_col_5' ], + ['nchar_col_1', 'nchar_col_2', '', 'nchar_col_4', 'nchar_col_5'], + [true, false, true, false, true], + [null, null, null, null, null] +] + +const tableCNValues = [ + [BigInt(1656677760000), BigInt(1656677770000), BigInt(1656677780000), BigInt(1656677790000), BigInt(1656677100000)], + [0, -1, -2, -3, -4], + [-1, -2,-3, -4, -5], + [ -2, -3, -4,-5, -6], + [BigInt(-3), BigInt(-4),BigInt(-5), BigInt(-6), BigInt(-7)], + [0, 1, 2, 3, 4], + [1, 2, 3, 4,5], + [2, 3, 4,5, 6], + [BigInt(3), BigInt(4), BigInt(5), BigInt(6), BigInt(7)], + [parseFloat(3.1415.toFixed(5)), parseFloat((3.1415 * 2).toFixed(5)), parseFloat((3.1415 * 3).toFixed(5)), parseFloat((3.1415 * 4).toFixed(5)), parseFloat((3.1415 * 5).toFixed(5))], + [parseFloat(3.14159265.toFixed(15)), parseFloat((3.14159265 * 2).toFixed(15)), parseFloat((3.14159265 * 3).toFixed(15)), parseFloat((3.14159265 * 4).toFixed(15)), parseFloat((3.14159265 * 5).toFixed(15))], + ['varchar_列_壹','varchar_列_贰','varchar_列_叁','varchar_列_肆','varchar_列_伍'], + ['nchar_列_甲','nchar_列_乙','nchar_列_丙','nchar_列_丁','nchar_列_戊'], + [true, false, true, false, true], + [null, null, null, null, null], +] + +let geoDataArray:any[] = []; +let varbinary:any[] = []; +const encoder = new TextEncoder(); +for (let i = 0; i< 5; i++) { + let data = new Uint8Array([0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x59,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x40,]) + geoDataArray.push(data.buffer) ; + let vdata = encoder.encode(`varchar_col_${i+1}`) + varbinary.push(vdata.buffer) +} + +const jsonTags = ["{\"key1\":\"taos\",\"key2\":null,\"key3\":\"TDengine\",\"key4\":0,\"key5\":false}"] +const jsonTagsCN = ["{\"key1\":\"taosdata\",\"key2\":null,\"key3\":\"TDengine涛思数据\",\"key4\":1,\"key5\":true}"] + +const selectStable = `select * from ${stable}` +const selectStableCN = `select * from ${stableCN}` +const selectTable = `select * from ${table}` +const selectTableCN = `select * from ${tableCN}` +const selectJsonTable = `select * from ${jsonTable}` +const selectJsonTableCN = `select * from ${jsonTableCN}` + +let dsn = 'ws://root:taosdata@localhost:6041'; +setLevel("debug") +beforeAll(async () => { + let conf :WSConfig = new WSConfig(dsn) + let ws = await WsSql.open(conf); + await ws.exec(dropDB); + await ws.exec(createDB); + await ws.exec(useDB); + await ws.exec(createBaseSTable(stable)); + await ws.exec(createSTableJSON(jsonTable)); + await ws.close(); +}) + +describe('TDWebSocket.Stmt()', () => { + jest.setTimeout(20 * 1000) + test('normal BindParam', async() => { + let wsConf = new WSConfig(dsn, "100.100.100.100"); + wsConf.setDb(db) + let connector = await WsSql.open(wsConf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare(getInsertBind(tableValues.length + 2, stableTags.length, db, stable)); + await stmt.setTableName(table); + let tagParams = stmt.newStmtParam(); + tagParams.setBoolean([stableTags[0]]) + tagParams.setTinyInt([stableTags[1]]) + tagParams.setSmallInt([stableTags[2]]) + tagParams.setInt([stableTags[3]]) + tagParams.setBigint([BigInt(stableTags[4])]) + tagParams.setUTinyInt([stableTags[5]]) + + tagParams.setUSmallInt([stableTags[6]]) + tagParams.setUInt([stableTags[7]]) + tagParams.setUBigint([BigInt(stableTags[8])]) + tagParams.setFloat([stableTags[9]]) + tagParams.setDouble([stableTags[10]]) + tagParams.setBinary([stableTags[11]]) + tagParams.setNchar([stableTags[12]]) + await stmt.setTags(tagParams); + + let bindParams = stmt.newStmtParam(); + bindParams.setTimestamp(tableValues[0]) + bindParams.setTinyInt(tableValues[1]) + bindParams.setSmallInt(tableValues[2]) + bindParams.setInt(tableValues[3]) + bindParams.setBigint(tableValues[4]) + bindParams.setUTinyInt(tableValues[5]) + bindParams.setUSmallInt(tableValues[6]) + bindParams.setUInt(tableValues[7]) + bindParams.setUBigint(tableValues[8]) + bindParams.setFloat(tableValues[9]) + bindParams.setDouble(tableValues[10]) + + bindParams.setBinary(tableValues[11]) + bindParams.setNchar(tableValues[12]) + bindParams.setBoolean(tableValues[13]) + bindParams.setInt(tableValues[14]) + bindParams.setGeometry(geoDataArray) + bindParams.setVarBinary(varbinary) + + await stmt.bind(bindParams); + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(5) + await stmt.close() + let result = await connector.exec(`select * from ${db}.${stable}`) + console.log(result) + await connector.close(); + }); + + test('normal CN BindParam', async() => { + let wsConf = new WSConfig(dsn, "100.100.100.100"); + wsConf.setDb(db) + let connector = await WsSql.open(wsConf) + let stmt = await (await connector).stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare(getInsertBind(tableValues.length + 2, stableTags.length, db, stable)); + await stmt.setTableName(table); + let tagParams = stmt.newStmtParam(); + tagParams.setBoolean([stableCNTags[0]]) + tagParams.setTinyInt([stableCNTags[1]]) + tagParams.setSmallInt([stableCNTags[2]]) + tagParams.setInt([stableCNTags[3]]) + tagParams.setBigint([BigInt(stableCNTags[4])]) + tagParams.setUTinyInt([stableCNTags[5]]) + + tagParams.setUSmallInt([stableCNTags[6]]) + tagParams.setUInt([stableCNTags[7]]) + tagParams.setUBigint([BigInt(stableCNTags[8])]) + tagParams.setFloat([stableCNTags[9]]) + tagParams.setDouble([stableCNTags[10]]) + tagParams.setBinary([stableCNTags[11]]) + tagParams.setNchar([stableCNTags[12]]) + await stmt.setTags(tagParams); + + let bindParams = stmt.newStmtParam(); + bindParams.setTimestamp(tableCNValues[0]) + bindParams.setTinyInt(tableCNValues[1]) + bindParams.setSmallInt(tableCNValues[2]) + bindParams.setInt(tableCNValues[3]) + bindParams.setBigint(tableCNValues[4]) + bindParams.setUTinyInt(tableCNValues[5]) + bindParams.setUSmallInt(tableCNValues[6]) + bindParams.setUInt(tableCNValues[7]) + bindParams.setUBigint(tableCNValues[8]) + bindParams.setFloat(tableCNValues[9]) + bindParams.setDouble(tableCNValues[10]) + + bindParams.setBinary(tableCNValues[11]) + bindParams.setNchar(tableCNValues[12]) + bindParams.setBoolean(tableCNValues[13]) + bindParams.setInt(tableCNValues[14]) + bindParams.setGeometry(geoDataArray) + bindParams.setVarBinary(varbinary) + + await stmt.bind(bindParams); + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(5) + await stmt.close() + let result = await connector.exec(`select count(*) from ${db}.${stable}`) + console.log(result) + await connector.close(); + }); + + test('normal json tag BindParam', async() => { + let wsConf = new WSConfig(dsn, "100.100.100.100"); + wsConf.setDb(db) + let connector = await WsSql.open(wsConf) + let stmt = await (await connector).stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare(getInsertBind(tableValues.length + 2, jsonTags.length, db, jsonTable)); + await stmt.setTableName(`${jsonTable}_001`); + let tagParams = stmt.newStmtParam(); + tagParams.setJson(jsonTags); + await stmt.setTags(tagParams); + + let bindParams = stmt.newStmtParam(); + bindParams.setTimestamp(tableCNValues[0]) + bindParams.setTinyInt(tableCNValues[1]) + bindParams.setSmallInt(tableCNValues[2]) + bindParams.setInt(tableCNValues[3]) + bindParams.setBigint(tableCNValues[4]) + bindParams.setUTinyInt(tableCNValues[5]) + bindParams.setUSmallInt(tableCNValues[6]) + bindParams.setUInt(tableCNValues[7]) + bindParams.setUBigint(tableCNValues[8]) + bindParams.setFloat(tableCNValues[9]) + bindParams.setDouble(tableCNValues[10]) + + bindParams.setBinary(tableCNValues[11]) + bindParams.setNchar(tableCNValues[12]) + bindParams.setBoolean(tableCNValues[13]) + bindParams.setInt(tableCNValues[14]) + bindParams.setGeometry(geoDataArray) + bindParams.setVarBinary(varbinary) + + await stmt.bind(bindParams); + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(5) + await stmt.close() + let result = await connector.exec(`select * from ${db}.${jsonTable}`) + console.log(result) + await connector.close(); + }); + + test('normal json cn tag BindParam', async() => { + let wsConf = new WSConfig(dsn, "100.100.100.100"); + wsConf.setDb(db) + let connector = await WsSql.open(wsConf) + let stmt = await connector.stmtInit() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt1); + expect(connector.state()).toBeGreaterThan(0) + await stmt.prepare(getInsertBind(tableValues.length + 2, jsonTags.length, db, jsonTable)); + await stmt.setTableName(`${jsonTable}_001`); + let tagParams = stmt.newStmtParam(); + tagParams.setJson(jsonTagsCN); + await stmt.setTags(tagParams); + + let bindParams = stmt.newStmtParam(); + bindParams.setTimestamp(tableCNValues[0]) + bindParams.setTinyInt(tableCNValues[1]) + bindParams.setSmallInt(tableCNValues[2]) + bindParams.setInt(tableCNValues[3]) + bindParams.setBigint(tableCNValues[4]) + bindParams.setUTinyInt(tableCNValues[5]) + bindParams.setUSmallInt(tableCNValues[6]) + bindParams.setUInt(tableCNValues[7]) + bindParams.setUBigint(tableCNValues[8]) + bindParams.setFloat(tableCNValues[9]) + bindParams.setDouble(tableCNValues[10]) + + bindParams.setBinary(tableCNValues[11]) + bindParams.setNchar(tableCNValues[12]) + bindParams.setBoolean(tableCNValues[13]) + bindParams.setInt(tableCNValues[14]) + bindParams.setGeometry(geoDataArray) + bindParams.setVarBinary(varbinary) + + await stmt.bind(bindParams); + await stmt.batch() + await stmt.exec() + expect(stmt.getLastAffected()).toEqual(5) + await stmt.close() + let result = await connector.exec(`select * from ${db}.${jsonTable}`) + console.log(result) + await connector.close(); + }); + +}) + +test('test bind exception cases', async() => { + let wsConf = new WSConfig(dsn, "100.100.100.100"); + let connector = await WsSql.open(wsConf) + let stmt = await connector.stmtInit() + expect(stmt).toBeInstanceOf(WsStmt1); + const params = stmt.newStmtParam(); + + const emptyArrayMethods = [ + { method: 'setBoolean', name: 'SetBooleanColumn' }, + { method: 'setTinyInt', name: 'SetTinyIntColumn' }, + { method: 'setUTinyInt', name: 'SetUTinyIntColumn' }, + { method: 'setSmallInt', name: 'SetSmallIntColumn' }, + { method: 'setUSmallInt', name: 'SetSmallIntColumn' }, + { method: 'setInt', name: 'SetIntColumn' }, + { method: 'setUInt', name: 'SetUIntColumn' }, + { method: 'setBigint', name: 'SetBigIntColumn' }, + { method: 'setUBigint', name: 'SetUBigIntColumn' }, + { method: 'setFloat', name: 'SetFloatColumn' }, + { method: 'setDouble', name: 'SetDoubleColumn' }, + { method: 'setTimestamp', name: 'SeTimestampColumn' } + ]; + + emptyArrayMethods.forEach(({ method, name }) => { + expect(() => { + (params as any)[method]([]); + }).toThrow(`${name} params is invalid!`); + + expect(() => { + (params as any)[method](null); + }).toThrow(`${name} params is invalid!`); + + expect(() => { + (params as any)[method](undefined); + }).toThrow(`${name} params is invalid!`); + }); + + expect(() => { + params.setBoolean(['not boolean']); + }).toThrow('SetTinyIntColumn params is invalid!'); + + expect(() => { + params.setTinyInt(['not number']); + }).toThrow('SetTinyIntColumn params is invalid!'); + + expect(() => { + params.setBigint(['not bigint']); + }).toThrow('SetTinyIntColumn params is invalid!'); + await connector.close(); +}); + + +afterAll(async () => { + let conf :WSConfig = new WSConfig(dsn) + let ws = await WsSql.open(conf); + await ws.exec(dropDB); + await ws.close(); + WebSocketConnectionPool.instance().destroyed() +}) \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts similarity index 82% rename from nodejs/test/bulkPulling/stmt.func.test.ts rename to nodejs/test/bulkPulling/stmt2.func.test.ts index 85e3f7d1..138c493d 100644 --- a/nodejs/test/bulkPulling/stmt.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -2,9 +2,10 @@ import { WebSocketConnectionPool } from "../../src/client/wsConnectorPool"; import { WSConfig } from "../../src/common/config"; import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; +import { WsStmt2 } from "../../src/stmt/wsStmt2"; import { Sleep } from "../utils"; -let dns = 'ws://192.168.2.156:6041' +let dns = 'ws://localhost:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); @@ -13,6 +14,31 @@ beforeAll(async () => { let wsSql = await WsSql.open(conf); await wsSql.exec('create database if not exists power_stmt KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); await wsSql.exec('CREATE STABLE if not exists power_stmt.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + await wsSql.exec('CREATE STABLE if not exists power_stmt.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + let insertQuery = "INSERT INTO " + + "power_stmt.d1001 USING power_stmt.query_meters TAGS('California.SanFrancisco', 1) " + + "VALUES " + + "('2024-12-19 19:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 19:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 19:12:47.642', 92.30000, 203, 0.31000) " + + "('2024-12-19 18:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 18:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 18:12:47.642', 92.30000, 203, 0.31000) " + + "('2024-12-19 17:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 17:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 17:12:47.642', 92.30000, 203, 0.31000) " + + "power_stmt.d1002 USING power_stmt.query_meters TAGS('Alabama.Montgomery', 2) " + + "VALUES " + + "('2024-12-19 19:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 19:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 19:12:47.642', 72.30000, 206, 0.31000) " + + "('2024-12-19 18:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 18:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 18:12:47.642', 72.30000, 206, 0.31000) " + + "('2024-12-19 17:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 17:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; + await wsSql.exec(insertQuery); await wsSql.close(); }) describe('TDWebSocket.Stmt()', () => { @@ -35,7 +61,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.close() await connector.close(); @@ -69,7 +96,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -88,7 +116,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -110,7 +139,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) try{ await stmt.prepare('INSERT ? INTO ? USING meters TAGS (?, ?) VALUES (?, ?, ?, ?)'); @@ -130,7 +160,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) try{ await stmt.prepare('INSERT INTO ? USING meters TAGS (?, ?, ?) VALUES (?, ?, ?, ?)'); @@ -150,7 +181,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('INSERT INTO meters (ts, tbname, current, voltage, phase, location, groupId) VALUES (?, ?, ?, ?, ?, ?, ?)'); let lastTs = 0 for (let i = 0; i < 10; i++) { @@ -186,7 +218,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); await stmt.setTableName('power_stmt.d1001'); @@ -225,7 +258,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); let lastTs = 0 for (let i = 0; i < 10; i++) { @@ -264,20 +298,25 @@ describe('TDWebSocket.Stmt()', () => { conf.setPwd('taosdata') conf.setDb('power_stmt') let connector = await WsSql.open(conf) + let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() - await stmt.prepare('select * from meters where ts > ? and ts < ?'); + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); + await stmt.prepare('select * from query_meters where ts >= ? and ts <= ?'); let dataParams = stmt.newStmtParam() - dataParams.setTimestamp([1709183268565]) - dataParams.setTimestamp([1709183268569]) + dataParams.setTimestamp([1734599565642]) + dataParams.setTimestamp([1734606767642]) await stmt.bind(dataParams) await stmt.exec() let wsRows = await stmt.resultSet() + let nRows = 0; while (await wsRows.next()) { - let result = await wsRows.getData(); + let result = wsRows.getData(); console.log(result) expect(result).toBeTruthy() + nRows++; } + expect(nRows).toEqual(18); await wsRows.close() await stmt.close() await connector.close(); @@ -291,7 +330,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -329,7 +369,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -366,7 +407,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -421,7 +463,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -450,7 +493,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1002'); @@ -482,7 +526,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_stmt') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); @@ -510,11 +555,11 @@ describe('TDWebSocket.Stmt()', () => { }) afterAll(async () => { - // let conf :WSConfig = new WSConfig(dns); - // conf.setUser('root'); - // conf.setPwd('taosdata'); - // let wsSql = await WsSql.open(conf); - // await wsSql.exec('drop database power_stmt'); - // await wsSql.close(); + let conf :WSConfig = new WSConfig(dns); + conf.setUser('root'); + conf.setPwd('taosdata'); + let wsSql = await WsSql.open(conf); + await wsSql.exec('drop database power_stmt'); + await wsSql.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt.type.test.ts b/nodejs/test/bulkPulling/stmt2.type.test.ts similarity index 95% rename from nodejs/test/bulkPulling/stmt.type.test.ts rename to nodejs/test/bulkPulling/stmt2.type.test.ts index 88fdb7a4..be06d074 100644 --- a/nodejs/test/bulkPulling/stmt.type.test.ts +++ b/nodejs/test/bulkPulling/stmt2.type.test.ts @@ -2,6 +2,7 @@ import { WebSocketConnectionPool } from "../../src/client/wsConnectorPool"; import { WSConfig } from "../../src/common/config"; import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; +import { WsStmt2 } from "../../src/stmt/wsStmt2"; import { createBaseSTable, createBaseSTableJSON, createSTableJSON, getInsertBind } from "../utils"; const stable = 'ws_stmt_stb'; @@ -83,7 +84,7 @@ const selectTableCN = `select * from ${tableCN}` const selectJsonTable = `select * from ${jsonTable}` const selectJsonTableCN = `select * from ${jsonTableCN}` -let dsn = 'ws://root:taosdata@192.168.2.156:6041'; +let dsn = 'ws://root:taosdata@localhost:6041'; setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dsn) @@ -103,7 +104,8 @@ describe('TDWebSocket.Stmt()', () => { wsConf.setDb(db) let connector = await WsSql.open(wsConf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare(getInsertBind(tableValues.length + 2, stableTags.length, db, stable)); await stmt.setTableName(table); @@ -159,7 +161,8 @@ describe('TDWebSocket.Stmt()', () => { wsConf.setDb(db) let connector = await WsSql.open(wsConf) let stmt = await (await connector).stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare(getInsertBind(tableValues.length + 2, stableTags.length, db, stable)); await stmt.setTableName(table); @@ -215,7 +218,8 @@ describe('TDWebSocket.Stmt()', () => { wsConf.setDb(db) let connector = await WsSql.open(wsConf) let stmt = await (await connector).stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare(getInsertBind(tableValues.length + 2, jsonTags.length, db, jsonTable)); await stmt.setTableName(`${jsonTable}_001`); @@ -258,7 +262,8 @@ describe('TDWebSocket.Stmt()', () => { wsConf.setDb(db) let connector = await WsSql.open(wsConf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare(getInsertBind(tableValues.length + 2, jsonTags.length, db, jsonTable)); await stmt.setTableName(`${jsonTable}_001`); @@ -302,6 +307,7 @@ test('test bind exception cases', async() => { let wsConf = new WSConfig(dsn); let connector = await WsSql.open(wsConf) let stmt = await connector.stmtInit() + expect(stmt).toBeInstanceOf(WsStmt2); const params = stmt.newStmtParam(); const emptyArrayMethods = [ @@ -335,23 +341,26 @@ test('test bind exception cases', async() => { expect(() => { params.setBoolean(['not boolean']); + params.encode(); }).toThrow('SetTinyIntColumn params is invalid!'); expect(() => { params.setTinyInt(['not number']); + params.encode(); }).toThrow('SetTinyIntColumn params is invalid!'); expect(() => { params.setBigint(['not bigint']); + params.encode(); }).toThrow('SetTinyIntColumn params is invalid!'); await connector.close(); }); afterAll(async () => { - // let conf :WSConfig = new WSConfig(dsn) - // let ws = await WsSql.open(conf); - // await ws.exec(dropDB); - // await ws.close(); + let conf :WSConfig = new WSConfig(dsn) + let ws = await WsSql.open(conf); + await ws.exec(dropDB); + await ws.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file From fd10399c9f9363fff3699408bd43512bc760a421 Mon Sep 17 00:00:00 2001 From: menshibin Date: Fri, 12 Sep 2025 15:22:22 +0800 Subject: [PATCH 06/23] feat: resolve conflicts in CI database creation --- nodejs/src/stmt/wsStmt.ts | 1 + nodejs/src/stmt/wsStmt2.ts | 4 ++ nodejs/test/bulkPulling/sql.test.ts | 3 ++ nodejs/test/bulkPulling/stmt1.func.test.ts | 35 ++++++------- nodejs/test/bulkPulling/stmt1.type.test.ts | 2 +- nodejs/test/bulkPulling/stmt2.func.test.ts | 49 ++++++++++--------- nodejs/test/bulkPulling/stmt2.type.test.ts | 2 +- nodejs/test/bulkPulling/wsConnectPool.test.ts | 2 +- 8 files changed, 54 insertions(+), 44 deletions(-) diff --git a/nodejs/src/stmt/wsStmt.ts b/nodejs/src/stmt/wsStmt.ts index c1401bbe..87000192 100644 --- a/nodejs/src/stmt/wsStmt.ts +++ b/nodejs/src/stmt/wsStmt.ts @@ -2,6 +2,7 @@ import { WSRows } from "../sql/wsRows"; import { StmtBindParams } from "./wsParamsBase"; export interface WsStmt { + getStmtId(): bigint | undefined | null prepare(sql: string): Promise; setTableName(tableName: string): Promise; setTags(paramsArray:StmtBindParams): Promise; diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 73d726f6..954dad4e 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -79,6 +79,10 @@ export class WsStmt2 implements WsStmt{ throw(new TDWebSocketClientError(ErrorCode.ERR_CONNECTION_CLOSED, "stmt connect closed")); } + getStmtId(): bigint | undefined | null { + return this._stmt_id; + } + async prepare(sql: string): Promise { let queryMsg = { action: 'stmt2_prepare', diff --git a/nodejs/test/bulkPulling/sql.test.ts b/nodejs/test/bulkPulling/sql.test.ts index 3fa8f930..d33b80ca 100644 --- a/nodejs/test/bulkPulling/sql.test.ts +++ b/nodejs/test/bulkPulling/sql.test.ts @@ -13,6 +13,8 @@ beforeAll(async () => { conf.setUser('root') conf.setPwd('taosdata') let wsSql = await WsSql.open(conf) + await wsSql.exec('drop database if exists sql_test'); + await wsSql.exec('drop database if exists sql_create') await wsSql.exec(`CREATE USER user1 PASS '${password1}'`); await wsSql.exec(`CREATE USER user2 PASS '${password2}'`); await wsSql.exec('create database if not exists sql_test KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); @@ -217,6 +219,7 @@ afterAll(async () => { conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); await wsSql.exec('drop database sql_test'); + await wsSql.exec('drop database sql_create'); await wsSql.exec('DROP USER user1;') await wsSql.exec('DROP USER user2;') await wsSql.close(); diff --git a/nodejs/test/bulkPulling/stmt1.func.test.ts b/nodejs/test/bulkPulling/stmt1.func.test.ts index 9d88c58c..ee826dd4 100644 --- a/nodejs/test/bulkPulling/stmt1.func.test.ts +++ b/nodejs/test/bulkPulling/stmt1.func.test.ts @@ -12,8 +12,9 @@ beforeAll(async () => { conf.setUser('root'); conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); - await wsSql.exec('create database if not exists power_stmt KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); - await wsSql.exec('CREATE STABLE if not exists power_stmt.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + await wsSql.exec('drop database if exists power_func_stmt1;'); + await wsSql.exec('create database if not exists power_func_stmt1 KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); + await wsSql.exec('CREATE STABLE if not exists power_func_stmt1.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); await wsSql.close(); }) describe('TDWebSocket.Stmt()', () => { @@ -34,7 +35,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -69,7 +70,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -89,7 +90,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -112,7 +113,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -133,7 +134,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -154,13 +155,13 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt1); await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); - await stmt.setTableName('power_stmt.d1001'); + await stmt.setTableName('power_func_stmt1.d1001'); let params = stmt.newStmtParam() params.setVarchar(['SanFrancisco']); @@ -194,7 +195,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -233,7 +234,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -271,7 +272,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -327,7 +328,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -357,7 +358,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -379,7 +380,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.batch() await stmt.exec() - let result = await connector.exec("select * from power_stmt.meters") + let result = await connector.exec("select * from power_func_stmt1.meters") console.log(result) await stmt.close() await connector.close(); @@ -390,7 +391,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns, "100.100.100.100"); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt1') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -426,7 +427,7 @@ afterAll(async () => { conf.setUser('root'); conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); - await wsSql.exec('drop database power_stmt'); + await wsSql.exec('drop database power_func_stmt1'); await wsSql.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt1.type.test.ts b/nodejs/test/bulkPulling/stmt1.type.test.ts index f4958824..a15111fb 100644 --- a/nodejs/test/bulkPulling/stmt1.type.test.ts +++ b/nodejs/test/bulkPulling/stmt1.type.test.ts @@ -7,7 +7,7 @@ import { createBaseSTable, createBaseSTableJSON, createSTableJSON, getInsertBind const stable = 'ws_stmt_stb'; const table = 'stmt_001'; -const db = 'ws_stmt' +const db = 'ws_stmt1' const createDB = `create database if not exists ${db} keep 3650` const useDB = `use ${db}` const dropDB = `drop database if exists ${db}` diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 138c493d..154bb0f8 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -12,11 +12,12 @@ beforeAll(async () => { conf.setUser('root'); conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); - await wsSql.exec('create database if not exists power_stmt KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); - await wsSql.exec('CREATE STABLE if not exists power_stmt.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); - await wsSql.exec('CREATE STABLE if not exists power_stmt.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + await wsSql.exec('drop database if exists power_func_stmt2;'); + await wsSql.exec('create database if not exists power_func_stmt2 KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); + await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); let insertQuery = "INSERT INTO " + - "power_stmt.d1001 USING power_stmt.query_meters TAGS('California.SanFrancisco', 1) " + + "power_func_stmt2.d1001 USING power_func_stmt2.query_meters TAGS('California.SanFrancisco', 1) " + "VALUES " + "('2024-12-19 19:12:45.642', 50.30000, 201, 0.31000) " + "('2024-12-19 19:12:46.642', 82.60000, 202, 0.33000) " + @@ -27,7 +28,7 @@ beforeAll(async () => { "('2024-12-19 17:12:45.642', 50.30000, 201, 0.31000) " + "('2024-12-19 17:12:46.642', 82.60000, 202, 0.33000) " + "('2024-12-19 17:12:47.642', 92.30000, 203, 0.31000) " + - "power_stmt.d1002 USING power_stmt.query_meters TAGS('Alabama.Montgomery', 2) " + + "power_func_stmt2.d1002 USING power_func_stmt2.query_meters TAGS('Alabama.Montgomery', 2) " + "VALUES " + "('2024-12-19 19:12:45.642', 50.30000, 204, 0.25000) " + "('2024-12-19 19:12:46.642', 62.60000, 205, 0.33000) " + @@ -58,7 +59,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -93,7 +94,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -113,7 +114,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -136,7 +137,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -157,7 +158,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -178,7 +179,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -215,13 +216,13 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) (ts, current, voltage, phase) VALUES (?, ?, ?, ?)'); - await stmt.setTableName('power_stmt.d1001'); + await stmt.setTableName('power_func_stmt2.d1001'); let params = stmt.newStmtParam() params.setVarchar(['SanFrancisco']); @@ -255,7 +256,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -267,7 +268,7 @@ describe('TDWebSocket.Stmt()', () => { multi[0][j] = multi[0][0] + j; lastTs = multi[0][j] } - await stmt.setTableName(`power_stmt.d100${i+1}`); + await stmt.setTableName(`power_func_stmt2.d100${i+1}`); let params = stmt.newStmtParam() params.setVarchar([`SanFrancisco${i+1}`]); @@ -296,7 +297,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() @@ -327,7 +328,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -366,7 +367,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -404,7 +405,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -460,7 +461,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -490,7 +491,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -512,7 +513,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.batch() await stmt.exec() - let result = await connector.exec("select * from power_stmt.meters") + let result = await connector.exec("select * from power_func_stmt2.meters") console.log(result) await stmt.close() await connector.close(); @@ -523,7 +524,7 @@ describe('TDWebSocket.Stmt()', () => { let conf = new WSConfig(dns); conf.setUser('root') conf.setPwd('taosdata') - conf.setDb('power_stmt') + conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() @@ -559,7 +560,7 @@ afterAll(async () => { conf.setUser('root'); conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); - await wsSql.exec('drop database power_stmt'); + await wsSql.exec('drop database power_func_stmt2'); await wsSql.close(); WebSocketConnectionPool.instance().destroyed() }) \ No newline at end of file diff --git a/nodejs/test/bulkPulling/stmt2.type.test.ts b/nodejs/test/bulkPulling/stmt2.type.test.ts index be06d074..fb763270 100644 --- a/nodejs/test/bulkPulling/stmt2.type.test.ts +++ b/nodejs/test/bulkPulling/stmt2.type.test.ts @@ -7,7 +7,7 @@ import { createBaseSTable, createBaseSTableJSON, createSTableJSON, getInsertBind const stable = 'ws_stmt_stb'; const table = 'stmt_001'; -const db = 'ws_stmt' +const db = 'ws_stmt2' const createDB = `create database if not exists ${db} keep 3650` const useDB = `use ${db}` const dropDB = `drop database if exists ${db}` diff --git a/nodejs/test/bulkPulling/wsConnectPool.test.ts b/nodejs/test/bulkPulling/wsConnectPool.test.ts index 82644039..36067df2 100644 --- a/nodejs/test/bulkPulling/wsConnectPool.test.ts +++ b/nodejs/test/bulkPulling/wsConnectPool.test.ts @@ -48,7 +48,7 @@ async function connect() { async function stmtConnect() { let dsn = 'ws://root:taosdata@localhost:6041'; - let wsConf = new WSConfig(dsn); + let wsConf = new WSConfig(dsn, "100.100.100.100"); wsConf.setDb(db) // let connector = WsStmtConnect.NewConnector(wsConf) // let stmt = await connector.Init() From 4664e7ecc49ac881049e1a5efbf970891c90f3bc Mon Sep 17 00:00:00 2001 From: menshibin Date: Fri, 12 Sep 2025 17:44:23 +0800 Subject: [PATCH 07/23] feat: modify stmt2 test case error --- nodejs/test/bulkPulling/stmt2.func.test.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 154bb0f8..f2a21d01 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -3,21 +3,19 @@ import { WSConfig } from "../../src/common/config"; import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -import { Sleep } from "../utils"; -let dns = 'ws://localhost:6041' +let dns = 'ws://192.168.2.156:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); conf.setUser('root'); conf.setPwd('taosdata'); let wsSql = await WsSql.open(conf); - await wsSql.exec('drop database if exists power_func_stmt2;'); await wsSql.exec('create database if not exists power_func_stmt2 KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); let insertQuery = "INSERT INTO " + - "power_func_stmt2.d1001 USING power_func_stmt2.query_meters TAGS('California.SanFrancisco', 1) " + + "power_func_stmt2.q1001 USING power_func_stmt2.query_meters TAGS('California.SanFrancisco', 1) " + "VALUES " + "('2024-12-19 19:12:45.642', 50.30000, 201, 0.31000) " + "('2024-12-19 19:12:46.642', 82.60000, 202, 0.33000) " + @@ -28,7 +26,7 @@ beforeAll(async () => { "('2024-12-19 17:12:45.642', 50.30000, 201, 0.31000) " + "('2024-12-19 17:12:46.642', 82.60000, 202, 0.33000) " + "('2024-12-19 17:12:47.642', 92.30000, 203, 0.31000) " + - "power_func_stmt2.d1002 USING power_func_stmt2.query_meters TAGS('Alabama.Montgomery', 2) " + + "power_func_stmt2.q1002 USING power_func_stmt2.query_meters TAGS('Alabama.Montgomery', 2) " + "VALUES " + "('2024-12-19 19:12:45.642', 50.30000, 204, 0.25000) " + "('2024-12-19 19:12:46.642', 62.60000, 205, 0.33000) " + @@ -198,7 +196,7 @@ describe('TDWebSocket.Stmt()', () => { dataParams.setFloat(multi[1]) dataParams.setInt(multi[2]) dataParams.setFloat(multi[3]) - dataParams.setVarchar(['SanFrancisco_1', 'SanFrancisco_2', 'SanFrancisco_3']); + dataParams.setVarchar(['SanFrancisco_1', 'SanFrancisco_2','SanFrancisco_3']); dataParams.setInt([1, 2, 3]); await stmt.bind(dataParams) multi[0][0] = lastTs + 1; @@ -300,6 +298,9 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) + + + let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); From 838b98833d121c0235b7865410c2472023e8b055 Mon Sep 17 00:00:00 2001 From: menshibin Date: Fri, 12 Sep 2025 17:55:46 +0800 Subject: [PATCH 08/23] feat: modify stmt2 test case ip --- nodejs/test/bulkPulling/stmt2.func.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index f2a21d01..73c19e5a 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -let dns = 'ws://192.168.2.156:6041' +let dns = 'ws://localhost:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); From a661050d4064ecc9c52969f175841e2b888ec41e Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 10:21:59 +0800 Subject: [PATCH 09/23] feat: modify stmt2 case errors --- nodejs/test/bulkPulling/sql.test.ts | 2 +- nodejs/test/bulkPulling/stmt2.func.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nodejs/test/bulkPulling/sql.test.ts b/nodejs/test/bulkPulling/sql.test.ts index d33b80ca..3b10b79e 100644 --- a/nodejs/test/bulkPulling/sql.test.ts +++ b/nodejs/test/bulkPulling/sql.test.ts @@ -92,7 +92,7 @@ describe('TDWebSocket.WsSql()', () => { }) test('connect url', async() => { - let url = 'ws://root:taosdata@192.1682.156:6041/information_schema?timezone=Asia/Shanghai' + let url = 'ws://root:taosdata@localhost:6041/information_schema?timezone=Asia/Shanghai' let conf :WSConfig = new WSConfig(url) let wsSql = await WsSql.open(conf) let version = await wsSql.version() diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 73c19e5a..311de7de 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -let dns = 'ws://localhost:6041' +let dns = 'ws://192.168.2.156:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); @@ -330,7 +330,7 @@ describe('TDWebSocket.Stmt()', () => { conf.setUser('root') conf.setPwd('taosdata') conf.setDb('power_func_stmt2') - let connector = await WsSql.open(conf) + let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); @@ -371,8 +371,8 @@ describe('TDWebSocket.Stmt()', () => { conf.setDb('power_func_stmt2') let connector = await WsSql.open(conf) let stmt = await connector.stmtInit() - expect(stmt).toBeTruthy() - expect(stmt).toBeInstanceOf(WsStmt2); + expect(stmt).toBeTruthy() + expect(stmt).toBeInstanceOf(WsStmt2); expect(connector.state()).toBeGreaterThan(0) await stmt.prepare('INSERT INTO ? USING meters (location, groupId) TAGS (?, ?) VALUES (?, ?, ?, ?)'); await stmt.setTableName('d1001'); From 224d328657a1e785c0b111f3bc18e981bfd70c94 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 10:48:07 +0800 Subject: [PATCH 10/23] feat: modify stmt2 case url --- nodejs/test/bulkPulling/stmt2.func.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 311de7de..35e99804 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -let dns = 'ws://192.168.2.156:6041' +let dns = 'ws://localhost:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); From 56a00044be220742d891f8aa668fe9ab6b3bea71 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 11:06:13 +0800 Subject: [PATCH 11/23] feat: modify stmt2 test query case --- nodejs/test/bulkPulling/stmt2.func.test.ts | 64 +++++++++++----------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 35e99804..90b8128f 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -let dns = 'ws://localhost:6041' +let dns = 'ws://192.168.2.156:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); @@ -13,31 +13,6 @@ beforeAll(async () => { let wsSql = await WsSql.open(conf); await wsSql.exec('create database if not exists power_func_stmt2 KEEP 3650 DURATION 10 BUFFER 16 WAL_LEVEL 1;'); await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); - await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); - let insertQuery = "INSERT INTO " + - "power_func_stmt2.q1001 USING power_func_stmt2.query_meters TAGS('California.SanFrancisco', 1) " + - "VALUES " + - "('2024-12-19 19:12:45.642', 50.30000, 201, 0.31000) " + - "('2024-12-19 19:12:46.642', 82.60000, 202, 0.33000) " + - "('2024-12-19 19:12:47.642', 92.30000, 203, 0.31000) " + - "('2024-12-19 18:12:45.642', 50.30000, 201, 0.31000) " + - "('2024-12-19 18:12:46.642', 82.60000, 202, 0.33000) " + - "('2024-12-19 18:12:47.642', 92.30000, 203, 0.31000) " + - "('2024-12-19 17:12:45.642', 50.30000, 201, 0.31000) " + - "('2024-12-19 17:12:46.642', 82.60000, 202, 0.33000) " + - "('2024-12-19 17:12:47.642', 92.30000, 203, 0.31000) " + - "power_func_stmt2.q1002 USING power_func_stmt2.query_meters TAGS('Alabama.Montgomery', 2) " + - "VALUES " + - "('2024-12-19 19:12:45.642', 50.30000, 204, 0.25000) " + - "('2024-12-19 19:12:46.642', 62.60000, 205, 0.33000) " + - "('2024-12-19 19:12:47.642', 72.30000, 206, 0.31000) " + - "('2024-12-19 18:12:45.642', 50.30000, 204, 0.25000) " + - "('2024-12-19 18:12:46.642', 62.60000, 205, 0.33000) " + - "('2024-12-19 18:12:47.642', 72.30000, 206, 0.31000) " + - "('2024-12-19 17:12:45.642', 50.30000, 204, 0.25000) " + - "('2024-12-19 17:12:46.642', 62.60000, 205, 0.33000) " + - "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; - await wsSql.exec(insertQuery); await wsSql.close(); }) describe('TDWebSocket.Stmt()', () => { @@ -296,12 +271,35 @@ describe('TDWebSocket.Stmt()', () => { conf.setUser('root') conf.setPwd('taosdata') conf.setDb('power_func_stmt2') - let connector = await WsSql.open(conf) - - - - - let stmt = await connector.stmtInit() + let wsSql = await WsSql.open(conf) + + await wsSql.exec('CREATE STABLE if not exists power_func_stmt2.query_meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);'); + let insertQuery = "INSERT INTO " + + "power_func_stmt2.q1001 USING power_func_stmt2.query_meters TAGS('California.SanFrancisco', 1) " + + "VALUES " + + "('2024-12-19 19:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 19:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 19:12:47.642', 92.30000, 203, 0.31000) " + + "('2024-12-19 18:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 18:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 18:12:47.642', 92.30000, 203, 0.31000) " + + "('2024-12-19 17:12:45.642', 50.30000, 201, 0.31000) " + + "('2024-12-19 17:12:46.642', 82.60000, 202, 0.33000) " + + "('2024-12-19 17:12:47.642', 92.30000, 203, 0.31000) " + + "power_func_stmt2.q1002 USING power_func_stmt2.query_meters TAGS('Alabama.Montgomery', 2) " + + "VALUES " + + "('2024-12-19 19:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 19:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 19:12:47.642', 72.30000, 206, 0.31000) " + + "('2024-12-19 18:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 18:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 18:12:47.642', 72.30000, 206, 0.31000) " + + "('2024-12-19 17:12:45.642', 50.30000, 204, 0.25000) " + + "('2024-12-19 17:12:46.642', 62.60000, 205, 0.33000) " + + "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; + await wsSql.exec(insertQuery); + + let stmt = await wsSql.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('select * from query_meters where ts >= ? and ts <= ?'); @@ -321,7 +319,7 @@ describe('TDWebSocket.Stmt()', () => { expect(nRows).toEqual(18); await wsRows.close() await stmt.close() - await connector.close(); + await wsSql.close(); }); From 8416e0a0dd6a655a3f104592cbc763119e078264 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 11:06:33 +0800 Subject: [PATCH 12/23] feat: modify stmt2 test query case url --- nodejs/test/bulkPulling/stmt2.func.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 90b8128f..931b6ddd 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -4,7 +4,7 @@ import { setLevel } from "../../src/common/log"; import { WsSql } from "../../src/sql/wsSql"; import { WsStmt2 } from "../../src/stmt/wsStmt2"; -let dns = 'ws://192.168.2.156:6041' +let dns = 'ws://localhost:6041' setLevel("debug") beforeAll(async () => { let conf :WSConfig = new WSConfig(dns); @@ -298,7 +298,7 @@ describe('TDWebSocket.Stmt()', () => { "('2024-12-19 17:12:46.642', 62.60000, 205, 0.33000) " + "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; await wsSql.exec(insertQuery); - + let stmt = await wsSql.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); From 89c20f3820ec885e4d2145fc601be03f61a779cf Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 11:23:27 +0800 Subject: [PATCH 13/23] feat: add stmt2 query case debug info --- nodejs/test/bulkPulling/stmt2.func.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 931b6ddd..d5e30fb4 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -299,6 +299,9 @@ describe('TDWebSocket.Stmt()', () => { "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; await wsSql.exec(insertQuery); + let result = await wsSql.exec("select * from query_meters") + console.log(result) + let stmt = await wsSql.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); From 6709a5071cd4b138601ae3baeb9c67a7560bee0a Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 15 Sep 2025 11:40:11 +0800 Subject: [PATCH 14/23] feat: modify stmt2 test query case tim --- nodejs/test/bulkPulling/stmt2.func.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index d5e30fb4..d1b088a3 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -299,16 +299,16 @@ describe('TDWebSocket.Stmt()', () => { "('2024-12-19 17:12:47.642', 72.30000, 206, 0.31000) "; await wsSql.exec(insertQuery); - let result = await wsSql.exec("select * from query_meters") - console.log(result) + // let result = await wsSql.exec("select * from query_meters") + // console.log(result) let stmt = await wsSql.stmtInit() expect(stmt).toBeTruthy() expect(stmt).toBeInstanceOf(WsStmt2); await stmt.prepare('select * from query_meters where ts >= ? and ts <= ?'); let dataParams = stmt.newStmtParam() - dataParams.setTimestamp([1734599565642]) - dataParams.setTimestamp([1734606767642]) + dataParams.setTimestamp([1734628365642n]) + dataParams.setTimestamp([1734635567642n]) await stmt.bind(dataParams) await stmt.exec() let wsRows = await stmt.resultSet() From 92bcd16785888671d6707ede13cc9e85e1d4bd78 Mon Sep 17 00:00:00 2001 From: menshibin Date: Fri, 19 Sep 2025 17:59:25 +0800 Subject: [PATCH 15/23] feat: clean up memory data after stmt2 execution --- nodejs/src/stmt/wsStmt2.ts | 11 +++++++++-- nodejs/test/bulkPulling/stmt2.func.test.ts | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 954dad4e..451ad1a4 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -227,6 +227,13 @@ export class WsStmt2 implements WsStmt{ }, }; await this.execute(execMsg); + this.cleanup(); + } + + private cleanup() { + this._stmtTableInfo.clear(); + this._stmtTableInfoList = []; + this._currentTableInfo = new TableInfo(); } getLastAffected(): number | null | undefined { @@ -282,11 +289,11 @@ export class WsStmt2 implements WsStmt{ if (resp.fields) { this.fields = resp.fields; } + if (resp.is_insert) { this._isInsert = resp.is_insert; - } else { - this._toBeBindColCount = resp.fields_count ? resp.fields_count : 0; } + return resp; } diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index d1b088a3..ff8e95da 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -505,6 +505,7 @@ describe('TDWebSocket.Stmt()', () => { params.setVarchar(['SanFrancisco']); params.setInt([7]); await stmt.setTags(params) + let dataParams = stmt.newStmtParam() dataParams.setTimestamp(multi[0]) dataParams.setFloat(multi[1]) From 7a9758cbb2c74da07dfa11f03d8e504d1ad2451b Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 22 Sep 2025 11:02:54 +0800 Subject: [PATCH 16/23] feat: improve stmt query parameters --- nodejs/src/stmt/wsStmt2.ts | 14 ++++++++------ nodejs/test/bulkPulling/stmt2.func.test.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 451ad1a4..13c2a3bb 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -93,8 +93,7 @@ export class WsStmt2 implements WsStmt{ get_fields: true, }, }; - await this.execute(queryMsg); - + let resp = await this.execute(queryMsg); if (this._isInsert && this.fields) { this._precision = this.fields[0].precision ? this.fields[0].precision : 0; this._toBeBindColCount = 0; @@ -108,11 +107,14 @@ export class WsStmt2 implements WsStmt{ this._toBeBindColCount++; } }); - } else { - this._stmtTableInfoList = [this._currentTableInfo]; + } else { + if (resp && resp.fields_count && resp.fields_count > 0) { + this._stmtTableInfoList = [this._currentTableInfo]; + this._toBeBindColCount = resp.fields_count; + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "prepare No columns to bind!"); + } } - - } async setTableName(tableName: string): Promise { diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index ff8e95da..300a1591 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -505,7 +505,7 @@ describe('TDWebSocket.Stmt()', () => { params.setVarchar(['SanFrancisco']); params.setInt([7]); await stmt.setTags(params) - + let dataParams = stmt.newStmtParam() dataParams.setTimestamp(multi[0]) dataParams.setFloat(multi[1]) From f3c8256e2d4fdded718813d2eb655f407a9bae76 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 22 Sep 2025 13:52:58 +0800 Subject: [PATCH 17/23] feat: improve stmt2 common issues --- nodejs/src/common/constant.ts | 30 +++++++++++++++++------------- nodejs/src/common/utils.ts | 2 +- nodejs/src/stmt/wsParams1.ts | 26 +++++++++++++------------- nodejs/src/stmt/wsParams2.ts | 2 +- nodejs/src/stmt/wsParamsBase.ts | 26 +++++++++++++------------- nodejs/src/stmt/wsProto.ts | 18 +++++++++--------- nodejs/src/stmt/wsStmt2.ts | 5 +---- nodejs/src/tmq/tmqResponse.ts | 2 +- 8 files changed, 56 insertions(+), 55 deletions(-) diff --git a/nodejs/src/common/constant.ts b/nodejs/src/common/constant.ts index 8e446305..3b0f1892 100644 --- a/nodejs/src/common/constant.ts +++ b/nodejs/src/common/constant.ts @@ -6,6 +6,10 @@ export interface StringIndexable { [index: string]: number } +export interface NumberIndexable { + [index: number]: number +} + export const BinaryQueryMessage: bigint = BigInt(6); export const FetchRawBlockMessage: bigint = BigInt(7); export const MinStmt2Version:string = "3.3.6.0"; @@ -83,19 +87,19 @@ export const TDenginePrecision: IndexableString = { 2: "NANOSECOND", } -export const TDengineTypeLength: StringIndexable = { - 'BOOL': 1, - 'TINYINT': 1, - 'SMALLINT': 2, - 'INT': 4, - 'BIGINT': 8, - 'FLOAT': 4, - 'DOUBLE': 8, - 'TIMESTAMP': 8, - 'TINYINT UNSIGNED': 1, - 'SMALLINT UNSIGNED': 2, - 'INT UNSIGNED': 4, - 'BIGINT UNSIGNED': 8, +export const TDengineTypeLength: NumberIndexable = { + [TDengineTypeCode.BOOL]: 1, + [TDengineTypeCode.TINYINT]: 1, + [TDengineTypeCode.SMALLINT]: 2, + [TDengineTypeCode.INT]: 4, + [TDengineTypeCode.BIGINT]: 8, + [TDengineTypeCode.FLOAT]: 4, + [TDengineTypeCode.DOUBLE]: 8, + [TDengineTypeCode.TIMESTAMP]: 8, + [TDengineTypeCode.TINYINT_UNSIGNED]: 1, + [TDengineTypeCode.SMALLINT_UNSIGNED]: 2, + [TDengineTypeCode.INT_UNSIGNED]: 4, + [TDengineTypeCode.BIGINT_UNSIGNED]: 8, } export const PrecisionLength: StringIndexable = { diff --git a/nodejs/src/common/utils.ts b/nodejs/src/common/utils.ts index e483f703..36dc7c84 100644 --- a/nodejs/src/common/utils.ts +++ b/nodejs/src/common/utils.ts @@ -177,7 +177,7 @@ export function decimalToString(valueStr: string, fields_scale: bigint | null): return decimalStr; } -export const shotToBytes = (value: number): number[] => { +export const shortToBytes = (value: number): number[] => { const buffer = new ArrayBuffer(2); const view = new DataView(buffer); view.setUint16(0, value, true); diff --git a/nodejs/src/stmt/wsParams1.ts b/nodejs/src/stmt/wsParams1.ts index 327a461a..7486bf5c 100644 --- a/nodejs/src/stmt/wsParams1.ts +++ b/nodejs/src/stmt/wsParams1.ts @@ -42,7 +42,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ //computing bitmap length let bitMapLen:number = bitmapLen(params.length) //Computing the length of data - let arrayBuffer = new ArrayBuffer(bitMapLen + TDengineTypeLength['TIMESTAMP'] * params.length); + let arrayBuffer = new ArrayBuffer(bitMapLen + TDengineTypeLength[TDengineTypeCode.TIMESTAMP] * params.length); //bitmap get data range let bitmapBuffer = new DataView(arrayBuffer) //skip bitmap get data range @@ -102,7 +102,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ } this._dataTotalLen += arrayBuffer.byteLength; - this._params.push(new ColumnInfo([TDengineTypeLength['TIMESTAMP'] * params.length, arrayBuffer], TDengineTypeCode.TIMESTAMP, TDengineTypeLength['TIMESTAMP'], this._rows)); + this._params.push(new ColumnInfo([TDengineTypeLength[TDengineTypeCode.TIMESTAMP] * params.length, arrayBuffer], TDengineTypeCode.TIMESTAMP, TDengineTypeLength[TDengineTypeCode.TIMESTAMP], this._rows)); } @@ -137,7 +137,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ let data:ArrayBuffer[] = [] let dataLength = 0; //create params length buffer - let paramsLenBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) + let paramsLenBuffer = new ArrayBuffer(TDengineTypeLength[TDengineTypeCode.INT] * params.length) let paramsLenView = new DataView(paramsLenBuffer) if (this._rows > 0) { if (this._rows !== params.length) { @@ -148,7 +148,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ } for (let i = 0; i < params.length; i++) { //get param length offset 4byte - let offset = TDengineTypeLength['INT'] * i; + let offset = TDengineTypeLength[TDengineTypeCode.INT] * i; if (!isEmpty(params[i])) { //save param length offset 4byte paramsLenView.setInt32(offset, dataLength, true); @@ -158,11 +158,11 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ let value = encode.encode(params[i]).buffer; data.push(value); //add offset length - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + dataLength += value.byteLength + TDengineTypeLength[TDengineTypeCode.SMALLINT]; } else if (params[i] instanceof ArrayBuffer) { //input arraybuffer, save not need encode let value:ArrayBuffer = params[i]; - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + dataLength += value.byteLength + TDengineTypeLength[TDengineTypeCode.SMALLINT]; data.push(value); } else { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, @@ -171,7 +171,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ }else{ //set length -1, param is null - for (let j = 0; j < TDengineTypeLength['INT']; j++) { + for (let j = 0; j < TDengineTypeLength[TDengineTypeCode.INT]; j++) { paramsLenView.setInt8(offset+j, 255); } @@ -210,7 +210,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ private encodeNcharColumn(params:any[]):ColumnInfo { let data:ArrayBuffer[] = [] let dataLength = 0; - let indexBuffer = new ArrayBuffer(TDengineTypeLength['INT'] * params.length) + let indexBuffer = new ArrayBuffer(TDengineTypeLength[TDengineTypeCode.INT] * params.length) let indexView = new DataView(indexBuffer) if (this._rows > 0) { if (this._rows !== params.length) { @@ -221,7 +221,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ } for (let i = 0; i < params.length; i++) { - let offset = TDengineTypeLength['INT'] * i; + let offset = TDengineTypeLength[TDengineTypeCode.INT] * i; if (!isEmpty(params[i])) { indexView.setInt32(offset, dataLength, true); if (typeof params[i] == 'string' ) { @@ -239,11 +239,11 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ ncharView.setUint32(j*4, codes[j], true); } data.push(ncharBuffer); - dataLength += codes.length * 4 + TDengineTypeLength['SMALLINT']; + dataLength += codes.length * 4 + TDengineTypeLength[TDengineTypeCode.SMALLINT]; } else if (params[i] instanceof ArrayBuffer) { let value:ArrayBuffer = params[i] - dataLength += value.byteLength + TDengineTypeLength['SMALLINT']; + dataLength += value.byteLength + TDengineTypeLength[TDengineTypeCode.SMALLINT]; data.push(value); } else { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "getColumString params is invalid! param_type:=" + typeof params[i]) @@ -251,7 +251,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ }else{ //set length -1, param is null - for (let j = 0; j < TDengineTypeLength['INT']; j++) { + for (let j = 0; j < TDengineTypeLength[TDengineTypeCode.INT]; j++) { indexView.setInt8(offset+j, 255) } @@ -259,7 +259,7 @@ export class Stmt1BindParams extends StmtBindParams implements IDataEncoder{ } this._dataTotalLen += indexBuffer.byteLength + dataLength; - return new ColumnInfo([dataLength, this.getBinaryColumnArrayBuffer(data, indexView.buffer, dataLength)], TDengineTypeCode.NCHAR, TDengineTypeLength['NCHAR'], this._rows); + return new ColumnInfo([dataLength, this.getBinaryColumnArrayBuffer(data, indexView.buffer, dataLength)], TDengineTypeCode.NCHAR, 0, this._rows); } private countBigintDigits(numeral: bigint): number { diff --git a/nodejs/src/stmt/wsParams2.ts b/nodejs/src/stmt/wsParams2.ts index e8843c96..a0a55918 100644 --- a/nodejs/src/stmt/wsParams2.ts +++ b/nodejs/src/stmt/wsParams2.ts @@ -1,4 +1,4 @@ -import { ColumnsBlockType, FieldBindType, PrecisionLength, TDengineTypeCode, TDengineTypeLength } from "../common/constant"; +import { ColumnsBlockType, FieldBindType, PrecisionLength } from "../common/constant"; import { ErrorCode, TaosError } from "../common/wsError"; import { isEmpty } from "../common/utils"; import { ColumnInfo } from "./wsColumnInfo"; diff --git a/nodejs/src/stmt/wsParamsBase.ts b/nodejs/src/stmt/wsParamsBase.ts index 2fa1c96e..0f8a0f7b 100644 --- a/nodejs/src/stmt/wsParamsBase.ts +++ b/nodejs/src/stmt/wsParamsBase.ts @@ -72,21 +72,21 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBooleanColumn params is invalid!"); } - this.addParams(params, "boolean", TDengineTypeLength['BOOL'], TDengineTypeCode.BOOL); + this.addParams(params, "boolean", TDengineTypeLength[TDengineTypeCode.BOOL], TDengineTypeCode.BOOL); } setTinyInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetTinyIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['TINYINT'], TDengineTypeCode.TINYINT) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.TINYINT], TDengineTypeCode.TINYINT) } setUTinyInt(params :any[]) { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUTinyIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['TINYINT UNSIGNED'], TDengineTypeCode.TINYINT_UNSIGNED) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.TINYINT_UNSIGNED], TDengineTypeCode.TINYINT_UNSIGNED) } @@ -94,7 +94,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['SMALLINT'], TDengineTypeCode.SMALLINT) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.SMALLINT], TDengineTypeCode.SMALLINT) } @@ -103,7 +103,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetSmallIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['SMALLINT UNSIGNED'], TDengineTypeCode.SMALLINT_UNSIGNED) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.SMALLINT_UNSIGNED], TDengineTypeCode.SMALLINT_UNSIGNED) } @@ -111,7 +111,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['INT'], TDengineTypeCode.INT) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.INT], TDengineTypeCode.INT) } @@ -119,7 +119,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUIntColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['INT UNSIGNED'], TDengineTypeCode.INT_UNSIGNED) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.INT_UNSIGNED], TDengineTypeCode.INT_UNSIGNED) } @@ -127,7 +127,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetBigIntColumn params is invalid!"); } - this.addParams(params, "bigint", TDengineTypeLength['BIGINT'], TDengineTypeCode.BIGINT) + this.addParams(params, "bigint", TDengineTypeLength[TDengineTypeCode.BIGINT], TDengineTypeCode.BIGINT) } @@ -135,7 +135,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetUBigIntColumn params is invalid!"); } - this.addParams(params, "bigint", TDengineTypeLength['BIGINT UNSIGNED'], TDengineTypeCode.BIGINT_UNSIGNED) + this.addParams(params, "bigint", TDengineTypeLength[TDengineTypeCode.BIGINT_UNSIGNED], TDengineTypeCode.BIGINT_UNSIGNED) } @@ -143,7 +143,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetFloatColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['FLOAT'], TDengineTypeCode.FLOAT) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.FLOAT], TDengineTypeCode.FLOAT) } @@ -151,7 +151,7 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SetDoubleColumn params is invalid!"); } - this.addParams(params, "number", TDengineTypeLength['DOUBLE'], TDengineTypeCode.DOUBLE) + this.addParams(params, "number", TDengineTypeLength[TDengineTypeCode.DOUBLE], TDengineTypeCode.DOUBLE) } @@ -205,8 +205,8 @@ export abstract class StmtBindParams { if (!params || params.length == 0) { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "SeTimestampColumn params is invalid!"); } - this.addParams(params, TDengineTypeName[9], TDengineTypeLength['TIMESTAMP'], TDengineTypeCode.TIMESTAMP); - + this.addParams(params, TDengineTypeName[9], TDengineTypeLength[TDengineTypeCode.TIMESTAMP], TDengineTypeCode.TIMESTAMP); + } protected writeDataToBuffer(dataBuffer: DataView, params: any, dataType: string = 'number', typeLen: number, columnType: number, i:number): void { diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 18ca3eb6..3346f1a8 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -1,9 +1,9 @@ import exp from "constants"; import { WSQueryResponse } from "../client/wsResponse"; -import { TDengineTypeLength } from "../common/constant"; +import { TDengineTypeCode, TDengineTypeLength } from "../common/constant"; import { MessageResp } from "../common/taosResult"; import { StmtBindParams } from "./wsParamsBase"; -import { bigintToBytes, intToBytes, shotToBytes } from "../common/utils"; +import { bigintToBytes, intToBytes, shortToBytes } from "../common/utils"; import { ColumnInfo } from "./wsColumnInfo"; import { ErrorCode, TaosResultError } from "../common/wsError"; import { TableInfo } from "./wsTableInfo"; @@ -54,8 +54,8 @@ export const enum StmtBindType { export function binaryBlockEncode(bindParams :StmtBindParams, bindType:StmtBindType, stmtId:bigint, reqId:bigint, row:number): ArrayBuffer { //Computing the length of data let columns = bindParams.getParams().length; - let length = TDengineTypeLength['BIGINT'] * 4; - length += TDengineTypeLength['INT'] * 5; + let length = TDengineTypeLength[TDengineTypeCode.BIGINT] * 4; + length += TDengineTypeLength[TDengineTypeCode.INT] * 5; length += columns * 5 + columns * 4; length += bindParams.getDataTotalLen(); @@ -119,7 +119,7 @@ export function stmt2BinaryBlockEncode(reqId: bigint, if(stmt_id == null || stmt_id == undefined) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "stmt_id is invalid"); } - // cloc totol size + // cloc total size let totalTableNameSize = 0; let tableNameSizeList:Array = []; if (toBeBindTableNameIndex != null && toBeBindTableNameIndex != undefined) { @@ -172,15 +172,15 @@ export function stmt2BinaryBlockEncode(reqId: bigint, + (toBeBindColCount > 0 ? 1 : 0) * 4); const bytes: number[] = []; - // 写入 req_id + // write req_id bytes.push(...bigintToBytes(reqId)); - // 写入 stmt_id + // write stmt_id if (stmt_id) { bytes.push(...bigintToBytes(stmt_id)); } bytes.push(...bigintToBytes(9n)); - bytes.push(...shotToBytes(1)); + bytes.push(...shortToBytes(1)); bytes.push(...intToBytes(-1)); bytes.push(...intToBytes(totalSize + 28, false)); @@ -225,7 +225,7 @@ export function stmt2BinaryBlockEncode(reqId: bigint, if (size === 0) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); } - bytes.push(...shotToBytes(size)); + bytes.push(...shortToBytes(size)); } for (let tableInfo of stmtTableInfoList) { diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 13c2a3bb..1234cdea 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -1,6 +1,6 @@ import JSONBig from 'json-bigint'; import { WsClient } from "../client/wsClient"; -import { ColumnsBlockType, FieldBindType, PrecisionLength } from "../common/constant"; +import { FieldBindType, PrecisionLength } from "../common/constant"; import logger from "../common/log"; import { ReqId } from "../common/reqid"; import { ErrorCode, TaosResultError, TDWebSocketClientError } from "../common/wsError"; @@ -8,13 +8,10 @@ import { StmtBindParams } from "./wsParamsBase"; import { stmt2BinaryBlockEncode, StmtFieldInfo, StmtMessageInfo, WsStmtQueryResponse } from "./wsProto"; import { WsStmt } from "./wsStmt"; import { _isVarType } from '../common/taosResult'; -import { bigintToBytes, intToBytes, shotToBytes } from '../common/utils'; import { Stmt2BindParams } from './wsParams2'; import { TableInfo } from './wsTableInfo'; -import { ColumnInfo } from './wsColumnInfo'; import { WSRows } from '../sql/wsRows'; import { FieldBindParams } from './FieldBindParams'; -import { log } from 'console'; export class WsStmt2 implements WsStmt{ private _wsClient: WsClient; diff --git a/nodejs/src/tmq/tmqResponse.ts b/nodejs/src/tmq/tmqResponse.ts index 2ac677a6..913e85ff 100644 --- a/nodejs/src/tmq/tmqResponse.ts +++ b/nodejs/src/tmq/tmqResponse.ts @@ -251,7 +251,7 @@ export class WSTmqFetchBlockInfo { //Variable length type let start = bufferOffset; let offsets:number[]= []; - for (let i = 0; i< rows; i++, start += TDengineTypeLength['INT']) { + for (let i = 0; i< rows; i++, start += TDengineTypeLength[TDengineTypeCode.INT]) { //get data length, -1 is null offsets.push(dataView.getInt32(start, true)) } From b67c32928b1d00bd71bad77eb546e2fa14dbd490 Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 22 Sep 2025 13:58:43 +0800 Subject: [PATCH 18/23] feat: improve stmt export --- nodejs/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/index.ts b/nodejs/index.ts index d1fe9629..2a278416 100644 --- a/nodejs/index.ts +++ b/nodejs/index.ts @@ -18,7 +18,7 @@ export * from "./src/sql/wsRows" export * from "./src/sql/wsSql" export * from "./src/stmt/wsParamsBase" export * from "./src/stmt/wsProto" -export * from "./src/stmt/wsStmt1" +export * from "./src/stmt/wsStmt" export * from "./src/tmq/config" export * from "./src/tmq/constant" export * from "./src/tmq/tmqResponse" From 188ce7a62d4f60b50e39bcd29e62588ccf1d694e Mon Sep 17 00:00:00 2001 From: menshibin Date: Mon, 22 Sep 2025 17:19:42 +0800 Subject: [PATCH 19/23] feat: optimize stmt2 encoding --- nodejs/src/stmt/wsProto.ts | 311 ++++++++++++++++++------------------- nodejs/src/stmt/wsStmt2.ts | 3 +- 2 files changed, 149 insertions(+), 165 deletions(-) diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 3346f1a8..5593ff7c 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -109,187 +109,172 @@ export function binaryBlockEncode(bindParams :StmtBindParams, bindType:StmtBindT } -export function stmt2BinaryBlockEncode(reqId: bigint, - stmtTableInfoList: TableInfo[], - stmtTableInfo: Map, - stmt_id: bigint | undefined | null, - toBeBindTableNameIndex:number | undefined | null, - toBeBindTagCount:number, - toBeBindColCount:number ):number[] { - if(stmt_id == null || stmt_id == undefined) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "stmt_id is invalid"); - } - // cloc total size - let totalTableNameSize = 0; - let tableNameSizeList:Array = []; - if (toBeBindTableNameIndex != null && toBeBindTableNameIndex != undefined) { - stmtTableInfo.forEach((tableInfo) => { - let tableName = tableInfo.getTableName(); - if (tableName) { - let size = new TextEncoder().encode(tableName).length; - totalTableNameSize += size + 1; - tableNameSizeList.push(size + 1); - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - }); - } - let totalTagSize = 0; - let tagSizeList:Array = []; - if (toBeBindTagCount > 0) { - stmtTableInfoList.forEach((tableInfo) => { - let params = tableInfo.getTags(); - if (params) { - params.encode(); - let tagSize = params.getDataTotalLen(); - totalTagSize += tagSize; - tagSizeList.push(tagSize); - } - }); - } - - let totalColSize = 0; - let colSizeList:Array = []; - if (toBeBindColCount > 0) { - stmtTableInfoList.forEach((tableInfo) => { - let params = tableInfo.getParams(); - if (!params) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); - } - - params.encode(); - let colSize = params.getDataTotalLen(); - totalColSize += colSize; - colSizeList.push(colSize); - - }); +function writeColumnToView(column: ColumnInfo, view: DataView, offset: number): number { + let currentOffset = offset; + + // length, type, _rows + view.setInt32(currentOffset, column.length, true); + currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(currentOffset, column.type, true); + currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(currentOffset, column._rows, true); + currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; + + // isNull bitmap + if (column.isNull && column.isNull.length > 0) { + const isNullBuffer = new Uint8Array(column.isNull); + new Uint8Array(view.buffer, view.byteOffset + currentOffset).set(isNullBuffer); + currentOffset += isNullBuffer.length; + } + + // _haveLength and _dataLengths + view.setUint8(currentOffset, column._haveLength); + currentOffset += 1; + if (column._haveLength === 1 && column._dataLengths) { + for (const length of column._dataLengths) { + view.setInt32(currentOffset, length, true); + currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; } - let totalSize = totalTableNameSize + totalTagSize + totalColSize; - let toBeBindTableNameCount = (toBeBindTableNameIndex != null && toBeBindTableNameIndex >= 0) ? 1 : 0; - totalSize += stmtTableInfoList.length * ( - toBeBindTableNameCount * 2 - + (toBeBindTagCount > 0 ? 1 : 0) * 4 - + (toBeBindColCount > 0 ? 1 : 0) * 4); - - const bytes: number[] = []; - // write req_id - bytes.push(...bigintToBytes(reqId)); - - // write stmt_id - if (stmt_id) { - bytes.push(...bigintToBytes(stmt_id)); + } + + // data + view.setInt32(currentOffset, column.data.byteLength, true); + currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; + if (column.data.byteLength > 0) { + new Uint8Array(view.buffer, view.byteOffset + currentOffset).set(new Uint8Array(column.data)); + currentOffset += column.data.byteLength; + } + + return currentOffset - offset; // Return the total number of bytes written +} + +export function stmt2BinaryBlockEncode( + reqId: bigint, + stmtTableInfoList: TableInfo[], + stmt_id: bigint | undefined | null, + toBeBindTableNameIndex: number | undefined | null, + toBeBindTagCount: number, + toBeBindColCount: number +): ArrayBuffer { + if (!stmt_id) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "stmt_id is invalid"); + } + + // --- 1. Single pass to prepare data and calculate the exact size --- + const hasTableName = toBeBindTableNameIndex != null && toBeBindTableNameIndex >= 0; + const hasTags = toBeBindTagCount > 0; + const hasCols = toBeBindColCount > 0; + + let totalDataSize = 0; + const processedTables = stmtTableInfoList.map(tableInfo => { + const tableNameBytes = hasTableName ? new TextEncoder().encode(tableInfo.getTableName() || '') : null; + if (hasTableName && (!tableNameBytes || tableNameBytes.length === 0)) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); } - bytes.push(...bigintToBytes(9n)); - bytes.push(...shortToBytes(1)); - bytes.push(...intToBytes(-1)); - bytes.push(...intToBytes(totalSize + 28, false)); - - bytes.push(...intToBytes(stmtTableInfoList.length)); - bytes.push(...intToBytes(toBeBindTagCount)); - bytes.push(...intToBytes(toBeBindColCount)); - if (toBeBindTableNameCount > 0) { - bytes.push(...intToBytes(0x1C)); + + const tags = hasTags ? tableInfo.getTags() : null; + if (tags) tags.encode(); + + const params = hasCols ? tableInfo.getParams() : null; + if (params) { + params.encode(); } else { - bytes.push(...intToBytes(0)); + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); } + + // Accumulate the size of each part + const tableNameBlockSize = tableNameBytes ? 2 + tableNameBytes.length + 1 : 0; // 2(len) + data + 1(\0) + const tagsBlockSize = tags ? tags.getDataTotalLen() : 0; + const colsBlockSize = params ? params.getDataTotalLen() : 0; - if (toBeBindTagCount) { - if (toBeBindTableNameCount > 0) { - bytes.push(...intToBytes(28 + totalTableNameSize + 2 * stmtTableInfoList.length)); - } else { - bytes.push(...intToBytes(28)); - } - } else { - bytes.push(...intToBytes(0)); + totalDataSize += tableNameBlockSize + tagsBlockSize + colsBlockSize; + if(hasTags){ + totalDataSize += TDengineTypeLength[TDengineTypeCode.INT]; // tag block length + } + if(hasCols){ + totalDataSize += TDengineTypeLength[TDengineTypeCode.INT]; // col block length } - - if (toBeBindColCount > 0) { - let skipSize = 0; - if (toBeBindTableNameCount > 0) { - skipSize += totalTableNameSize + 2 * stmtTableInfoList.length; - } - if (toBeBindTagCount > 0) { - skipSize += totalTagSize + 4 * stmtTableInfoList.length; - } + return { tableNameBytes, tags, params }; + }); - // colOffset = 28(固定头) + skipSize - bytes.push(...intToBytes(28 + skipSize)); - } else { - bytes.push(...intToBytes(0)); - } + // --- 2. Allocate an ArrayBuffer with the exact size --- + const HEADER_SIZE = 28; // Fixed header size + const OFFSETS_SIZE = 12; // 3 offset pointers + const totalBufferSize = HEADER_SIZE + OFFSETS_SIZE + totalDataSize; + + const buffer = new ArrayBuffer(totalBufferSize); + const view = new DataView(buffer); + let offset = 0; + // --- 3. Write the fixed header --- + view.setBigUint64(offset, reqId, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; + view.setBigUint64(offset, stmt_id, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; + view.setBigUint64(offset, 9n, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; // action: bind + view.setUint32(offset, 1, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - if (toBeBindTableNameCount > 0) { - for (let size of tableNameSizeList) { - if (size === 0) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - bytes.push(...shortToBytes(size)); - } + // --- 4. Write the dynamic header and offsets --- + const tableCount = stmtTableInfoList.length; + view.setInt32(offset, totalBufferSize, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; // total length + view.setInt32(offset, tableCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(offset, toBeBindTagCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(offset, toBeBindColCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - for (let tableInfo of stmtTableInfoList) { - let tableName = tableInfo.getTableName(); - if (tableName && tableName.length > 0) { - let encoder = new TextEncoder().encode(tableName); - bytes.push(...encoder); - bytes.push(0); // null terminator - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); - } - } - } + let currentDataOffset = HEADER_SIZE + OFFSETS_SIZE; + + // TableName Offset + view.setInt32(offset, hasTableName ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + const tableNameDataStart = currentDataOffset; + if (hasTableName) { + currentDataOffset += processedTables.reduce((sum, t) => sum + (t.tableNameBytes ? 2 + t.tableNameBytes.length + 1 : 0), 0); + } - if (toBeBindTagCount > 0) { - for (let size of tagSizeList) { - bytes.push(...intToBytes(size)); - } - for (let tableInfo of stmtTableInfoList) { - let tags = tableInfo.getTags(); - if (tags && tags.getParams().length > 0) { - for (let tagColumnInfo of tags.getParams()) { - serializeColumn(tagColumnInfo, bytes); - } - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); - } - } + // Tags Offset + view.setInt32(offset, hasTags ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + const tagsDataStart = currentDataOffset; + if (hasTags) { + currentDataOffset += processedTables.reduce((sum, t) => sum + 4 + (t.tags ? t.tags.getDataTotalLen() : 0), 0); + } + + // Columns Offset + view.setInt32(offset, hasCols ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + + // --- 5. Write the actual data blocks --- + // a. Table Names + if (hasTableName) { + offset = tableNameDataStart; + for (const table of processedTables) { + const len = table.tableNameBytes!.length + 1; + view.setUint16(offset, len, true); offset += 2; + new Uint8Array(buffer, offset).set(table.tableNameBytes!); offset += table.tableNameBytes!.length; + view.setUint8(offset, 0); offset += 1; // null terminator } + } - // ColumnDataLength - if (toBeBindColCount > 0) { - for (let colSize of colSizeList) { - bytes.push(...intToBytes(colSize, false)); - } - for (let tableInfo of stmtTableInfoList) { - let params = tableInfo.getParams(); - if (!params) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); - } - - let colColumnInfos:ColumnInfo[] = params.getParams() - colColumnInfos.forEach(colColumnInfo => { - serializeColumn(colColumnInfo, bytes); - }); + // b. Tags + if (hasTags) { + offset = tagsDataStart; + for (const table of processedTables) { + const tags = table.tags!; + const tagDataLen = tags.getDataTotalLen(); + view.setInt32(offset, tagDataLen, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + for (const col of tags.getParams()) { + offset += writeColumnToView(col, new DataView(buffer, offset), 0); } } - return bytes - } - function serializeColumn(column: ColumnInfo, bytes: number[]) { - bytes.push(...intToBytes(column.length, false)); - bytes.push(...intToBytes(column.type)); - bytes.push(...intToBytes(column._rows)); - bytes.push(...column.isNull ? column.isNull : []); - bytes.push(column._haveLength); - - if (column._haveLength == 1 && column._dataLengths) { - column._dataLengths.forEach(length => { - bytes.push(...intToBytes(length)); - }); + // c. Columns + if (hasCols) { + for (const table of processedTables) { + const params = table.params!; + const colDataLen = params.getDataTotalLen(); + view.setInt32(offset, colDataLen, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; + for (const col of params.getParams()) { + offset += writeColumnToView(col, new DataView(buffer, offset), 0); + } } - bytes.push(...intToBytes(column.data.byteLength, false)); - bytes.push(...column.data.byteLength ? new Uint8Array(column.data) : []); } + return buffer; +} diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 1234cdea..9f3d7124 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -210,8 +210,7 @@ export class WsStmt2 implements WsStmt{ let reqId = BigInt(ReqId.getReqID()); let bytes = stmt2BinaryBlockEncode(reqId, - this._stmtTableInfoList, - this._stmtTableInfo, + this._stmtTableInfoList, this._stmt_id, this._toBeBindTableNameIndex, this._toBeBindTagCount, From 7f423f6aedb3da026872f22246f08277b2cc4401 Mon Sep 17 00:00:00 2001 From: menshibin Date: Tue, 23 Sep 2025 13:40:34 +0800 Subject: [PATCH 20/23] feat: optimize stmt2 parameter binding serialization --- nodejs/src/stmt/wsProto.ts | 239 +++++++++++++-------- nodejs/src/stmt/wsStmt2.ts | 2 +- nodejs/src/stmt/wsTableInfo.ts | 19 +- nodejs/test/bulkPulling/stmt2.func.test.ts | 2 +- 4 files changed, 161 insertions(+), 101 deletions(-) diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 5593ff7c..9e82d98d 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -140,6 +140,7 @@ function writeColumnToView(column: ColumnInfo, view: DataView, offset: number): // data view.setInt32(currentOffset, column.data.byteLength, true); currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; + // console.log(currentOffset, column.data.byteLength, view.byteOffset, view.buffer.byteLength); if (column.data.byteLength > 0) { new Uint8Array(view.buffer, view.byteOffset + currentOffset).set(new Uint8Array(column.data)); currentOffset += column.data.byteLength; @@ -160,121 +161,171 @@ export function stmt2BinaryBlockEncode( throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "stmt_id is invalid"); } - // --- 1. Single pass to prepare data and calculate the exact size --- + const listLength = stmtTableInfoList.length; + const textEncoder = new TextEncoder(); // 复用 + + const result = { + totalTableNameSize: 0, + totalTagSize: 0, + totalColSize: 0, + tableNameSizeList: new Array(listLength), + tagSizeList: new Array(listLength), + colSizeList: new Array(listLength) + }; + const hasTableName = toBeBindTableNameIndex != null && toBeBindTableNameIndex >= 0; const hasTags = toBeBindTagCount > 0; - const hasCols = toBeBindColCount > 0; + for (let i = 0; i < listLength; i++) { + const tableInfo = stmtTableInfoList[i]; + if (hasTableName) { + const tableName = tableInfo.getTableName(); + if (!tableName) throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); + const size = tableInfo.getTableNameLength() + if (size > 0) { + result.tableNameSizeList[i] = size + 1; + result.totalTableNameSize += size + 1; + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name len is empty"); + } - let totalDataSize = 0; - const processedTables = stmtTableInfoList.map(tableInfo => { - const tableNameBytes = hasTableName ? new TextEncoder().encode(tableInfo.getTableName() || '') : null; - if (hasTableName && (!tableNameBytes || tableNameBytes.length === 0)) { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); } - const tags = hasTags ? tableInfo.getTags() : null; - if (tags) tags.encode(); - - const params = hasCols ? tableInfo.getParams() : null; - if (params) { - params.encode(); - } else { - throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + if (hasTags) { + const tagParams = tableInfo.getTags(); + if (!tagParams) throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind tags is empty!"); + + tagParams.encode(); + const size = tagParams.getDataTotalLen(); + result.tagSizeList[i] = size; + result.totalTagSize += size; } - // Accumulate the size of each part - const tableNameBlockSize = tableNameBytes ? 2 + tableNameBytes.length + 1 : 0; // 2(len) + data + 1(\0) - const tagsBlockSize = tags ? tags.getDataTotalLen() : 0; - const colsBlockSize = params ? params.getDataTotalLen() : 0; - - totalDataSize += tableNameBlockSize + tagsBlockSize + colsBlockSize; - if(hasTags){ - totalDataSize += TDengineTypeLength[TDengineTypeCode.INT]; // tag block length - } - if(hasCols){ - totalDataSize += TDengineTypeLength[TDengineTypeCode.INT]; // col block length - } + const params = tableInfo.getParams(); + if (!params) throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + params.encode(); + const size = params.getDataTotalLen(); + result.colSizeList[i] = size; + result.totalColSize += size; + } - return { tableNameBytes, tags, params }; - }); + let totalSize = result.totalTableNameSize + result.totalTagSize + result.totalColSize; + let toBeBindTableNameCount = (toBeBindTableNameIndex != null && toBeBindTableNameIndex >= 0) ? 1 : 0; + totalSize += stmtTableInfoList.length * ( + toBeBindTableNameCount * 2 + + (toBeBindTagCount > 0 ? 1 : 0) * 4 + + (toBeBindColCount > 0 ? 1 : 0) * 4); - // --- 2. Allocate an ArrayBuffer with the exact size --- - const HEADER_SIZE = 28; // Fixed header size - const OFFSETS_SIZE = 12; // 3 offset pointers - const totalBufferSize = HEADER_SIZE + OFFSETS_SIZE + totalDataSize; - - const buffer = new ArrayBuffer(totalBufferSize); + let headerSize = 28; // Fixed header size + let msgHeaderSize = 30; // Fixed msg header size + const buffer = new ArrayBuffer(headerSize + msgHeaderSize + totalSize); const view = new DataView(buffer); let offset = 0; - - // --- 3. Write the fixed header --- - view.setBigUint64(offset, reqId, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; - view.setBigUint64(offset, stmt_id, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; - view.setBigUint64(offset, 9n, true); offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; // action: bind - view.setUint32(offset, 1, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - - // --- 4. Write the dynamic header and offsets --- - const tableCount = stmtTableInfoList.length; - view.setInt32(offset, totalBufferSize, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; // total length - view.setInt32(offset, tableCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - view.setInt32(offset, toBeBindTagCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - view.setInt32(offset, toBeBindColCount, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - - let currentDataOffset = HEADER_SIZE + OFFSETS_SIZE; - - // TableName Offset - view.setInt32(offset, hasTableName ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - const tableNameDataStart = currentDataOffset; - if (hasTableName) { - currentDataOffset += processedTables.reduce((sum, t) => sum + (t.tableNameBytes ? 2 + t.tableNameBytes.length + 1 : 0), 0); + view.setBigUint64(offset, reqId, true); + offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; + view.setBigUint64(offset, stmt_id, true); + offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; + view.setBigUint64(offset, 9n, true); + offset += TDengineTypeLength[TDengineTypeCode.BIGINT]; // action: bind + view.setInt16(offset, 1, true); + offset += TDengineTypeLength[TDengineTypeCode.SMALLINT]; // version + view.setInt32(offset, -1, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(offset, totalSize + headerSize, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; // total length + + view.setInt32(offset, stmtTableInfoList.length, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(offset, toBeBindTagCount, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + view.setInt32(offset, toBeBindColCount, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + + view.setInt32(offset, toBeBindTableNameCount > 0 ? 0x1C : 0, true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + console.log(`bytes.length=${offset}, totalSize=${totalSize}`); + if (toBeBindTagCount) { + if (toBeBindTableNameCount > 0) { + view.setInt32(offset, headerSize + result.totalTableNameSize + 2 * stmtTableInfoList.length, true); + } else { + view.setInt32(offset, headerSize, true); + } + } else { + view.setInt32(offset, 0, true); } + offset += TDengineTypeLength[TDengineTypeCode.INT]; - // Tags Offset - view.setInt32(offset, hasTags ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - const tagsDataStart = currentDataOffset; - if (hasTags) { - currentDataOffset += processedTables.reduce((sum, t) => sum + 4 + (t.tags ? t.tags.getDataTotalLen() : 0), 0); - } + if (toBeBindColCount > 0) { + let skipSize = 0; + if (toBeBindTableNameCount > 0) { + skipSize += result.totalTableNameSize + 2 * stmtTableInfoList.length; + } - // Columns Offset - view.setInt32(offset, hasCols ? currentDataOffset : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - - // --- 5. Write the actual data blocks --- - // a. Table Names - if (hasTableName) { - offset = tableNameDataStart; - for (const table of processedTables) { - const len = table.tableNameBytes!.length + 1; - view.setUint16(offset, len, true); offset += 2; - new Uint8Array(buffer, offset).set(table.tableNameBytes!); offset += table.tableNameBytes!.length; - view.setUint8(offset, 0); offset += 1; // null terminator + if (toBeBindTagCount > 0) { + skipSize += result.totalTagSize + 4 * stmtTableInfoList.length; } + // colOffset = 28(固定头) + skipSize + view.setInt32(offset, skipSize + headerSize, true); + } else { + view.setInt32(offset, 0, true); } - - // b. Tags - if (hasTags) { - offset = tagsDataStart; - for (const table of processedTables) { - const tags = table.tags!; - const tagDataLen = tags.getDataTotalLen(); - view.setInt32(offset, tagDataLen, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - for (const col of tags.getParams()) { - offset += writeColumnToView(col, new DataView(buffer, offset), 0); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + + if (toBeBindTableNameCount > 0) { + let dataOffset = offset + result.tableNameSizeList.length * TDengineTypeLength[TDengineTypeCode.SMALLINT]; + for (let i = 0; i < listLength; i++) { + view.setInt16(offset, result.tableNameSizeList[i], true); + offset += TDengineTypeLength[TDengineTypeCode.SMALLINT]; + + let tableName = stmtTableInfoList[i].getTableName(); + if (tableName && tableName.length > 0) { + console.log(`tableName=${tableName}, tableName.length=${tableName.length} dataOffset=${dataOffset}`); + new Uint8Array(buffer, dataOffset).set(tableName); + dataOffset += tableName.length; + view.setUint8(dataOffset, 0); + dataOffset += 1; + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Table name is empty"); } } + offset = dataOffset; } - - // c. Columns - if (hasCols) { - for (const table of processedTables) { - const params = table.params!; - const colDataLen = params.getDataTotalLen(); - view.setInt32(offset, colDataLen, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - for (const col of params.getParams()) { - offset += writeColumnToView(col, new DataView(buffer, offset), 0); + + if (toBeBindTagCount > 0) { + let dataOffset = offset + result.tagSizeList.length * TDengineTypeLength[TDengineTypeCode.INT]; + for (let i = 0; i < listLength; i++) { + view.setInt32(offset, result.tagSizeList[i], true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + let tags = stmtTableInfoList[i].getTags(); + + if (tags && tags.getParams().length > 0) { + for (const col of tags.getParams()) { + dataOffset += writeColumnToView(col, new DataView(buffer, dataOffset), 0); + } + } else { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Tags are empty"); } } + offset = dataOffset; + } + + // ColumnDataLength + if (toBeBindColCount > 0) { + let dataOffset = offset + result.colSizeList.length * TDengineTypeLength[TDengineTypeCode.INT]; + for (let i = 0; i < listLength; i++) { + view.setInt32(offset, result.colSizeList[i], true); + offset += TDengineTypeLength[TDengineTypeCode.INT]; + + let params = stmtTableInfoList[i].getParams(); + if (!params) { + throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); + } + // console.log(headerSize + msgHeaderSize + totalSize, dataOffset); + for (const col of params.getParams()) { + // console.log(dataOffset); + dataOffset += writeColumnToView(col, new DataView(buffer, dataOffset), 0); + } + } } - return buffer; + } diff --git a/nodejs/src/stmt/wsStmt2.ts b/nodejs/src/stmt/wsStmt2.ts index 9f3d7124..1bfe190d 100644 --- a/nodejs/src/stmt/wsStmt2.ts +++ b/nodejs/src/stmt/wsStmt2.ts @@ -210,7 +210,7 @@ export class WsStmt2 implements WsStmt{ let reqId = BigInt(ReqId.getReqID()); let bytes = stmt2BinaryBlockEncode(reqId, - this._stmtTableInfoList, + this._stmtTableInfoList, this._stmt_id, this._toBeBindTableNameIndex, this._toBeBindTagCount, diff --git a/nodejs/src/stmt/wsTableInfo.ts b/nodejs/src/stmt/wsTableInfo.ts index d74ea904..f7b3cb95 100644 --- a/nodejs/src/stmt/wsTableInfo.ts +++ b/nodejs/src/stmt/wsTableInfo.ts @@ -3,18 +3,26 @@ import { StmtBindParams } from "./wsParamsBase"; import JSONBig from 'json-bigint'; export class TableInfo { - name: string | undefined | null; + name: Uint8Array | undefined | null; tags: StmtBindParams | undefined | null; params?: StmtBindParams; - + length: number = 0; + textEncoder = new TextEncoder(); constructor(name?: string) { - this.name = name; + if (name && name.length > 0) { + this.name = this.textEncoder.encode(name); + this.length = this.name.length; + } } - public getTableName(): string | undefined | null { + public getTableName(): Uint8Array | undefined | null { return this.name; } + public getTableNameLength(): number { + return this.length; + } + public getTags(): StmtBindParams | undefined | null { return this.tags; } @@ -25,7 +33,8 @@ export class TableInfo { public setTableName(name: string): void { if (name && name.length > 0) { - this.name = name; + this.name = this.textEncoder.encode(name); + this.length = this.name.length; } else { throw new TaosError(ErrorCode.ERR_INVALID_PARAMS, "Table name is invalid!"); } diff --git a/nodejs/test/bulkPulling/stmt2.func.test.ts b/nodejs/test/bulkPulling/stmt2.func.test.ts index 300a1591..83646e14 100644 --- a/nodejs/test/bulkPulling/stmt2.func.test.ts +++ b/nodejs/test/bulkPulling/stmt2.func.test.ts @@ -483,7 +483,7 @@ describe('TDWebSocket.Stmt()', () => { await stmt.exec() }catch(e) { let err:any = e - expect(err.message).toMatch(/Retry needed|Tags are empty/); + expect(err.message).toMatch(/Retry needed|Bind tags is empty!/); } await stmt.close() await connector.close(); From 988510707d64523855c925eeb9e99a0d72c9859b Mon Sep 17 00:00:00 2001 From: menshibin Date: Tue, 23 Sep 2025 13:44:59 +0800 Subject: [PATCH 21/23] feat: remove stmt2 parameter binding debugging log --- nodejs/src/stmt/wsProto.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 9e82d98d..1a2c25ba 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -140,7 +140,6 @@ function writeColumnToView(column: ColumnInfo, view: DataView, offset: number): // data view.setInt32(currentOffset, column.data.byteLength, true); currentOffset += TDengineTypeLength[TDengineTypeCode.INT]; - // console.log(currentOffset, column.data.byteLength, view.byteOffset, view.buffer.byteLength); if (column.data.byteLength > 0) { new Uint8Array(view.buffer, view.byteOffset + currentOffset).set(new Uint8Array(column.data)); currentOffset += column.data.byteLength; @@ -242,7 +241,6 @@ export function stmt2BinaryBlockEncode( view.setInt32(offset, toBeBindTableNameCount > 0 ? 0x1C : 0, true); offset += TDengineTypeLength[TDengineTypeCode.INT]; - console.log(`bytes.length=${offset}, totalSize=${totalSize}`); if (toBeBindTagCount) { if (toBeBindTableNameCount > 0) { view.setInt32(offset, headerSize + result.totalTableNameSize + 2 * stmtTableInfoList.length, true); @@ -278,7 +276,6 @@ export function stmt2BinaryBlockEncode( let tableName = stmtTableInfoList[i].getTableName(); if (tableName && tableName.length > 0) { - console.log(`tableName=${tableName}, tableName.length=${tableName.length} dataOffset=${dataOffset}`); new Uint8Array(buffer, dataOffset).set(tableName); dataOffset += tableName.length; view.setUint8(dataOffset, 0); @@ -319,9 +316,7 @@ export function stmt2BinaryBlockEncode( if (!params) { throw new TaosResultError(ErrorCode.ERR_INVALID_PARAMS, "Bind params is empty!"); } - // console.log(headerSize + msgHeaderSize + totalSize, dataOffset); for (const col of params.getParams()) { - // console.log(dataOffset); dataOffset += writeColumnToView(col, new DataView(buffer, dataOffset), 0); } } From 47c18cc44fa55ad4b9e7243ed895262bcc6be6f2 Mon Sep 17 00:00:00 2001 From: menshibin Date: Tue, 23 Sep 2025 20:36:06 +0800 Subject: [PATCH 22/23] feat: remove code with invalid stmt2 parameter binding --- nodejs/src/common/utils.ts | 27 +-------------------------- nodejs/src/stmt/wsProto.ts | 5 +---- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/nodejs/src/common/utils.ts b/nodejs/src/common/utils.ts index 36dc7c84..e8ace1c1 100644 --- a/nodejs/src/common/utils.ts +++ b/nodejs/src/common/utils.ts @@ -175,29 +175,4 @@ export function decimalToString(valueStr: string, fields_scale: bigint | null): } } return decimalStr; -} - -export const shortToBytes = (value: number): number[] => { - const buffer = new ArrayBuffer(2); - const view = new DataView(buffer); - view.setUint16(0, value, true); - return [...new Uint8Array(buffer)]; -}; - -export const bigintToBytes = (value: bigint): number[] => { - const buffer = new ArrayBuffer(8); - const view = new DataView(buffer); - view.setBigUint64(0, value, true); - return [...new Uint8Array(buffer)]; -}; - -export const intToBytes = (value: number, bSymbol = true): number[] => { - const buffer = new ArrayBuffer(4); - const view = new DataView(buffer); - if (bSymbol) { - view.setInt32(0, value, true); - } else { - view.setUint32(0, value, true); - } - return [...new Uint8Array(buffer)]; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/nodejs/src/stmt/wsProto.ts b/nodejs/src/stmt/wsProto.ts index 1a2c25ba..bf0a52c0 100644 --- a/nodejs/src/stmt/wsProto.ts +++ b/nodejs/src/stmt/wsProto.ts @@ -3,7 +3,6 @@ import { WSQueryResponse } from "../client/wsResponse"; import { TDengineTypeCode, TDengineTypeLength } from "../common/constant"; import { MessageResp } from "../common/taosResult"; import { StmtBindParams } from "./wsParamsBase"; -import { bigintToBytes, intToBytes, shortToBytes } from "../common/utils"; import { ColumnInfo } from "./wsColumnInfo"; import { ErrorCode, TaosResultError } from "../common/wsError"; import { TableInfo } from "./wsTableInfo"; @@ -161,8 +160,6 @@ export function stmt2BinaryBlockEncode( } const listLength = stmtTableInfoList.length; - const textEncoder = new TextEncoder(); // 复用 - const result = { totalTableNameSize: 0, totalTagSize: 0, @@ -261,7 +258,7 @@ export function stmt2BinaryBlockEncode( if (toBeBindTagCount > 0) { skipSize += result.totalTagSize + 4 * stmtTableInfoList.length; } - // colOffset = 28(固定头) + skipSize + // colOffset = 28 + skipSize view.setInt32(offset, skipSize + headerSize, true); } else { view.setInt32(offset, 0, true); From ba5a2cf38975f1fc291b114131c18b1abf6d711e Mon Sep 17 00:00:00 2001 From: menshibin Date: Thu, 25 Sep 2025 09:35:41 +0800 Subject: [PATCH 23/23] feat: modify version --- nodejs/package-lock.json | 4 ++-- nodejs/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index f7d21081..9384ad36 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tdengine/websocket", - "version": "3.1.9", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tdengine/websocket", - "version": "3.1.9", + "version": "3.2.0", "license": "MIT", "dependencies": { "async-mutex": "^0.5.0", diff --git a/nodejs/package.json b/nodejs/package.json index 204e046c..81ab4c4f 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@tdengine/websocket", - "version": "3.1.9", + "version": "3.2.0", "description": "The websocket Node.js connector for TDengine. TDengine versions 3.3.2.0 and above are recommended to use this connector.", "source": "index.ts", "main": "lib/index.js",