-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathDBSQLParameter.ts
More file actions
228 lines (205 loc) · 7.79 KB
/
Copy pathDBSQLParameter.ts
File metadata and controls
228 lines (205 loc) · 7.79 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
import Int64 from 'node-int64';
import { TSparkParameter, TSparkParameterValue } from '../thrift/TCLIService_types';
export type DBSQLParameterValue = undefined | null | boolean | number | bigint | Int64 | Date | string;
/**
* Wire shape expected by the SEA napi codec
* (`databricks-sql-kernel/napi/src/params.rs::TypedValueInput`). The Rust
* side parses `value` per `sqlType`; we stringify the JS value the same
* way `toSparkParameter` does for Thrift, then hand the pair to napi.
*
* Why a parallel converter rather than re-using `toSparkParameter` and
* unwrapping the wire-Thrift `TSparkParameter`: napi consumes a flat
* `{ sqlType, value: string | null }` POJO. Mining the value out of a
* `TSparkParameter` (which wraps it in `TSparkParameterValue.stringValue`
* and may use `name` for named-binding mode) requires more glue than
* just emitting the SEA shape directly. The two emitters share the
* same stringification rules — boolean → "TRUE"/"FALSE", Date →
* `toISOString()`, etc.
*/
export interface TypedValueInput {
/**
* Canonical Databricks SQL type name (`"INT"`, `"STRING"`,
* `"DECIMAL(10,2)"`, …). The napi codec is case-insensitive for the
* simple variants and requires the parenthesised form for DECIMAL.
*/
sqlType: string;
/**
* String-encoded literal, or `null` for SQL NULL. The Rust codec
* short-circuits to `TypedValue::Null` regardless of `sqlType` when
* this is `null` — matches the Thrift `TSparkParameter(name)` (no
* type, no value) shape on the wire.
*/
value: string | null;
}
export enum DBSQLParameterType {
VOID = 'VOID', // aka NULL
STRING = 'STRING',
DATE = 'DATE',
TIMESTAMP = 'TIMESTAMP',
FLOAT = 'FLOAT',
DECIMAL = 'DECIMAL',
DOUBLE = 'DOUBLE',
INTEGER = 'INTEGER',
BIGINT = 'BIGINT',
SMALLINT = 'SMALLINT',
TINYINT = 'TINYINT',
BOOLEAN = 'BOOLEAN',
INTERVALMONTH = 'INTERVAL MONTH',
INTERVALDAY = 'INTERVAL DAY',
}
interface DBSQLParameterOptions {
type?: DBSQLParameterType;
value: DBSQLParameterValue;
}
interface ToSparkParameterOptions {
name?: string;
}
export class DBSQLParameter {
public readonly type?: string;
public readonly value: DBSQLParameterValue;
constructor({ type, value }: DBSQLParameterOptions) {
this.type = type;
this.value = value;
}
public toSparkParameter({ name }: ToSparkParameterOptions = {}): TSparkParameter {
// If VOID type was set explicitly - ignore value
if (this.type === DBSQLParameterType.VOID) {
return new TSparkParameter({ name }); // for NULL neither `type` nor `value` should be set
}
// Infer NULL values
if (this.value === undefined || this.value === null) {
return new TSparkParameter({ name }); // for NULL neither `type` nor `value` should be set
}
if (typeof this.value === 'boolean') {
return new TSparkParameter({
name,
type: this.type ?? DBSQLParameterType.BOOLEAN,
value: new TSparkParameterValue({
stringValue: this.value ? 'TRUE' : 'FALSE',
}),
});
}
if (typeof this.value === 'number') {
return new TSparkParameter({
name,
type: this.type ?? (Number.isInteger(this.value) ? DBSQLParameterType.INTEGER : DBSQLParameterType.DOUBLE),
value: new TSparkParameterValue({
stringValue: Number(this.value).toString(),
}),
});
}
if (this.value instanceof Int64 || typeof this.value === 'bigint') {
return new TSparkParameter({
name,
type: this.type ?? DBSQLParameterType.BIGINT,
value: new TSparkParameterValue({
stringValue: this.value.toString(),
}),
});
}
if (this.value instanceof Date) {
return new TSparkParameter({
name,
type: this.type ?? DBSQLParameterType.TIMESTAMP,
value: new TSparkParameterValue({
stringValue: this.value.toISOString(),
}),
});
}
return new TSparkParameter({
name,
type: this.type ?? DBSQLParameterType.STRING,
value: new TSparkParameterValue({
stringValue: this.value,
}),
});
}
/**
* SEA-backend sibling of `toSparkParameter`. Emits the flat
* `{ sqlType, value }` shape the napi codec consumes.
*
* Stringification rules are kept in lock-step with `toSparkParameter`
* so a positional parameter bound on the Thrift backend and on the
* SEA backend hits the server with the same wire-level type-name +
* value string. Divergence between the two emitters here would
* silently re-introduce the kind of cross-backend behavior split the
* driver-test parity suite is built to catch.
*
* @returns null for SQL NULL (caller is expected to emit it as
* `{ sqlType: "VOID", value: null }` or to skip the entry,
* depending on call-site convention). This method itself never
* throws; unsupported value shapes fall through the STRING default
* to match the Thrift emitter's behaviour.
*/
public toNapiTypedValue(): TypedValueInput {
// VOID literal — explicit NULL, the napi codec short-circuits.
if (this.type === DBSQLParameterType.VOID) {
return { sqlType: 'VOID', value: null };
}
// Inferred NULL — same shape as VOID. The napi codec's contract is
// that `value: null` produces `TypedValue::Null` regardless of
// sqlType, so any non-empty sqlType would work here; we emit VOID
// for consistency with the explicit-NULL path above.
if (this.value === undefined || this.value === null) {
return { sqlType: 'VOID', value: null };
}
if (typeof this.value === 'boolean') {
return {
sqlType: this.type ?? DBSQLParameterType.BOOLEAN,
// Thrift emits "TRUE" / "FALSE"; the napi `parse_bool` accepts
// both "true"/"false" and "TRUE"/"FALSE" via its case-insensitive
// match. Keep the casing aligned with the Thrift emitter so any
// log scrape that diffs the two wires sees identical strings.
value: this.value ? 'TRUE' : 'FALSE',
};
}
if (typeof this.value === 'number') {
return {
sqlType: this.type ?? (Number.isInteger(this.value) ? DBSQLParameterType.INTEGER : DBSQLParameterType.DOUBLE),
value: Number(this.value).toString(),
};
}
if (this.value instanceof Int64 || typeof this.value === 'bigint') {
return {
sqlType: this.type ?? DBSQLParameterType.BIGINT,
value: this.value.toString(),
};
}
if (this.value instanceof Date) {
return {
sqlType: this.type ?? DBSQLParameterType.TIMESTAMP,
value: this.value.toISOString(),
};
}
return {
sqlType: this.type ?? DBSQLParameterType.STRING,
value: this.value,
};
}
}
/**
* Convert the user-facing `ordinalParameters` array into the flat
* `TypedValueInput[]` shape the SEA napi codec accepts.
*
* Mirrors the ordinal-only branch of `lib/DBSQLSession.ts::getQueryParameters`
* — entries may be a `DBSQLParameter` instance or a bare value, and a
* bare value is wrapped in a `DBSQLParameter` for emission. The wrapping
* path is the load-bearing one (today the Node driver's public surface
* accepts bare JS values for positional binding); this helper is the
* single source of truth for how those bare values stringify against
* the napi codec.
*
* Returns an empty array for an undefined / empty input. The caller is
* expected to fall back to a no-positional-params execute in that case.
*/
export function convertOrdinalParametersToTypedValueInputs(
ordinalParameters?: Array<DBSQLParameter | DBSQLParameterValue>,
): Array<TypedValueInput> {
if (ordinalParameters === undefined || ordinalParameters.length === 0) {
return [];
}
return ordinalParameters.map((value) => {
const param = value instanceof DBSQLParameter ? value : new DBSQLParameter({ value });
return param.toNapiTypedValue();
});
}