-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathDBSQLParameter.test.ts
More file actions
267 lines (250 loc) · 11.4 KB
/
Copy pathDBSQLParameter.test.ts
File metadata and controls
267 lines (250 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import { expect } from 'chai';
import Int64 from 'node-int64';
import { TSparkParameterValue, TSparkParameter } from '../../thrift/TCLIService_types';
import {
DBSQLParameter,
DBSQLParameterType,
DBSQLParameterValue,
TypedValueInput,
convertOrdinalParametersToTypedValueInputs,
} from '../../lib/DBSQLParameter';
describe('DBSQLParameter', () => {
it('should infer types correctly', () => {
const cases: Array<[DBSQLParameterValue, TSparkParameter]> = [
[
false,
new TSparkParameter({
type: DBSQLParameterType.BOOLEAN,
value: new TSparkParameterValue({ stringValue: 'FALSE' }),
}),
],
[
true,
new TSparkParameter({
type: DBSQLParameterType.BOOLEAN,
value: new TSparkParameterValue({ stringValue: 'TRUE' }),
}),
],
[
123,
new TSparkParameter({
type: DBSQLParameterType.INTEGER,
value: new TSparkParameterValue({ stringValue: '123' }),
}),
],
[
3.14,
new TSparkParameter({
type: DBSQLParameterType.DOUBLE,
value: new TSparkParameterValue({ stringValue: '3.14' }),
}),
],
[
BigInt(1234),
new TSparkParameter({
type: DBSQLParameterType.BIGINT,
value: new TSparkParameterValue({ stringValue: '1234' }),
}),
],
[
new Int64(1234),
new TSparkParameter({
type: DBSQLParameterType.BIGINT,
value: new TSparkParameterValue({ stringValue: '1234' }),
}),
],
[
new Date('2023-09-06T03:14:27.843Z'),
new TSparkParameter({
type: DBSQLParameterType.TIMESTAMP,
value: new TSparkParameterValue({ stringValue: '2023-09-06T03:14:27.843Z' }),
}),
],
[
'Hello',
new TSparkParameter({
type: DBSQLParameterType.STRING,
value: new TSparkParameterValue({ stringValue: 'Hello' }),
}),
],
];
for (const [value, expectedParam] of cases) {
const dbsqlParam = new DBSQLParameter({ value });
expect(dbsqlParam.toSparkParameter()).to.deep.equal(expectedParam);
}
});
it('should use provided type', () => {
const expectedType = '_CUSTOM_TYPE_' as DBSQLParameterType; // it doesn't have to be valid type name, just any string
const cases: Array<[DBSQLParameterValue, TSparkParameter]> = [
[false, new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: 'FALSE' }) })],
[true, new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: 'TRUE' }) })],
[123, new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: '123' }) })],
[3.14, new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: '3.14' }) })],
[
BigInt(1234),
new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: '1234' }) }),
],
[
new Int64(1234),
new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: '1234' }) }),
],
[
new Date('2023-09-06T03:14:27.843Z'),
new TSparkParameter({
type: expectedType,
value: new TSparkParameterValue({ stringValue: '2023-09-06T03:14:27.843Z' }),
}),
],
['Hello', new TSparkParameter({ type: expectedType, value: new TSparkParameterValue({ stringValue: 'Hello' }) })],
];
for (const [value, expectedParam] of cases) {
const dbsqlParam = new DBSQLParameter({ type: expectedType, value });
expect(dbsqlParam.toSparkParameter()).to.deep.equal(expectedParam);
}
});
// ─── toNapiTypedValue (SEA-backend codec input) ─────────────────────
//
// These tests pin the wire-level alignment between the Thrift emitter
// (`toSparkParameter`) and the SEA emitter (`toNapiTypedValue`).
// Divergence between the two is a parity regression — see C5 cluster
// in sea-workflow/decisions/2026-05-28-autonomous-parity-decisions.md.
describe('toNapiTypedValue', () => {
it('infers types in sync with toSparkParameter (10-type C5 matrix)', () => {
const cases: Array<[DBSQLParameterValue, TypedValueInput]> = [
// BOOLEAN — note "TRUE"/"FALSE" casing matches Thrift; the napi
// codec's parse_bool is case-insensitive so this is wire-safe.
[false, { sqlType: DBSQLParameterType.BOOLEAN, value: 'FALSE' }],
[true, { sqlType: DBSQLParameterType.BOOLEAN, value: 'TRUE' }],
// INTEGER from a plain JS number (`Number.isInteger(123) === true`).
[123, { sqlType: DBSQLParameterType.INTEGER, value: '123' }],
// DOUBLE from a non-integer JS number.
[3.14, { sqlType: DBSQLParameterType.DOUBLE, value: '3.14' }],
// BIGINT from JS bigint.
[BigInt(9999999999), { sqlType: DBSQLParameterType.BIGINT, value: '9999999999' }],
// BIGINT from Int64 (the node-int64 path the Thrift driver uses).
[new Int64(1234), { sqlType: DBSQLParameterType.BIGINT, value: '1234' }],
// TIMESTAMP from a JS Date — `.toISOString()` emits the trailing
// `Z`, the napi codec strips it before NaiveDateTime parsing.
[
new Date('2023-09-06T03:14:27.843Z'),
{ sqlType: DBSQLParameterType.TIMESTAMP, value: '2023-09-06T03:14:27.843Z' },
],
// STRING fallback.
['Hello', { sqlType: DBSQLParameterType.STRING, value: 'Hello' }],
];
for (const [value, expected] of cases) {
const param = new DBSQLParameter({ value });
expect(param.toNapiTypedValue()).to.deep.equal(expected);
}
});
it('honours an explicitly-set type', () => {
const customType = 'DECIMAL(10,2)' as DBSQLParameterType;
const param = new DBSQLParameter({ type: customType, value: '-123.45' });
expect(param.toNapiTypedValue()).to.deep.equal({
sqlType: 'DECIMAL(10,2)',
value: '-123.45',
});
});
it('emits VOID/null for an explicit VOID type regardless of value', () => {
const param = new DBSQLParameter({ type: DBSQLParameterType.VOID, value: 'ignored' });
expect(param.toNapiTypedValue()).to.deep.equal({ sqlType: 'VOID', value: null });
});
it('emits VOID/null for an undefined or null value', () => {
for (const value of [undefined, null] as Array<DBSQLParameterValue>) {
const param = new DBSQLParameter({ value });
expect(param.toNapiTypedValue()).to.deep.equal({ sqlType: 'VOID', value: null });
}
});
it('alignment regression-lock: every type-inference branch matches the Thrift emitter', () => {
// Every value the Thrift emitter handles must round-trip to the
// same wire-level (sqlType, stringValue) pair through the napi
// emitter. This is the one-liner that catches a future
// refactor that diverges the two stringification paths.
const values: Array<DBSQLParameterValue> = [
false,
true,
0,
1,
-1,
3.14,
BigInt(0),
new Int64(0),
new Date(0),
'',
];
for (const value of values) {
const param = new DBSQLParameter({ value });
const thrift = param.toSparkParameter();
const napi = param.toNapiTypedValue();
expect(napi.sqlType, `sqlType for ${String(value)}`).to.equal(thrift.type);
expect(napi.value, `value for ${String(value)}`).to.equal(thrift.value?.stringValue);
}
});
});
describe('convertOrdinalParametersToTypedValueInputs', () => {
it('returns [] for undefined input', () => {
expect(convertOrdinalParametersToTypedValueInputs(undefined)).to.deep.equal([]);
});
it('returns [] for an empty array', () => {
expect(convertOrdinalParametersToTypedValueInputs([])).to.deep.equal([]);
});
it('wraps bare JS values in a DBSQLParameter for stringification', () => {
const got = convertOrdinalParametersToTypedValueInputs([42, 'hello', true, null]);
expect(got).to.deep.equal([
{ sqlType: DBSQLParameterType.INTEGER, value: '42' },
{ sqlType: DBSQLParameterType.STRING, value: 'hello' },
{ sqlType: DBSQLParameterType.BOOLEAN, value: 'TRUE' },
// null → VOID/null per the toNapiTypedValue contract.
{ sqlType: 'VOID', value: null },
]);
});
it('passes DBSQLParameter instances through with their explicit type', () => {
const decimal = new DBSQLParameter({
type: 'DECIMAL(10,2)' as DBSQLParameterType,
value: '-123.45',
});
const got = convertOrdinalParametersToTypedValueInputs([decimal, BigInt('9999999999')]);
expect(got).to.deep.equal([
{ sqlType: 'DECIMAL(10,2)', value: '-123.45' },
{ sqlType: DBSQLParameterType.BIGINT, value: '9999999999' },
]);
});
it('preserves order — index i in the input is index i on the wire', () => {
// The napi codec's `positional_params` is 1-based (index i ↔
// ordinal i+1). The JS adapter is responsible for preserving the
// input ordering — this test pins that contract.
const got = convertOrdinalParametersToTypedValueInputs([1, 2, 3, 4, 5]);
expect(got.map((x) => x.value)).to.deep.equal(['1', '2', '3', '4', '5']);
});
it('c5 ten-type matrix — one positional value per basic SQL type', () => {
// Mirrors the kernel-napi `c5_ten_type_matrix_round_trips` test
// (databricks-sql-kernel/napi/src/params.rs) one-for-one. The
// wire-level (sqlType, value) pairs emitted here must be the
// exact input the kernel codec accepts. BINARY is omitted —
// the napi codec rejects it at bind time, see the cross-driver
// validation reply from python-sea-oracle for the contract.
const inputs: Array<DBSQLParameter | DBSQLParameterValue> = [
new DBSQLParameter({ type: DBSQLParameterType.INTEGER, value: 42 }),
new DBSQLParameter({ type: DBSQLParameterType.BIGINT, value: BigInt('9999999999') }),
new DBSQLParameter({ type: DBSQLParameterType.FLOAT, value: 3.14 }),
new DBSQLParameter({ type: DBSQLParameterType.DOUBLE, value: 2.718281828459045 }),
new DBSQLParameter({ type: DBSQLParameterType.STRING, value: 'hello world' }),
new DBSQLParameter({ type: DBSQLParameterType.BOOLEAN, value: true }),
new DBSQLParameter({ type: DBSQLParameterType.DATE, value: '2026-05-15' }),
new DBSQLParameter({ type: DBSQLParameterType.TIMESTAMP, value: '2026-05-15 12:30:45' }),
new DBSQLParameter({ type: 'DECIMAL(10,2)' as DBSQLParameterType, value: '-123.45' }),
];
const got = convertOrdinalParametersToTypedValueInputs(inputs);
expect(got).to.have.lengthOf(9);
expect(got[0]).to.deep.equal({ sqlType: 'INTEGER', value: '42' });
expect(got[1]).to.deep.equal({ sqlType: 'BIGINT', value: '9999999999' });
expect(got[2]).to.deep.equal({ sqlType: 'FLOAT', value: '3.14' });
expect(got[3]).to.deep.equal({ sqlType: 'DOUBLE', value: '2.718281828459045' });
expect(got[4]).to.deep.equal({ sqlType: 'STRING', value: 'hello world' });
expect(got[5]).to.deep.equal({ sqlType: 'BOOLEAN', value: 'TRUE' });
expect(got[6]).to.deep.equal({ sqlType: 'DATE', value: '2026-05-15' });
expect(got[7]).to.deep.equal({ sqlType: 'TIMESTAMP', value: '2026-05-15 12:30:45' });
expect(got[8]).to.deep.equal({ sqlType: 'DECIMAL(10,2)', value: '-123.45' });
});
});
});