@databricks/appkit-ui 0.33.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/index.js +1 -0
- package/dist/shared/src/index.js +2 -0
- package/dist/shared/src/sql/helpers.d.ts +113 -8
- package/dist/shared/src/sql/helpers.d.ts.map +1 -1
- package/dist/shared/src/sql/helpers.js +116 -7
- package/dist/shared/src/sql/helpers.js.map +1 -1
- package/dist/shared/src/sql/index.js +1 -0
- package/dist/shared/src/sql/types.d.ts +16 -2
- package/dist/shared/src/sql/types.d.ts.map +1 -1
- package/docs/api/appkit/Variable.sql.md +203 -21
- package/docs/app-management.md +2 -2
- package/docs/plugins/analytics.md +7 -2
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
package/dist/js/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSQLTypeMarker, sql } from "../shared/src/sql/helpers.js";
|
|
2
|
+
import "../shared/src/index.js";
|
|
2
3
|
import { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS } from "./constants.js";
|
|
3
4
|
import { getArrowModule, initializeTypeIdSets } from "./arrow/lazy-arrow.js";
|
|
4
5
|
import { ArrowClient } from "./arrow/arrow-client.js";
|
|
@@ -45,22 +45,127 @@ declare const sql: {
|
|
|
45
45
|
*/
|
|
46
46
|
timestamp(value: Date | string | number): SQLTimestampMarker;
|
|
47
47
|
/**
|
|
48
|
-
* Creates a
|
|
49
|
-
*
|
|
48
|
+
* Creates a numeric type parameter. The wire SQL type is inferred from the
|
|
49
|
+
* value so the parameter binds correctly in any context, including `LIMIT`
|
|
50
|
+
* and `OFFSET`:
|
|
51
|
+
*
|
|
52
|
+
* - JS integer in `[-2^31, 2^31 - 1]` → `INT`
|
|
53
|
+
* - JS integer outside `INT` but within `Number.MAX_SAFE_INTEGER` → `BIGINT`
|
|
54
|
+
* - JS non-integer (`3.14`) → `DOUBLE`
|
|
55
|
+
* - integer-shaped string in `INT` range → `INT` (common HTTP-input case)
|
|
56
|
+
* - integer-shaped string outside `INT` but within `BIGINT` → `BIGINT`
|
|
57
|
+
* - decimal-shaped string (`"123.45"`) → `NUMERIC` (preserves precision)
|
|
58
|
+
*
|
|
59
|
+
* Why default to `INT`? Spark's `LIMIT` and `OFFSET` operators require
|
|
60
|
+
* `IntegerType` specifically — `BIGINT` (`LongType`) is rejected with
|
|
61
|
+
* `INVALID_LIMIT_LIKE_EXPRESSION.DATA_TYPE`. Catalyst auto-widens `INT`
|
|
62
|
+
* to `BIGINT` / `DECIMAL` / `DOUBLE` for wider columns, so `INT` is a
|
|
63
|
+
* strictly better default than `BIGINT`.
|
|
64
|
+
*
|
|
65
|
+
* Throws on `NaN`, `Infinity`, JS integers outside `Number.MAX_SAFE_INTEGER`,
|
|
66
|
+
* integer-shaped strings outside the `BIGINT` range, or non-numeric strings.
|
|
67
|
+
* Reach for `sql.int()`, `sql.bigint()`, `sql.float()`, `sql.double()`, or
|
|
68
|
+
* `sql.numeric()` to override the inferred type.
|
|
69
|
+
*
|
|
50
70
|
* @param value - Number or numeric string
|
|
51
|
-
* @returns Marker
|
|
71
|
+
* @returns Marker for a numeric SQL parameter
|
|
52
72
|
* @example
|
|
53
73
|
* ```typescript
|
|
54
|
-
*
|
|
55
|
-
*
|
|
74
|
+
* sql.number(123); // { __sql_type: "INT", value: "123" }
|
|
75
|
+
* sql.number(3_000_000_000); // { __sql_type: "BIGINT", value: "3000000000" }
|
|
76
|
+
* sql.number(0.5); // { __sql_type: "DOUBLE", value: "0.5" }
|
|
77
|
+
* sql.number("10"); // { __sql_type: "INT", value: "10" }
|
|
78
|
+
* sql.number("123.45"); // { __sql_type: "NUMERIC", value: "123.45" }
|
|
56
79
|
* ```
|
|
80
|
+
*/
|
|
81
|
+
number(value: number | string): SQLNumberMarker;
|
|
82
|
+
/**
|
|
83
|
+
* Creates an `INT` (32-bit signed integer) parameter. Use when the column
|
|
84
|
+
* or context requires `INT` specifically (e.g. legacy schemas, or to make
|
|
85
|
+
* the wire type explicit).
|
|
86
|
+
*
|
|
87
|
+
* Rejects non-integers, values outside `Number.MAX_SAFE_INTEGER` (for
|
|
88
|
+
* number inputs), and values outside the signed 32-bit range
|
|
89
|
+
* `[-2^31, 2^31 - 1]`.
|
|
90
|
+
*
|
|
91
|
+
* @param value - Integer number or integer-shaped string
|
|
92
|
+
* @returns Marker pinned to `INT`
|
|
57
93
|
* @example
|
|
58
94
|
* ```typescript
|
|
59
|
-
*
|
|
60
|
-
*
|
|
95
|
+
* sql.int(42); // { __sql_type: "INT", value: "42" }
|
|
96
|
+
* sql.int("42"); // { __sql_type: "INT", value: "42" }
|
|
61
97
|
* ```
|
|
62
98
|
*/
|
|
63
|
-
|
|
99
|
+
int(value: number | string): SQLNumberMarker & {
|
|
100
|
+
__sql_type: "INT";
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Creates a `BIGINT` (64-bit signed integer) parameter. Accepts JS
|
|
104
|
+
* `bigint` so callers can round-trip values outside `Number.MAX_SAFE_INTEGER`
|
|
105
|
+
* without precision loss; for `number` inputs, requires
|
|
106
|
+
* `Number.isSafeInteger(value)`.
|
|
107
|
+
*
|
|
108
|
+
* Rejects values outside the signed 64-bit range `[-2^63, 2^63 - 1]`.
|
|
109
|
+
*
|
|
110
|
+
* @param value - Integer number, bigint, or integer-shaped string
|
|
111
|
+
* @returns Marker pinned to `BIGINT`
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* sql.bigint(42); // { __sql_type: "BIGINT", value: "42" }
|
|
115
|
+
* sql.bigint(9007199254740993n); // { __sql_type: "BIGINT", value: "9007199254740993" }
|
|
116
|
+
* sql.bigint("9007199254740993"); // { __sql_type: "BIGINT", value: "9007199254740993" }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
bigint(value: number | bigint | string): SQLNumberMarker & {
|
|
120
|
+
__sql_type: "BIGINT";
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Creates a `FLOAT` (single-precision, 32-bit) parameter. Note that JS
|
|
124
|
+
* numbers are 64-bit doubles, so values may be rounded to fit FLOAT
|
|
125
|
+
* precision at bind time.
|
|
126
|
+
*
|
|
127
|
+
* @param value - Number or numeric string
|
|
128
|
+
* @returns Marker pinned to `FLOAT`
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* sql.float(3.14); // { __sql_type: "FLOAT", value: "3.14" }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
float(value: number | string): SQLNumberMarker & {
|
|
135
|
+
__sql_type: "FLOAT";
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Creates a `DOUBLE` (double-precision, 64-bit) parameter. Same precision
|
|
139
|
+
* as a JS `number`, so `sql.double(value)` is exact for any JS number.
|
|
140
|
+
*
|
|
141
|
+
* @param value - Number or numeric string
|
|
142
|
+
* @returns Marker pinned to `DOUBLE`
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* sql.double(3.14); // { __sql_type: "DOUBLE", value: "3.14" }
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
double(value: number | string): SQLNumberMarker & {
|
|
149
|
+
__sql_type: "DOUBLE";
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Creates a `NUMERIC` (fixed-point DECIMAL) parameter. Use when you need
|
|
153
|
+
* exact decimal arithmetic (currency, percentages) — pass values as
|
|
154
|
+
* strings to avoid JS-number precision loss.
|
|
155
|
+
*
|
|
156
|
+
* Note: passing a JS `number` is accepted but lossy for many values
|
|
157
|
+
* (e.g. `0.1 + 0.2` → `"0.30000000000000004"`). Prefer strings.
|
|
158
|
+
*
|
|
159
|
+
* @param value - Number or numeric string (strings preferred for precision)
|
|
160
|
+
* @returns Marker pinned to `NUMERIC`
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* sql.numeric("12345.6789"); // { __sql_type: "NUMERIC", value: "12345.6789" }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
numeric(value: number | string): SQLNumberMarker & {
|
|
167
|
+
__sql_type: "NUMERIC";
|
|
168
|
+
};
|
|
64
169
|
/**
|
|
65
170
|
* Creates a STRING type parameter
|
|
66
171
|
* Accepts strings, numbers, or booleans
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../../../../../shared/src/sql/helpers.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../../../../../shared/src/sql/helpers.ts"],"mappings":";;;;;AAqGA;cAAa,GAAA;;;;;;;;;;;;;;;;;cAiBC,IAAA,YAAgB,aAAA;EAmbqC;;;;;;;;;;;;;;;;;;;;;mBAhYhD,IAAA,qBAAyB,kBAAA;EAqMX;;;;;;;;;;;;;;;;;;;;;;;AA2OjC;;;;;;;;;;;kCAjXkC,eAAA;;;;;;;;;;;;;;;;;;+BAkEH,eAAA;IAAoB,UAAA;EAAA;;;;;;;;;;;;;;;;;;2CAgC9C,eAAA;IAAoB,UAAA;EAAA;;;;;;;;;;;;;iCAoCQ,eAAA;IAAoB,UAAA;EAAA;;;;;;;;;;;;kCAkBnB,eAAA;IAAoB,UAAA;EAAA;;;;;;;;;;;;;;;;mCAsBnB,eAAA;IAAoB,UAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;4CA4BX,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAyDC,gBAAA;;;;;;;;;;;;;;;;;;;;;;gBA8D7B,UAAA,GAAa,WAAA,YAAuB,eAAA;AAAA;;;;;;;;;;;;;;;iBAgDpC,eAAA,CAAgB,KAAA,QAAa,KAAA,IAAS,aAAA"}
|
|
@@ -1,4 +1,43 @@
|
|
|
1
1
|
//#region ../shared/src/sql/helpers.ts
|
|
2
|
+
const NUMERIC_LITERAL_RE = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
3
|
+
const INTEGER_LITERAL_RE = /^-?\d+$/;
|
|
4
|
+
const INT_MIN = -(2n ** 31n);
|
|
5
|
+
const INT_MAX = 2n ** 31n - 1n;
|
|
6
|
+
const BIGINT_MIN = -(2n ** 63n);
|
|
7
|
+
const BIGINT_MAX = 2n ** 63n - 1n;
|
|
8
|
+
function ensureFiniteNumber(value, fnName) {
|
|
9
|
+
if (!Number.isFinite(value)) throw new Error(`${fnName}() expects a finite number, got: ${value}`);
|
|
10
|
+
}
|
|
11
|
+
function ensureSafeInteger(value, fnName) {
|
|
12
|
+
if (!Number.isSafeInteger(value)) throw new Error(`${fnName}() received an integer outside Number.MAX_SAFE_INTEGER (${value}); JS numbers cannot represent it exactly. Pass a bigint (sql.bigint(BigInt("..."))) or an integer-shaped string instead.`);
|
|
13
|
+
}
|
|
14
|
+
function ensureInBigIntRange(parsed, min, max, typeName, fnName, hint) {
|
|
15
|
+
if (parsed < min || parsed > max) throw new Error(`${fnName}() value ${parsed} is outside ${typeName} range [${min}, ${max}]. ${hint}`);
|
|
16
|
+
}
|
|
17
|
+
function coerceNumericLike(value, fnName) {
|
|
18
|
+
if (typeof value === "number") {
|
|
19
|
+
ensureFiniteNumber(value, fnName);
|
|
20
|
+
return value.toString();
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
if (!NUMERIC_LITERAL_RE.test(value)) throw new Error(`${fnName}() expects number or numeric string, got: ${value === "" ? "empty string" : value}`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`${fnName}() expects number or numeric string, got: ${typeof value}`);
|
|
27
|
+
}
|
|
28
|
+
function coerceIntegerLike(value, fnName) {
|
|
29
|
+
if (typeof value === "number") {
|
|
30
|
+
ensureFiniteNumber(value, fnName);
|
|
31
|
+
if (!Number.isInteger(value)) throw new Error(`${fnName}() expects an integer, got non-integer number: ${value}`);
|
|
32
|
+
ensureSafeInteger(value, fnName);
|
|
33
|
+
return BigInt(value).toString();
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
if (!INTEGER_LITERAL_RE.test(value)) throw new Error(`${fnName}() expects integer number or integer-shaped string, got: ${value === "" ? "empty string" : value}`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`${fnName}() expects integer number or integer-shaped string, got: ${typeof value}`);
|
|
40
|
+
}
|
|
2
41
|
/**
|
|
3
42
|
* SQL helper namespace
|
|
4
43
|
*/
|
|
@@ -29,15 +68,85 @@ const sql = {
|
|
|
29
68
|
};
|
|
30
69
|
},
|
|
31
70
|
number(value) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
71
|
+
if (typeof value === "number") {
|
|
72
|
+
ensureFiniteNumber(value, "sql.number");
|
|
73
|
+
if (Number.isInteger(value)) {
|
|
74
|
+
ensureSafeInteger(value, "sql.number");
|
|
75
|
+
const asBigInt = BigInt(value);
|
|
76
|
+
if (asBigInt >= INT_MIN && asBigInt <= INT_MAX) return {
|
|
77
|
+
__sql_type: "INT",
|
|
78
|
+
value: asBigInt.toString()
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
__sql_type: "BIGINT",
|
|
82
|
+
value: asBigInt.toString()
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
__sql_type: "DOUBLE",
|
|
87
|
+
value: value.toString()
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
if (!NUMERIC_LITERAL_RE.test(value)) throw new Error(`sql.number() expects number or numeric string, got: ${value === "" ? "empty string" : value}`);
|
|
92
|
+
if (INTEGER_LITERAL_RE.test(value)) {
|
|
93
|
+
const parsed = BigInt(value);
|
|
94
|
+
ensureInBigIntRange(parsed, BIGINT_MIN, BIGINT_MAX, "BIGINT (64-bit signed)", "sql.number", "Use sql.numeric() with a string for arbitrary-precision integers.");
|
|
95
|
+
if (parsed >= INT_MIN && parsed <= INT_MAX) return {
|
|
96
|
+
__sql_type: "INT",
|
|
97
|
+
value
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
__sql_type: "BIGINT",
|
|
101
|
+
value
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
__sql_type: "NUMERIC",
|
|
106
|
+
value
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`sql.number() expects number or numeric string, got: ${typeof value}`);
|
|
110
|
+
},
|
|
111
|
+
int(value) {
|
|
112
|
+
const stringValue = coerceIntegerLike(value, "sql.int");
|
|
113
|
+
ensureInBigIntRange(BigInt(stringValue), INT_MIN, INT_MAX, "INT (32-bit signed)", "sql.int", "Use sql.bigint() for 64-bit values.");
|
|
114
|
+
return {
|
|
115
|
+
__sql_type: "INT",
|
|
116
|
+
value: stringValue
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
bigint(value) {
|
|
120
|
+
if (typeof value === "bigint") {
|
|
121
|
+
ensureInBigIntRange(value, BIGINT_MIN, BIGINT_MAX, "BIGINT (64-bit signed)", "sql.bigint", "Use sql.numeric() with a string for arbitrary-precision integers.");
|
|
122
|
+
return {
|
|
123
|
+
__sql_type: "BIGINT",
|
|
124
|
+
value: value.toString()
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const stringValue = coerceIntegerLike(value, "sql.bigint");
|
|
128
|
+
ensureInBigIntRange(BigInt(stringValue), BIGINT_MIN, BIGINT_MAX, "BIGINT (64-bit signed)", "sql.bigint", "Use sql.numeric() with a string for arbitrary-precision integers.");
|
|
129
|
+
return {
|
|
130
|
+
__sql_type: "BIGINT",
|
|
131
|
+
value: stringValue
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
float(value) {
|
|
135
|
+
return {
|
|
136
|
+
__sql_type: "FLOAT",
|
|
137
|
+
value: coerceNumericLike(value, "sql.float")
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
double(value) {
|
|
141
|
+
return {
|
|
142
|
+
__sql_type: "DOUBLE",
|
|
143
|
+
value: coerceNumericLike(value, "sql.double")
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
numeric(value) {
|
|
38
147
|
return {
|
|
39
148
|
__sql_type: "NUMERIC",
|
|
40
|
-
value:
|
|
149
|
+
value: coerceNumericLike(value, "sql.numeric")
|
|
41
150
|
};
|
|
42
151
|
},
|
|
43
152
|
string(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":[],"sources":["../../../../../shared/src/sql/helpers.ts"],"sourcesContent":["import type {\n SQLBinaryMarker,\n SQLBooleanMarker,\n SQLDateMarker,\n SQLNumberMarker,\n SQLStringMarker,\n SQLTimestampMarker,\n SQLTypeMarker,\n} from \"./types\";\n\n/**\n * SQL helper namespace\n */\nexport const sql = {\n /**\n * Creates a DATE type parameter\n * Accepts Date objects or ISO date strings (YYYY-MM-DD format)\n * @param value - Date object or ISO date string\n * @returns Marker object for DATE type parameter\n * @example\n * ```typescript\n * const params = { startDate: sql.date(new Date(\"2024-01-01\")) };\n * params = { startDate: \"2024-01-01\" }\n * ```\n * @example\n * ```typescript\n * const params = { startDate: sql.date(\"2024-01-01\") };\n * params = { startDate: \"2024-01-01\" }\n * ```\n */\n date(value: Date | string): SQLDateMarker {\n let dateValue: string = \"\";\n\n // check if value is a Date object\n if (value instanceof Date) {\n dateValue = value.toISOString().split(\"T\")[0];\n }\n // check if value is a string\n else if (typeof value === \"string\") {\n // validate format\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new Error(\n `sql.date() expects Date or ISO date string (YYYY-MM-DD format), got: ${value}`,\n );\n }\n dateValue = value;\n }\n // if value is not a Date object or string, throw an error\n else {\n throw new Error(\n `sql.date() expects Date or ISO date string (YYYY-MM-DD format), got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"DATE\",\n value: dateValue,\n };\n },\n\n /**\n * Creates a TIMESTAMP type parameter\n * Accepts Date objects, ISO timestamp strings, or Unix timestamp numbers\n * @param value - Date object, ISO timestamp string, or Unix timestamp number\n * @returns Marker object for TIMESTAMP type parameter\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(new Date(\"2024-01-01T12:00:00Z\")) };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(\"2024-01-01T12:00:00Z\") };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(1704110400000) };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n */\n timestamp(value: Date | string | number): SQLTimestampMarker {\n let timestampValue: string = \"\";\n\n if (value instanceof Date) {\n timestampValue = value.toISOString().replace(/\\.000(Z|[+-])/, \"$1\");\n } else if (typeof value === \"string\") {\n const isoRegex =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})?$/;\n if (!isoRegex.test(value)) {\n throw new Error(\n `sql.timestamp() expects ISO timestamp string (YYYY-MM-DDTHH:MM:SS.mmmZ or YYYY-MM-DDTHH:MM:SS.mmm+HH:MM), got: ${value}`,\n );\n }\n timestampValue = value;\n } else if (typeof value === \"number\") {\n const date = new Date(value > 1e12 ? value : value * 1000);\n timestampValue = date.toISOString().replace(/\\.000(Z|[+-])/, \"$1\");\n } else {\n throw new Error(\n `sql.timestamp() expects Date, ISO timestamp string, or Unix timestamp number, got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"TIMESTAMP\",\n value: timestampValue,\n };\n },\n\n /**\n * Creates a NUMERIC type parameter\n * Accepts numbers or numeric strings\n * @param value - Number or numeric string\n * @returns Marker object for NUMERIC type parameter\n * @example\n * ```typescript\n * const params = { userId: sql.number(123) };\n * params = { userId: \"123\" }\n * ```\n * @example\n * ```typescript\n * const params = { userId: sql.number(\"123\") };\n * params = { userId: \"123\" }\n * ```\n */\n number(value: number | string): SQLNumberMarker {\n let numValue: string = \"\";\n\n // check if value is a number\n if (typeof value === \"number\") {\n numValue = value.toString();\n }\n // check if value is a string\n else if (typeof value === \"string\") {\n if (value === \"\" || Number.isNaN(Number(value))) {\n throw new Error(\n `sql.number() expects number or numeric string, got: ${value === \"\" ? \"empty string\" : value}`,\n );\n }\n numValue = value;\n }\n // if value is not a number or string, throw an error\n else {\n throw new Error(\n `sql.number() expects number or numeric string, got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"NUMERIC\",\n value: numValue,\n };\n },\n\n /**\n * Creates a STRING type parameter\n * Accepts strings, numbers, or booleans\n * @param value - String, number, or boolean\n * @returns Marker object for STRING type parameter\n * @example\n * ```typescript\n * const params = { name: sql.string(\"John\") };\n * params = { name: \"John\" }\n * ```\n * @example\n * ```typescript\n * const params = { name: sql.string(123) };\n * params = { name: \"123\" }\n * ```\n * @example\n * ```typescript\n * const params = { name: sql.string(true) };\n * params = { name: \"true\" }\n * ```\n */\n string(value: string | number | boolean): SQLStringMarker {\n if (\n typeof value !== \"string\" &&\n typeof value !== \"number\" &&\n typeof value !== \"boolean\"\n ) {\n throw new Error(\n `sql.string() expects string or number or boolean, got: ${typeof value}`,\n );\n }\n\n let stringValue: string = \"\";\n\n if (typeof value === \"string\") {\n stringValue = value;\n } else {\n stringValue = value.toString();\n }\n\n return {\n __sql_type: \"STRING\",\n value: stringValue,\n };\n },\n\n /**\n * Create a BOOLEAN type parameter\n * Accepts booleans, strings, or numbers\n * @param value - Boolean, string, or number\n * @returns Marker object for BOOLEAN type parameter\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(true) };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(\"true\") };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(1) };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(\"false\") };\n * params = { isActive: \"false\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(0) };\n * params = { isActive: \"false\" }\n * ```\n * @returns\n */\n boolean(value: boolean | string | number): SQLBooleanMarker {\n if (\n typeof value !== \"boolean\" &&\n typeof value !== \"string\" &&\n typeof value !== \"number\"\n ) {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${typeof value}`,\n );\n }\n\n let booleanValue: string = \"\";\n\n if (typeof value === \"boolean\") {\n booleanValue = value.toString();\n }\n // check if value is a number\n else if (typeof value === \"number\") {\n if (value !== 1 && value !== 0) {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${value}`,\n );\n }\n booleanValue = value === 1 ? \"true\" : \"false\";\n }\n // check if value is a string\n else if (typeof value === \"string\") {\n if (value !== \"true\" && value !== \"false\") {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${value}`,\n );\n }\n booleanValue = value;\n }\n\n return {\n __sql_type: \"BOOLEAN\",\n value: booleanValue,\n };\n },\n\n /**\n * Creates a BINARY parameter as hex-encoded STRING\n * Accepts Uint8Array, ArrayBuffer, or hex string\n * Note: Databricks SQL Warehouse doesn't support BINARY as parameter type,\n * so this helper returns a STRING with hex encoding. Use UNHEX(:param) in your SQL.\n * @param value - Uint8Array, ArrayBuffer, or hex string\n * @returns Marker object with STRING type and hex-encoded value\n * @example\n * ```typescript\n * // From Uint8Array:\n * const params = { data: sql.binary(new Uint8Array([0x53, 0x70, 0x61, 0x72, 0x6b])) };\n * // Returns: { __sql_type: \"STRING\", value: \"537061726B\" }\n * // SQL: SELECT UNHEX(:data) as binary_value\n * ```\n * @example\n * ```typescript\n * // From hex string:\n * const params = { data: sql.binary(\"537061726B\") };\n * // Returns: { __sql_type: \"STRING\", value: \"537061726B\" }\n * ```\n */\n binary(value: Uint8Array | ArrayBuffer | string): SQLBinaryMarker {\n let hexValue: string = \"\";\n\n if (value instanceof Uint8Array) {\n // if value is a Uint8Array, convert it to a hex string\n hexValue = Array.from(value)\n .map((b) => b.toString(16).padStart(2, \"0\").toUpperCase())\n .join(\"\");\n } else if (value instanceof ArrayBuffer) {\n // if value is an ArrayBuffer, convert it to a hex string\n hexValue = Array.from(new Uint8Array(value))\n .map((b) => b.toString(16).padStart(2, \"0\").toUpperCase())\n .join(\"\");\n } else if (typeof value === \"string\") {\n // validate hex string\n if (!/^[0-9A-Fa-f]*$/.test(value)) {\n throw new Error(\n `sql.binary() expects Uint8Array, ArrayBuffer, or hex string, got invalid hex: ${value}`,\n );\n }\n hexValue = value.toUpperCase();\n } else {\n throw new Error(\n `sql.binary() expects Uint8Array, ArrayBuffer, or hex string, got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"STRING\",\n value: hexValue,\n };\n },\n};\n\n/**\n * Type guard to check if a value is a SQL type marker\n * @param value - Value to check\n * @returns True if the value is a SQL type marker, false otherwise\n * @example\n * ```typescript\n * const value = {\n * __sql_type: \"DATE\",\n * value: \"2024-01-01\",\n * };\n * const isSQLTypeMarker = isSQLTypeMarker(value);\n * console.log(isSQLTypeMarker); // true\n * ```\n */\nexport function isSQLTypeMarker(value: any): value is SQLTypeMarker {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"__sql_type\" in value &&\n \"value\" in value &&\n typeof value.__sql_type === \"string\" &&\n typeof value.value === \"string\"\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../../../../../shared/src/sql/helpers.ts"],"sourcesContent":["import type {\n SQLBinaryMarker,\n SQLBooleanMarker,\n SQLDateMarker,\n SQLNumberMarker,\n SQLStringMarker,\n SQLTimestampMarker,\n SQLTypeMarker,\n} from \"./types\";\n\n// Strict numeric-literal regex used by string-input paths. Rejects empty\n// strings, whitespace, hex/octal/binary, `NaN`, `Infinity`, and other forms\n// that JS `Number()` would silently coerce.\nconst NUMERIC_LITERAL_RE = /^-?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/;\nconst INTEGER_LITERAL_RE = /^-?\\d+$/;\n\n// 32-bit signed INT range\nconst INT_MIN = -(2n ** 31n);\nconst INT_MAX = 2n ** 31n - 1n;\n// 64-bit signed BIGINT range\nconst BIGINT_MIN = -(2n ** 63n);\nconst BIGINT_MAX = 2n ** 63n - 1n;\n\nfunction ensureFiniteNumber(value: number, fnName: string): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fnName}() expects a finite number, got: ${value}`);\n }\n}\n\nfunction ensureSafeInteger(value: number, fnName: string): void {\n if (!Number.isSafeInteger(value)) {\n throw new Error(\n `${fnName}() received an integer outside Number.MAX_SAFE_INTEGER ` +\n `(${value}); JS numbers cannot represent it exactly. ` +\n `Pass a bigint (sql.bigint(BigInt(\"...\"))) or an integer-shaped string instead.`,\n );\n }\n}\n\nfunction ensureInBigIntRange(\n parsed: bigint,\n min: bigint,\n max: bigint,\n typeName: string,\n fnName: string,\n hint: string,\n): void {\n if (parsed < min || parsed > max) {\n throw new Error(\n `${fnName}() value ${parsed} is outside ${typeName} range [${min}, ${max}]. ${hint}`,\n );\n }\n}\n\nfunction coerceNumericLike(value: number | string, fnName: string): string {\n if (typeof value === \"number\") {\n ensureFiniteNumber(value, fnName);\n return value.toString();\n }\n if (typeof value === \"string\") {\n if (!NUMERIC_LITERAL_RE.test(value)) {\n throw new Error(\n `${fnName}() expects number or numeric string, got: ${value === \"\" ? \"empty string\" : value}`,\n );\n }\n return value;\n }\n throw new Error(\n `${fnName}() expects number or numeric string, got: ${typeof value}`,\n );\n}\n\nfunction coerceIntegerLike(value: number | string, fnName: string): string {\n if (typeof value === \"number\") {\n ensureFiniteNumber(value, fnName);\n if (!Number.isInteger(value)) {\n throw new Error(\n `${fnName}() expects an integer, got non-integer number: ${value}`,\n );\n }\n ensureSafeInteger(value, fnName);\n // BigInt(value).toString() emits canonical decimal-integer text;\n // Number.prototype.toString emits exponent notation for values like 1e21.\n return BigInt(value).toString();\n }\n if (typeof value === \"string\") {\n if (!INTEGER_LITERAL_RE.test(value)) {\n throw new Error(\n `${fnName}() expects integer number or integer-shaped string, got: ${value === \"\" ? \"empty string\" : value}`,\n );\n }\n return value;\n }\n throw new Error(\n `${fnName}() expects integer number or integer-shaped string, got: ${typeof value}`,\n );\n}\n\n/**\n * SQL helper namespace\n */\nexport const sql = {\n /**\n * Creates a DATE type parameter\n * Accepts Date objects or ISO date strings (YYYY-MM-DD format)\n * @param value - Date object or ISO date string\n * @returns Marker object for DATE type parameter\n * @example\n * ```typescript\n * const params = { startDate: sql.date(new Date(\"2024-01-01\")) };\n * params = { startDate: \"2024-01-01\" }\n * ```\n * @example\n * ```typescript\n * const params = { startDate: sql.date(\"2024-01-01\") };\n * params = { startDate: \"2024-01-01\" }\n * ```\n */\n date(value: Date | string): SQLDateMarker {\n let dateValue: string = \"\";\n\n // check if value is a Date object\n if (value instanceof Date) {\n dateValue = value.toISOString().split(\"T\")[0];\n }\n // check if value is a string\n else if (typeof value === \"string\") {\n // validate format\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new Error(\n `sql.date() expects Date or ISO date string (YYYY-MM-DD format), got: ${value}`,\n );\n }\n dateValue = value;\n }\n // if value is not a Date object or string, throw an error\n else {\n throw new Error(\n `sql.date() expects Date or ISO date string (YYYY-MM-DD format), got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"DATE\",\n value: dateValue,\n };\n },\n\n /**\n * Creates a TIMESTAMP type parameter\n * Accepts Date objects, ISO timestamp strings, or Unix timestamp numbers\n * @param value - Date object, ISO timestamp string, or Unix timestamp number\n * @returns Marker object for TIMESTAMP type parameter\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(new Date(\"2024-01-01T12:00:00Z\")) };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(\"2024-01-01T12:00:00Z\") };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n * @example\n * ```typescript\n * const params = { createdTime: sql.timestamp(1704110400000) };\n * params = { createdTime: \"2024-01-01T12:00:00Z\" }\n * ```\n */\n timestamp(value: Date | string | number): SQLTimestampMarker {\n let timestampValue: string = \"\";\n\n if (value instanceof Date) {\n timestampValue = value.toISOString().replace(/\\.000(Z|[+-])/, \"$1\");\n } else if (typeof value === \"string\") {\n const isoRegex =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})?$/;\n if (!isoRegex.test(value)) {\n throw new Error(\n `sql.timestamp() expects ISO timestamp string (YYYY-MM-DDTHH:MM:SS.mmmZ or YYYY-MM-DDTHH:MM:SS.mmm+HH:MM), got: ${value}`,\n );\n }\n timestampValue = value;\n } else if (typeof value === \"number\") {\n const date = new Date(value > 1e12 ? value : value * 1000);\n timestampValue = date.toISOString().replace(/\\.000(Z|[+-])/, \"$1\");\n } else {\n throw new Error(\n `sql.timestamp() expects Date, ISO timestamp string, or Unix timestamp number, got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"TIMESTAMP\",\n value: timestampValue,\n };\n },\n\n /**\n * Creates a numeric type parameter. The wire SQL type is inferred from the\n * value so the parameter binds correctly in any context, including `LIMIT`\n * and `OFFSET`:\n *\n * - JS integer in `[-2^31, 2^31 - 1]` → `INT`\n * - JS integer outside `INT` but within `Number.MAX_SAFE_INTEGER` → `BIGINT`\n * - JS non-integer (`3.14`) → `DOUBLE`\n * - integer-shaped string in `INT` range → `INT` (common HTTP-input case)\n * - integer-shaped string outside `INT` but within `BIGINT` → `BIGINT`\n * - decimal-shaped string (`\"123.45\"`) → `NUMERIC` (preserves precision)\n *\n * Why default to `INT`? Spark's `LIMIT` and `OFFSET` operators require\n * `IntegerType` specifically — `BIGINT` (`LongType`) is rejected with\n * `INVALID_LIMIT_LIKE_EXPRESSION.DATA_TYPE`. Catalyst auto-widens `INT`\n * to `BIGINT` / `DECIMAL` / `DOUBLE` for wider columns, so `INT` is a\n * strictly better default than `BIGINT`.\n *\n * Throws on `NaN`, `Infinity`, JS integers outside `Number.MAX_SAFE_INTEGER`,\n * integer-shaped strings outside the `BIGINT` range, or non-numeric strings.\n * Reach for `sql.int()`, `sql.bigint()`, `sql.float()`, `sql.double()`, or\n * `sql.numeric()` to override the inferred type.\n *\n * @param value - Number or numeric string\n * @returns Marker for a numeric SQL parameter\n * @example\n * ```typescript\n * sql.number(123); // { __sql_type: \"INT\", value: \"123\" }\n * sql.number(3_000_000_000); // { __sql_type: \"BIGINT\", value: \"3000000000\" }\n * sql.number(0.5); // { __sql_type: \"DOUBLE\", value: \"0.5\" }\n * sql.number(\"10\"); // { __sql_type: \"INT\", value: \"10\" }\n * sql.number(\"123.45\"); // { __sql_type: \"NUMERIC\", value: \"123.45\" }\n * ```\n */\n number(value: number | string): SQLNumberMarker {\n if (typeof value === \"number\") {\n ensureFiniteNumber(value, \"sql.number\");\n if (Number.isInteger(value)) {\n ensureSafeInteger(value, \"sql.number\");\n const asBigInt = BigInt(value);\n // INT (32-bit) is required by Spark for LIMIT/OFFSET; Catalyst\n // widens INT → BIGINT/DECIMAL/DOUBLE automatically.\n if (asBigInt >= INT_MIN && asBigInt <= INT_MAX) {\n return { __sql_type: \"INT\", value: asBigInt.toString() };\n }\n return { __sql_type: \"BIGINT\", value: asBigInt.toString() };\n }\n return { __sql_type: \"DOUBLE\", value: value.toString() };\n }\n if (typeof value === \"string\") {\n if (!NUMERIC_LITERAL_RE.test(value)) {\n throw new Error(\n `sql.number() expects number or numeric string, got: ${value === \"\" ? \"empty string\" : value}`,\n );\n }\n // Integer-shaped strings get the same INT-preferring inference, so\n // `sql.number(req.query.n)` (Express/URLSearchParams strings) works\n // with LIMIT/OFFSET out of the box. Out-of-BIGINT-range throws —\n // sql.numeric() is the right helper for arbitrary-precision integers.\n if (INTEGER_LITERAL_RE.test(value)) {\n const parsed = BigInt(value);\n ensureInBigIntRange(\n parsed,\n BIGINT_MIN,\n BIGINT_MAX,\n \"BIGINT (64-bit signed)\",\n \"sql.number\",\n \"Use sql.numeric() with a string for arbitrary-precision integers.\",\n );\n if (parsed >= INT_MIN && parsed <= INT_MAX) {\n return { __sql_type: \"INT\", value };\n }\n return { __sql_type: \"BIGINT\", value };\n }\n // Non-integer strings stay NUMERIC: the caller chose to pass a string,\n // honour their precision intent rather than coercing through JS number.\n return { __sql_type: \"NUMERIC\", value };\n }\n throw new Error(\n `sql.number() expects number or numeric string, got: ${typeof value}`,\n );\n },\n\n /**\n * Creates an `INT` (32-bit signed integer) parameter. Use when the column\n * or context requires `INT` specifically (e.g. legacy schemas, or to make\n * the wire type explicit).\n *\n * Rejects non-integers, values outside `Number.MAX_SAFE_INTEGER` (for\n * number inputs), and values outside the signed 32-bit range\n * `[-2^31, 2^31 - 1]`.\n *\n * @param value - Integer number or integer-shaped string\n * @returns Marker pinned to `INT`\n * @example\n * ```typescript\n * sql.int(42); // { __sql_type: \"INT\", value: \"42\" }\n * sql.int(\"42\"); // { __sql_type: \"INT\", value: \"42\" }\n * ```\n */\n int(value: number | string): SQLNumberMarker & { __sql_type: \"INT\" } {\n const stringValue = coerceIntegerLike(value, \"sql.int\");\n ensureInBigIntRange(\n BigInt(stringValue),\n INT_MIN,\n INT_MAX,\n \"INT (32-bit signed)\",\n \"sql.int\",\n \"Use sql.bigint() for 64-bit values.\",\n );\n return { __sql_type: \"INT\", value: stringValue };\n },\n\n /**\n * Creates a `BIGINT` (64-bit signed integer) parameter. Accepts JS\n * `bigint` so callers can round-trip values outside `Number.MAX_SAFE_INTEGER`\n * without precision loss; for `number` inputs, requires\n * `Number.isSafeInteger(value)`.\n *\n * Rejects values outside the signed 64-bit range `[-2^63, 2^63 - 1]`.\n *\n * @param value - Integer number, bigint, or integer-shaped string\n * @returns Marker pinned to `BIGINT`\n * @example\n * ```typescript\n * sql.bigint(42); // { __sql_type: \"BIGINT\", value: \"42\" }\n * sql.bigint(9007199254740993n); // { __sql_type: \"BIGINT\", value: \"9007199254740993\" }\n * sql.bigint(\"9007199254740993\"); // { __sql_type: \"BIGINT\", value: \"9007199254740993\" }\n * ```\n */\n bigint(\n value: number | bigint | string,\n ): SQLNumberMarker & { __sql_type: \"BIGINT\" } {\n if (typeof value === \"bigint\") {\n ensureInBigIntRange(\n value,\n BIGINT_MIN,\n BIGINT_MAX,\n \"BIGINT (64-bit signed)\",\n \"sql.bigint\",\n \"Use sql.numeric() with a string for arbitrary-precision integers.\",\n );\n return { __sql_type: \"BIGINT\", value: value.toString() };\n }\n const stringValue = coerceIntegerLike(value, \"sql.bigint\");\n ensureInBigIntRange(\n BigInt(stringValue),\n BIGINT_MIN,\n BIGINT_MAX,\n \"BIGINT (64-bit signed)\",\n \"sql.bigint\",\n \"Use sql.numeric() with a string for arbitrary-precision integers.\",\n );\n return { __sql_type: \"BIGINT\", value: stringValue };\n },\n\n /**\n * Creates a `FLOAT` (single-precision, 32-bit) parameter. Note that JS\n * numbers are 64-bit doubles, so values may be rounded to fit FLOAT\n * precision at bind time.\n *\n * @param value - Number or numeric string\n * @returns Marker pinned to `FLOAT`\n * @example\n * ```typescript\n * sql.float(3.14); // { __sql_type: \"FLOAT\", value: \"3.14\" }\n * ```\n */\n float(value: number | string): SQLNumberMarker & { __sql_type: \"FLOAT\" } {\n return {\n __sql_type: \"FLOAT\",\n value: coerceNumericLike(value, \"sql.float\"),\n };\n },\n\n /**\n * Creates a `DOUBLE` (double-precision, 64-bit) parameter. Same precision\n * as a JS `number`, so `sql.double(value)` is exact for any JS number.\n *\n * @param value - Number or numeric string\n * @returns Marker pinned to `DOUBLE`\n * @example\n * ```typescript\n * sql.double(3.14); // { __sql_type: \"DOUBLE\", value: \"3.14\" }\n * ```\n */\n double(value: number | string): SQLNumberMarker & { __sql_type: \"DOUBLE\" } {\n return {\n __sql_type: \"DOUBLE\",\n value: coerceNumericLike(value, \"sql.double\"),\n };\n },\n\n /**\n * Creates a `NUMERIC` (fixed-point DECIMAL) parameter. Use when you need\n * exact decimal arithmetic (currency, percentages) — pass values as\n * strings to avoid JS-number precision loss.\n *\n * Note: passing a JS `number` is accepted but lossy for many values\n * (e.g. `0.1 + 0.2` → `\"0.30000000000000004\"`). Prefer strings.\n *\n * @param value - Number or numeric string (strings preferred for precision)\n * @returns Marker pinned to `NUMERIC`\n * @example\n * ```typescript\n * sql.numeric(\"12345.6789\"); // { __sql_type: \"NUMERIC\", value: \"12345.6789\" }\n * ```\n */\n numeric(value: number | string): SQLNumberMarker & { __sql_type: \"NUMERIC\" } {\n return {\n __sql_type: \"NUMERIC\",\n value: coerceNumericLike(value, \"sql.numeric\"),\n };\n },\n\n /**\n * Creates a STRING type parameter\n * Accepts strings, numbers, or booleans\n * @param value - String, number, or boolean\n * @returns Marker object for STRING type parameter\n * @example\n * ```typescript\n * const params = { name: sql.string(\"John\") };\n * params = { name: \"John\" }\n * ```\n * @example\n * ```typescript\n * const params = { name: sql.string(123) };\n * params = { name: \"123\" }\n * ```\n * @example\n * ```typescript\n * const params = { name: sql.string(true) };\n * params = { name: \"true\" }\n * ```\n */\n string(value: string | number | boolean): SQLStringMarker {\n if (\n typeof value !== \"string\" &&\n typeof value !== \"number\" &&\n typeof value !== \"boolean\"\n ) {\n throw new Error(\n `sql.string() expects string or number or boolean, got: ${typeof value}`,\n );\n }\n\n let stringValue: string = \"\";\n\n if (typeof value === \"string\") {\n stringValue = value;\n } else {\n stringValue = value.toString();\n }\n\n return {\n __sql_type: \"STRING\",\n value: stringValue,\n };\n },\n\n /**\n * Create a BOOLEAN type parameter\n * Accepts booleans, strings, or numbers\n * @param value - Boolean, string, or number\n * @returns Marker object for BOOLEAN type parameter\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(true) };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(\"true\") };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(1) };\n * params = { isActive: \"true\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(\"false\") };\n * params = { isActive: \"false\" }\n * ```\n * @example\n * ```typescript\n * const params = { isActive: sql.boolean(0) };\n * params = { isActive: \"false\" }\n * ```\n * @returns\n */\n boolean(value: boolean | string | number): SQLBooleanMarker {\n if (\n typeof value !== \"boolean\" &&\n typeof value !== \"string\" &&\n typeof value !== \"number\"\n ) {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${typeof value}`,\n );\n }\n\n let booleanValue: string = \"\";\n\n if (typeof value === \"boolean\") {\n booleanValue = value.toString();\n }\n // check if value is a number\n else if (typeof value === \"number\") {\n if (value !== 1 && value !== 0) {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${value}`,\n );\n }\n booleanValue = value === 1 ? \"true\" : \"false\";\n }\n // check if value is a string\n else if (typeof value === \"string\") {\n if (value !== \"true\" && value !== \"false\") {\n throw new Error(\n `sql.boolean() expects boolean or string (true or false) or number (1 or 0), got: ${value}`,\n );\n }\n booleanValue = value;\n }\n\n return {\n __sql_type: \"BOOLEAN\",\n value: booleanValue,\n };\n },\n\n /**\n * Creates a BINARY parameter as hex-encoded STRING\n * Accepts Uint8Array, ArrayBuffer, or hex string\n * Note: Databricks SQL Warehouse doesn't support BINARY as parameter type,\n * so this helper returns a STRING with hex encoding. Use UNHEX(:param) in your SQL.\n * @param value - Uint8Array, ArrayBuffer, or hex string\n * @returns Marker object with STRING type and hex-encoded value\n * @example\n * ```typescript\n * // From Uint8Array:\n * const params = { data: sql.binary(new Uint8Array([0x53, 0x70, 0x61, 0x72, 0x6b])) };\n * // Returns: { __sql_type: \"STRING\", value: \"537061726B\" }\n * // SQL: SELECT UNHEX(:data) as binary_value\n * ```\n * @example\n * ```typescript\n * // From hex string:\n * const params = { data: sql.binary(\"537061726B\") };\n * // Returns: { __sql_type: \"STRING\", value: \"537061726B\" }\n * ```\n */\n binary(value: Uint8Array | ArrayBuffer | string): SQLBinaryMarker {\n let hexValue: string = \"\";\n\n if (value instanceof Uint8Array) {\n // if value is a Uint8Array, convert it to a hex string\n hexValue = Array.from(value)\n .map((b) => b.toString(16).padStart(2, \"0\").toUpperCase())\n .join(\"\");\n } else if (value instanceof ArrayBuffer) {\n // if value is an ArrayBuffer, convert it to a hex string\n hexValue = Array.from(new Uint8Array(value))\n .map((b) => b.toString(16).padStart(2, \"0\").toUpperCase())\n .join(\"\");\n } else if (typeof value === \"string\") {\n // validate hex string\n if (!/^[0-9A-Fa-f]*$/.test(value)) {\n throw new Error(\n `sql.binary() expects Uint8Array, ArrayBuffer, or hex string, got invalid hex: ${value}`,\n );\n }\n hexValue = value.toUpperCase();\n } else {\n throw new Error(\n `sql.binary() expects Uint8Array, ArrayBuffer, or hex string, got: ${typeof value}`,\n );\n }\n\n return {\n __sql_type: \"STRING\",\n value: hexValue,\n };\n },\n};\n\n/**\n * Type guard to check if a value is a SQL type marker\n * @param value - Value to check\n * @returns True if the value is a SQL type marker, false otherwise\n * @example\n * ```typescript\n * const value = {\n * __sql_type: \"DATE\",\n * value: \"2024-01-01\",\n * };\n * const isSQLTypeMarker = isSQLTypeMarker(value);\n * console.log(isSQLTypeMarker); // true\n * ```\n */\nexport function isSQLTypeMarker(value: any): value is SQLTypeMarker {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"__sql_type\" in value &&\n \"value\" in value &&\n typeof value.__sql_type === \"string\" &&\n typeof value.value === \"string\"\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { isSQLTypeMarker, sql } from "./helpers.js";
|
|
@@ -3,8 +3,20 @@ interface SQLStringMarker {
|
|
|
3
3
|
__sql_type: "STRING";
|
|
4
4
|
value: string;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* SQL numeric parameter marker. The wire type controls how Databricks SQL
|
|
8
|
+
* binds the value — notably, only integer types satisfy the `LIMIT` and
|
|
9
|
+
* `OFFSET` clauses.
|
|
10
|
+
*
|
|
11
|
+
* - `BIGINT` / `INT` — integer columns, LIMIT/OFFSET, IDs
|
|
12
|
+
* - `FLOAT` / `DOUBLE` — floating-point columns
|
|
13
|
+
* - `NUMERIC` — fixed-point DECIMAL columns (preserves precision)
|
|
14
|
+
*
|
|
15
|
+
* Created by `sql.number()` (auto-inferred), or by typed variants
|
|
16
|
+
* `sql.int()`, `sql.bigint()`, `sql.float()`, `sql.double()`, `sql.numeric()`.
|
|
17
|
+
*/
|
|
6
18
|
interface SQLNumberMarker {
|
|
7
|
-
__sql_type: "NUMERIC";
|
|
19
|
+
__sql_type: "INT" | "BIGINT" | "FLOAT" | "DOUBLE" | "NUMERIC";
|
|
8
20
|
value: string;
|
|
9
21
|
}
|
|
10
22
|
interface SQLBooleanMarker {
|
|
@@ -26,7 +38,9 @@ interface SQLTimestampMarker {
|
|
|
26
38
|
}
|
|
27
39
|
/**
|
|
28
40
|
* Object that identifies a typed SQL parameter.
|
|
29
|
-
* Created using sql.date(), sql.string(), sql.number()
|
|
41
|
+
* Created using sql.date(), sql.string(), sql.number() (or the typed numeric
|
|
42
|
+
* variants sql.int/bigint/float/double/numeric), sql.boolean(),
|
|
43
|
+
* sql.timestamp(), or sql.binary().
|
|
30
44
|
*/
|
|
31
45
|
type SQLTypeMarker = SQLStringMarker | SQLNumberMarker | SQLBooleanMarker | SQLBinaryMarker | SQLDateMarker | SQLTimestampMarker;
|
|
32
46
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../../shared/src/sql/types.ts"],"mappings":";UAAiB,eAAA;EACf,UAAA;EACA,KAAA;AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../../shared/src/sql/types.ts"],"mappings":";UAAiB,eAAA;EACf,UAAA;EACA,KAAA;AAAA;;;AAeF;;;;;AAKA;;;;;UALiB,eAAA;EACf,UAAA;EACA,KAAA;AAAA;AAAA,UAGe,gBAAA;EACf,UAAA;EACA,KAAA;AAAA;;UAIe,eAAA;EACf,UAAA;EACA,KAAA;AAAA;AAAA,UAGe,aAAA;EACf,UAAA;EACA,KAAA;AAAA;AAAA,UAGe,kBAAA;EACf,UAAA;EACA,KAAA;AAAA;;;;;;;KASU,aAAA,GACR,eAAA,GACA,eAAA,GACA,gBAAA,GACA,eAAA,GACA,aAAA,GACA,kBAAA"}
|