Skip to content

Commit 740bfdd

Browse files
committed
feat: add decimal support in Stmt2 with validation and encoding logic
1 parent bbaaeb5 commit 740bfdd

6 files changed

Lines changed: 441 additions & 1 deletion

File tree

nodejs/src/common/constant.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ export const TDengineTypeName: IndexableString = {
3434
14: "BIGINT UNSIGNED",
3535
15: "JSON",
3636
16: "VARBINARY",
37+
17: "DECIMAL",
3738
18: "BLOB",
3839
20: "GEOMETRY",
40+
21: "DECIMAL64",
3941
};
4042

4143
export const ColumnsBlockType: StringIndexable = {
@@ -105,6 +107,8 @@ export const TDengineTypeLength: NumberIndexable = {
105107
[TDengineTypeCode.SMALLINT_UNSIGNED]: 2,
106108
[TDengineTypeCode.INT_UNSIGNED]: 4,
107109
[TDengineTypeCode.BIGINT_UNSIGNED]: 8,
110+
[TDengineTypeCode.DECIMAL]: 16,
111+
[TDengineTypeCode.DECIMAL64]: 8,
108112
};
109113

110114
export const PrecisionLength: StringIndexable = {

nodejs/src/stmt/wsParams2.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {
22
ColumnsBlockType,
33
FieldBindType,
44
PrecisionLength,
5+
TDengineTypeCode,
6+
TDengineTypeName,
57
} from "../common/constant";
68
import { ErrorCode, TaosError } from "../common/wsError";
79
import { isEmpty } from "../common/utils";
@@ -105,6 +107,29 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder {
105107
}
106108
}
107109

110+
setDecimal(params: any[]) {
111+
if (!params || params.length == 0) {
112+
throw new TaosError(
113+
ErrorCode.ERR_INVALID_PARAMS,
114+
"SetDecimalColumn params is invalid!"
115+
);
116+
}
117+
for (let i = 0; i < params.length; i++) {
118+
if (!isEmpty(params[i]) && typeof params[i] !== "string") {
119+
throw new TaosError(
120+
ErrorCode.ERR_INVALID_PARAMS,
121+
"SetDecimalColumn params is invalid!"
122+
);
123+
}
124+
}
125+
this.addParams(
126+
params,
127+
TDengineTypeName[TDengineTypeCode.DECIMAL],
128+
0,
129+
TDengineTypeCode.DECIMAL
130+
);
131+
}
132+
108133
encode(): void {
109134
this.paramIndex = 0;
110135
if (!this._fieldParams || this._fieldParams.length == 0) {
@@ -130,6 +155,21 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder {
130155
continue;
131156
}
132157

158+
if (
159+
fieldParam.columnType === TDengineTypeCode.DECIMAL ||
160+
fieldParam.columnType === TDengineTypeCode.DECIMAL64
161+
) {
162+
this._params.push(
163+
this.encodeVarColumns(
164+
fieldParam.params,
165+
fieldParam.dataType,
166+
fieldParam.typeLen,
167+
fieldParam.columnType
168+
)
169+
);
170+
continue;
171+
}
172+
133173
let isVarType = _isVarType(fieldParam.columnType);
134174
if (isVarType == ColumnsBlockType.SOLID) {
135175
if (fieldParam.dataType === "TIMESTAMP") {
@@ -198,6 +238,7 @@ export class Stmt2BindParams extends StmtBindParams implements IDataEncoder {
198238
}
199239
} else {
200240
isNull.push(1);
241+
dataLengths.push(0);
201242
}
202243
}
203244
this._dataTotalLen += totalLength;

nodejs/src/stmt/wsParamsBase.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,13 @@ export abstract class StmtBindParams {
326326
this.addParams(params, TDengineTypeName[20], 0, TDengineTypeCode.GEOMETRY);
327327
}
328328

329+
setDecimal(params: any[]) {
330+
throw new TaosError(
331+
ErrorCode.ERR_UNSUPPORTED_TDENGINE_TYPE,
332+
"setDecimal is not supported in stmt1, please use stmt2 instead!"
333+
);
334+
}
335+
329336
setTimestamp(params: any[]) {
330337
if (!params || params.length == 0) {
331338
throw new TaosError(

nodejs/src/stmt/wsStmt2.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import JSONBig from "json-bigint";
22
import { WsClient } from "../client/wsClient";
3-
import { FieldBindType, PrecisionLength } from "../common/constant";
3+
import {
4+
FieldBindType,
5+
PrecisionLength,
6+
TDengineTypeCode,
7+
} from "../common/constant";
48
import logger from "../common/log";
59
import { ReqId } from "../common/reqid";
610
import {
@@ -222,6 +226,16 @@ export class WsStmt2 implements WsStmt {
222226
}
223227

224228
if (this._isInsert && this.fields && paramsArray.getBindCount() == this.fields.length) {
229+
for (let j = 0; j < paramsArray._fieldParams.length; j++) {
230+
const fieldParam = paramsArray._fieldParams[j];
231+
if (!fieldParam) {
232+
continue;
233+
}
234+
this.overrideDecimalColumnType(
235+
fieldParam,
236+
this.fields[j].field_type
237+
);
238+
}
225239
const tableNameIndex = this._toBeBindTableNameIndex;
226240
if (tableNameIndex === null || tableNameIndex === undefined) {
227241
throw new TaosResultError(
@@ -277,12 +291,42 @@ export class WsStmt2 implements WsStmt {
277291
}
278292
}
279293
} else {
294+
if (this._isInsert && this.fields) {
295+
const colFields = this.fields.filter(
296+
(f) => f.bind_type === FieldBindType.TAOS_FIELD_COL
297+
);
298+
for (let k = 0; k < paramsArray._fieldParams.length; k++) {
299+
const fieldParam = paramsArray._fieldParams[k];
300+
if (!fieldParam) {
301+
continue;
302+
}
303+
this.overrideDecimalColumnType(
304+
fieldParam,
305+
colFields[k]?.field_type
306+
);
307+
}
308+
}
280309
await this._currentTableInfo.setParams(paramsArray);
281310
}
282311

283312
return Promise.resolve();
284313
}
285314

315+
private overrideDecimalColumnType(
316+
fieldParam: FieldBindParams,
317+
fieldType: number | undefined | null
318+
): void {
319+
if (fieldParam.columnType !== TDengineTypeCode.DECIMAL) {
320+
return;
321+
}
322+
if (
323+
fieldType === TDengineTypeCode.DECIMAL ||
324+
fieldType === TDengineTypeCode.DECIMAL64
325+
) {
326+
fieldParam.columnType = fieldType;
327+
}
328+
}
329+
286330
async batch(): Promise<void> {
287331
Promise.resolve();
288332
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { FieldBindType, TDengineTypeCode } from "@src/common/constant";
2+
import { StmtFieldInfo } from "@src/stmt/wsProto";
3+
import { Stmt1BindParams } from "@src/stmt/wsParams1";
4+
import { Stmt2BindParams } from "@src/stmt/wsParams2";
5+
import { WsStmt2 } from "@src/stmt/wsStmt2";
6+
7+
function createMockWsClient() {
8+
return {
9+
getState: jest.fn(() => 1),
10+
connect: jest.fn(async () => { }),
11+
checkVersion: jest.fn(async () => { }),
12+
exec: jest.fn(async () => ({ totalTime: 0, msg: { code: 0, message: "", timing: 0, stmt_id: 1 } })),
13+
execNoResp: jest.fn(async () => { }),
14+
sendBinaryMsg: jest.fn(async () => ({ totalTime: 0, msg: { code: 0, message: "", timing: 0, stmt_id: 1 } })),
15+
waitForReady: jest.fn(async () => { }),
16+
isNetworkError: jest.fn((_err: unknown) => false),
17+
getReconnectRetries: jest.fn(() => 5),
18+
};
19+
}
20+
21+
function createInsertFields(): StmtFieldInfo[] {
22+
return [
23+
{
24+
name: "tbname",
25+
field_type: TDengineTypeCode.VARCHAR,
26+
precision: 0,
27+
scale: 0,
28+
bytes: 0,
29+
bind_type: FieldBindType.TAOS_FIELD_TBNAME,
30+
},
31+
{
32+
name: "location",
33+
field_type: TDengineTypeCode.VARCHAR,
34+
precision: 0,
35+
scale: 0,
36+
bytes: 0,
37+
bind_type: FieldBindType.TAOS_FIELD_TAG,
38+
},
39+
{
40+
name: "gid",
41+
field_type: TDengineTypeCode.INT,
42+
precision: 0,
43+
scale: 0,
44+
bytes: 0,
45+
bind_type: FieldBindType.TAOS_FIELD_TAG,
46+
},
47+
{
48+
name: "d64",
49+
field_type: TDengineTypeCode.DECIMAL64,
50+
precision: 0,
51+
scale: 0,
52+
bytes: 0,
53+
bind_type: FieldBindType.TAOS_FIELD_COL,
54+
},
55+
{
56+
name: "v",
57+
field_type: TDengineTypeCode.INT,
58+
precision: 0,
59+
scale: 0,
60+
bytes: 0,
61+
bind_type: FieldBindType.TAOS_FIELD_COL,
62+
},
63+
];
64+
}
65+
66+
function createBareStmt(fields: StmtFieldInfo[], isInsert: boolean = true) {
67+
const stmt = new (WsStmt2 as any)(createMockWsClient());
68+
stmt._stmt_id = 1n;
69+
stmt.fields = fields;
70+
stmt._isInsert = isInsert;
71+
stmt._toBeBindTableNameIndex = 0;
72+
stmt._toBeBindTagCount = fields.filter(
73+
(f) => f.bind_type === FieldBindType.TAOS_FIELD_TAG
74+
).length;
75+
stmt._toBeBindColCount = fields.filter(
76+
(f) => f.bind_type === FieldBindType.TAOS_FIELD_COL
77+
).length;
78+
return stmt;
79+
}
80+
81+
describe("Stmt2 decimal bind behavior (mock)", () => {
82+
test("stmt1 setDecimal should throw unsupported error", () => {
83+
const params = new Stmt1BindParams();
84+
expect(() => {
85+
params.setDecimal(["1.2300"]);
86+
}).toThrow("setDecimal is not supported in stmt1, please use stmt2 instead!");
87+
});
88+
89+
test("stmt2 setDecimal should reject non-string values", () => {
90+
const params = new Stmt2BindParams();
91+
expect(() => {
92+
params.setDecimal([]);
93+
}).toThrow("SetDecimalColumn params is invalid!");
94+
95+
expect(() => {
96+
params.setDecimal([123]);
97+
}).toThrow("SetDecimalColumn params is invalid!");
98+
});
99+
100+
test("stmt2 setDecimal should be encoded as variable-length data", () => {
101+
const params = new Stmt2BindParams();
102+
params.setDecimal(["1.2300", null, "-0.0001"]);
103+
params.encode();
104+
const cols = params.getParams();
105+
expect(cols.length).toBe(1);
106+
expect(cols[0].type).toBe(TDengineTypeCode.DECIMAL);
107+
expect(cols[0]._haveLength).toBe(1);
108+
expect(cols[0]._rows).toBe(3);
109+
});
110+
111+
test("bind should override DECIMAL to real column decimal type in full-binding insert", async () => {
112+
const fields = createInsertFields();
113+
const stmt = createBareStmt(fields, true);
114+
const params = new Stmt2BindParams(fields.length, 13, fields);
115+
116+
params.setVarchar(["d0"]);
117+
params.setVarchar(["beijing"]);
118+
params.setInt([1]);
119+
params.setDecimal(["123.456700"]);
120+
params.setInt([10]);
121+
122+
await stmt.bind(params);
123+
124+
const tableInfo = stmt._stmtTableInfo.get("d0");
125+
const colParams = tableInfo?.getParams();
126+
expect(colParams?._fieldParams?.length).toBe(2);
127+
expect(colParams?._fieldParams?.[0].columnType).toBe(
128+
TDengineTypeCode.DECIMAL64
129+
);
130+
});
131+
132+
test("bind should override DECIMAL to real column decimal type in non-full-binding insert", async () => {
133+
const fields = createInsertFields();
134+
const stmt = createBareStmt(fields, true);
135+
const params = new Stmt2BindParams();
136+
137+
params.setDecimal(["-0.000001"]);
138+
params.setInt([11]);
139+
140+
await stmt.bind(params);
141+
142+
const bindParams = stmt._currentTableInfo.getParams();
143+
expect(bindParams?._fieldParams?.[0].columnType).toBe(
144+
TDengineTypeCode.DECIMAL64
145+
);
146+
expect(bindParams?._fieldParams?.[1].columnType).toBe(
147+
TDengineTypeCode.INT
148+
);
149+
});
150+
151+
test("query mode should keep DECIMAL default type", async () => {
152+
const fields = createInsertFields();
153+
const stmt = createBareStmt(fields, false);
154+
const params = new Stmt2BindParams();
155+
params.setDecimal(["99.0001"]);
156+
157+
await stmt.bind(params);
158+
159+
const bindParams = stmt._currentTableInfo.getParams();
160+
expect(bindParams?._fieldParams?.[0].columnType).toBe(
161+
TDengineTypeCode.DECIMAL
162+
);
163+
});
164+
});

0 commit comments

Comments
 (0)