Skip to content
Merged
4 changes: 2 additions & 2 deletions nodejs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nodejs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tdengine/websocket",
"version": "3.1.8",
"version": "3.1.9",
"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",
Expand Down
44 changes: 33 additions & 11 deletions nodejs/src/client/wsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,38 @@ import { ReqId } from '../common/reqid';
import logger from '../common/log';
import { safeDecodeURIComponent, compareVersions} from '../common/utils';
import { w3cwebsocket } from 'websocket';
import { TSDB_OPTION_CONNECTION } from '../common/constant';

export class WsClient {
private _wsConnector?: WebSocketConnector;
private _timeout?:number | undefined | null;
private _timezone?:string | undefined | null;
private readonly _url:URL;
private static readonly _minVersion = "3.3.2.0";

constructor(url: URL, timeout ?:number | undefined | null) {
this.checkURL(url);
this._url = url;
this._timeout = timeout;

if (this._url.searchParams.has("timezone")) {
this._timezone = this._url.searchParams.get("timezone") || undefined;
this._url.searchParams.delete("timezone");
}

}

async connect(database?: string | undefined | null): Promise<void> {
let _db = this._url.pathname.split('/')[3];
if (database) {
_db = database;
}

let connMsg = {
action: 'conn',
args: {
req_id: ReqId.getReqID(),
user: safeDecodeURIComponent(this._url.username),
password: safeDecodeURIComponent(this._url.password),
db: _db,
db: database,
...(this._timezone && { tz: this._timezone }),
},
};

logger.debug("[wsClient.connect.connMsg]===>" + JSONBig.stringify(connMsg));
this._wsConnector = await WebSocketConnectionPool.instance().getConnection(this._url, this._timeout);
if (this._wsConnector.readyState() === w3cwebsocket.OPEN) {
return;
Expand All @@ -62,6 +64,29 @@ export class WsClient {

}

async setOptionConnection(option: TSDB_OPTION_CONNECTION, value: string | null): Promise<void> {
logger.debug("[wsClient.setOptionConnection]===>" + option + ", " + value);

let connMsg = {
action: 'options_connection',
args: {
req_id: ReqId.getReqID(),
options: [
{
option: option,
value: value
}
]
}
};
try {
await this.exec(JSONBig.stringify(connMsg), false);
} catch (e: any) {
logger.error("[wsClient.setOptionConnection] failed: " + e.message);
throw e;
}
}

async execNoResp(queryMsg: string): Promise<void> {
logger.debug('[wsQueryInterface.query.queryMsg]===>' + queryMsg);
if (this._wsConnector && this._wsConnector.readyState() === w3cwebsocket.OPEN) {
Expand Down Expand Up @@ -200,8 +225,6 @@ export class WsClient {
logger.error(`connection creation failed, url: ${this._url}, code: ${e.code}, message: ${e.message}`);
throw(new TDWebSocketClientError(ErrorCode.ERR_WEBSOCKET_CONNECTION_FAIL, `connection creation failed, url: ${this._url}, code: ${e.code}, message: ${e.message}`));
}


}
throw(ErrorCode.ERR_CONNECTION_CLOSED, "invalid websocket connect");
}
Expand All @@ -212,7 +235,6 @@ export class WsClient {
this._wsConnector = undefined
// this._wsConnector.close();
}

}

checkURL(url: URL) {
Expand Down
3 changes: 1 addition & 2 deletions nodejs/src/client/wsConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export class WebSocketConnector {
WsEventCallback.instance().handleEventCallback({id:BigInt(0), action:msg.action, req_id:msg.req_id},
OnMessageType.MESSAGE_TYPE_STRING, msg);
} else {
throw new TDWebSocketClientError(ErrorCode.ERR_INVALID_MESSAGE_TYPE,
`invalid message type ${Object.prototype.toString.call(data)}`);
throw new TDWebSocketClientError(ErrorCode.ERR_INVALID_MESSAGE_TYPE, `invalid message type ${Object.prototype.toString.call(data)}`);
}
}

Expand Down
11 changes: 7 additions & 4 deletions nodejs/src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class WSConfig {
private _url: string;
private _timeout:number| undefined | null;
private _token:string | undefined | null;
private _timezone:string | undefined | null;

constructor(url:string) {
this._url = url;
Expand Down Expand Up @@ -52,8 +53,10 @@ export class WSConfig {
public getTimeOut(): number | undefined | null {
return this._timeout
}


public setTimezone(timezone: string) {
this._timezone = timezone;
}
public getTimezone(): string | undefined | null {
return this._timezone;
}
}


7 changes: 7 additions & 0 deletions nodejs/src/common/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export enum TDengineTypeCode {
GEOMETRY = 20,
}

export enum TSDB_OPTION_CONNECTION {
TSDB_OPTION_CONNECTION_CHARSET, // charset, Same as the scope supported by the system
TSDB_OPTION_CONNECTION_TIMEZONE, // timezone, Same as the scope supported by the system
TSDB_OPTION_CONNECTION_USER_IP, // user ip
TSDB_OPTION_CONNECTION_USER_APP, // user app
}

export const TDenginePrecision: IndexableString = {
0: 'MILLISECOND',
1: "MICROSECOND",
Expand Down
14 changes: 14 additions & 0 deletions nodejs/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ export function getUrl(wsConfig:WSConfig):URL {
if (token) {
url.searchParams.set("token", token)
}

let timezone = wsConfig.getTimezone()
if (timezone) {
url.searchParams.set("timezone", timezone)
}

if (url.pathname && url.pathname !== '/') {
wsConfig.setDb(url.pathname.slice(1))
}

if (url.searchParams.has("timezone")) {
wsConfig.setTimezone(url.searchParams.get("timezone") || '');
}

url.pathname = '/ws'
return url
}
Expand Down
11 changes: 10 additions & 1 deletion nodejs/src/sql/wsSql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { WSFetchBlockResponse, WSQueryResponse } from '../client/wsResponse'
import { Precision, SchemalessMessageInfo, SchemalessProto } from './wsProto'
import { WsStmt } from '../stmt/wsStmt'
import { ReqId } from '../common/reqid'
import { BinaryQueryMessage, FetchRawBlockMessage, PrecisionLength } from '../common/constant'
import { BinaryQueryMessage, FetchRawBlockMessage, PrecisionLength, TSDB_OPTION_CONNECTION } from '../common/constant'
import logger from '../common/log'

export class WsSql{
Expand All @@ -31,7 +31,16 @@ export class WsSql{
await wsSql._wsClient.checkVersion();
if(database && database.length > 0) {
await wsSql.exec(`use ${database}`);
} else {
await wsSql.exec('use information_schema');
}
let timezone = wsConfig.getTimezone();
if (timezone && timezone.length > 0) {
await wsSql._wsClient.setOptionConnection(TSDB_OPTION_CONNECTION.TSDB_OPTION_CONNECTION_TIMEZONE, timezone);
} else {
await wsSql._wsClient.setOptionConnection(TSDB_OPTION_CONNECTION.TSDB_OPTION_CONNECTION_TIMEZONE, null);
}

return wsSql;
} catch (e: any) {
logger.error(`WsSql open is failed, ${e.code}, ${e.message}`);
Expand Down
26 changes: 26 additions & 0 deletions nodejs/test/bulkPulling/sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ describe('TDWebSocket.WsSql()', () => {
conf.setUser('root');
conf.setPwd('taosdata');
conf.setDb('power');
conf.setTimezone('America/New_York');
conf.setTimeOut(6000);
wsSql = await WsSql.open(conf)
expect(wsSql.state()).toBeGreaterThan(0)
let wsRows = await wsSql.query('select timezone()')
while (await wsRows.next()) {
let result = wsRows.getData()
console.log(result);
expect(result).toBeTruthy()
expect(JSON.stringify(result)).toContain('America/New_York')
}
await wsSql.close();
});

Expand Down Expand Up @@ -80,6 +88,24 @@ describe('TDWebSocket.WsSql()', () => {
}
}
})

test('connect url', async() => {
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()
console.log(version);
expect(version).toBeTruthy()
let wsRows = await wsSql.query('select timezone()')
while (await wsRows.next()) {
let result = wsRows.getData()
console.log(result);
expect(result).toBeTruthy()
expect(JSON.stringify(result)).toContain('Asia/Shanghai')
}
await wsSql.close();
})

test('get taosc version', async() => {
let conf :WSConfig = new WSConfig(dns)
conf.setUser('root')
Expand Down
50 changes: 50 additions & 0 deletions nodejs/test/bulkPulling/stmt.type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,56 @@ describe('TDWebSocket.Stmt()', () => {

})

test('test bind exception cases', async() => {
let wsConf = new WSConfig(dsn);
let connector = await WsSql.open(wsConf)
let stmt = await connector.stmtInit()
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);
Expand Down