-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathSeaPositionalParams.ts
More file actions
124 lines (117 loc) · 5.54 KB
/
Copy pathSeaPositionalParams.ts
File metadata and controls
124 lines (117 loc) · 5.54 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
// Copyright (c) 2026 Databricks, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { DBSQLParameter, DBSQLParameterValue } from '../DBSQLParameter';
import ParameterError from '../errors/ParameterError';
import { SeaNativeTypedValueInput, SeaNativeNamedTypedValueInput } from './SeaNativeLoader';
import assertBindableValue from './SeaInputValidation';
/**
* Derive `(precision,scale)` from a decimal value string for the SEA
* `DECIMAL(p,s)` type name — the kernel param codec requires the parenthesised
* form (plain `"DECIMAL"` is rejected) so it preserves the caller's fractional
* digits. `"99.99"` ⇒ `"4,2"`; `"-123"` ⇒ `"3,0"`; `"0.00001"` ⇒ `"5,5"`.
*
* Precision is significant integer digits (insignificant leading zeros
* stripped, matching Spark's decimal-literal rule) plus the fractional scale.
* The value is NOT clamped: a non-numeric value, or one that needs precision >
* the Databricks maximum of 38, throws `ParameterError` at bind time rather
* than emitting an inconsistent `DECIMAL(38,…)` type alongside an
* unrepresentable value (which the kernel would reject or silently truncate).
*/
function decimalPrecisionScale(v: string): string {
// Accept an optional sign + plain decimal numeral only. Exponential form
// ("1e+21"), empty, and non-numeric ("abc") are rejected with a clear error
// instead of mis-deriving (p,s) and surfacing an opaque kernel failure.
const m = /^[+-]?(\d*)(?:\.(\d+))?$/.exec(v.trim());
const intPart = m?.[1] ?? '';
const fracPart = m?.[2] ?? '';
if (!m || (intPart === '' && fracPart === '')) {
throw new ParameterError(
`DECIMAL parameter value "${v}" is not a plain decimal numeral (expected [+-]?digits[.digits]).`,
);
}
const scale = fracPart.length;
// Significant integer digits: drop insignificant leading zeros ("007" → 1,
// "0" / "" → 0) so e.g. 0.00001 is DECIMAL(5,5), not DECIMAL(6,5).
const significantIntDigits = intPart.replace(/^0+/, '').length;
const precision = Math.max(significantIntDigits + scale, 1);
if (precision > 38) {
throw new ParameterError(
`DECIMAL parameter value "${v}" needs precision ${precision}, exceeding the Databricks maximum of 38. ` +
'Round/scale the value or bind it as a STRING.',
);
}
return `${precision},${scale}`;
}
/**
* Reduce a `DBSQLParameter | DBSQLParameterValue` to the napi
* `TypedValueInput` (`{ sqlType, value? }`) the kernel's positional-param
* codec (`parse_typed_value`) accepts. Reuses `DBSQLParameter.toSparkParameter`
* — the same type-inference + value-stringification the Thrift backend uses —
* then adapts the type name to the codec's expectations:
* - DECIMAL → `DECIMAL(p,s)` (parenthesised form required)
* - INTERVAL * → `INTERVAL` (the codec's single interval type name)
* - a missing value ⇒ SQL NULL (`parse_typed_value` maps `value: None` to NULL).
*/
function toTypedValueInput(value: DBSQLParameter | DBSQLParameterValue): SeaNativeTypedValueInput {
const param = value instanceof DBSQLParameter ? value : new DBSQLParameter({ value });
const spark = param.toSparkParameter();
const stringValue = spark.value?.stringValue ?? undefined;
// NULL: no value (and `VOID` ignores any type), matching toSparkParameter's
// type/value-less shape for null/undefined.
if (stringValue === undefined || stringValue === null) {
return { sqlType: 'VOID' };
}
let sqlType = spark.type ?? 'STRING';
const upper = sqlType.toUpperCase();
if (upper === 'DECIMAL') {
sqlType = `DECIMAL(${decimalPrecisionScale(stringValue)})`;
} else if (upper.startsWith('INTERVAL')) {
sqlType = 'INTERVAL';
}
return { sqlType, value: stringValue };
}
/**
* Convert the public `ordinalParameters` option into the napi
* `positionalParams` array (1-based `?` placeholders). Returns `undefined`
* when none were supplied, so the caller can keep the minimal no-options
* call shape.
*/
export function buildSeaPositionalParams(
ordinalParameters?: Array<DBSQLParameter | DBSQLParameterValue>,
): Array<SeaNativeTypedValueInput> | undefined {
if (ordinalParameters === undefined || ordinalParameters.length === 0) {
return undefined;
}
return ordinalParameters.map((value, i) => {
assertBindableValue(value, `ordinalParameters[${i}]`);
return toTypedValueInput(value);
});
}
/**
* Convert the public `namedParameters` option (`Record<name, value>`) into
* the napi `namedParams` array (`:name` placeholders). Each value reuses the
* same `toTypedValueInput` mapping (DECIMAL → DECIMAL(p,s), NULL → VOID, …),
* then carries its name. Returns `undefined` when none were supplied.
*/
export function buildSeaNamedParams(
namedParameters?: Record<string, DBSQLParameter | DBSQLParameterValue>,
): Array<SeaNativeNamedTypedValueInput> | undefined {
if (namedParameters === undefined || Object.keys(namedParameters).length === 0) {
return undefined;
}
return Object.keys(namedParameters).map((name) => {
assertBindableValue(namedParameters[name], `namedParameters[${name}]`);
return { name, ...toTypedValueInput(namedParameters[name]) };
});
}