@duckdbfan/drizzle-duckdb 0.0.6 → 1.3.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/README.md +344 -62
- package/dist/bin/duckdb-introspect.d.ts +2 -0
- package/dist/client.d.ts +42 -0
- package/dist/columns.d.ts +142 -0
- package/dist/dialect.d.ts +27 -2
- package/dist/driver.d.ts +53 -37
- package/dist/duckdb-introspect.mjs +2890 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.mjs +360 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +3071 -209
- package/dist/introspect.d.ts +74 -0
- package/dist/migrator.d.ts +3 -2
- package/dist/olap.d.ts +46 -0
- package/dist/operators.d.ts +8 -0
- package/dist/options.d.ts +7 -0
- package/dist/pool.d.ts +30 -0
- package/dist/select-builder.d.ts +31 -0
- package/dist/session.d.ts +33 -8
- package/dist/sql/ast-transformer.d.ts +33 -0
- package/dist/sql/result-mapper.d.ts +9 -0
- package/dist/sql/selection.d.ts +2 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +10 -0
- package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
- package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
- package/dist/utils.d.ts +2 -5
- package/dist/value-wrappers-core.d.ts +42 -0
- package/dist/value-wrappers.d.ts +8 -0
- package/package.json +53 -16
- package/src/bin/duckdb-introspect.ts +181 -0
- package/src/client.ts +528 -0
- package/src/columns.ts +510 -1
- package/src/dialect.ts +111 -15
- package/src/driver.ts +266 -180
- package/src/helpers.ts +18 -0
- package/src/index.ts +8 -1
- package/src/introspect.ts +935 -0
- package/src/migrator.ts +10 -5
- package/src/olap.ts +190 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +25 -0
- package/src/pool.ts +274 -0
- package/src/select-builder.ts +110 -0
- package/src/session.ts +306 -66
- package/src/sql/ast-transformer.ts +170 -0
- package/src/sql/result-mapper.ts +303 -0
- package/src/sql/selection.ts +60 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +586 -0
- package/src/sql/visitors/generate-series-alias.ts +291 -0
- package/src/sql/visitors/union-with-hoister.ts +106 -0
- package/src/utils.ts +2 -216
- package/src/value-wrappers-core.ts +168 -0
- package/src/value-wrappers.ts +165 -0
package/dist/index.mjs
CHANGED
|
@@ -1,42 +1,174 @@
|
|
|
1
1
|
// src/driver.ts
|
|
2
|
-
import {
|
|
2
|
+
import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
|
|
3
|
+
import { entityKind as entityKind3 } from "drizzle-orm/entity";
|
|
3
4
|
import { DefaultLogger } from "drizzle-orm/logger";
|
|
4
5
|
import { PgDatabase } from "drizzle-orm/pg-core/db";
|
|
5
6
|
import {
|
|
6
7
|
createTableRelationsHelpers,
|
|
7
8
|
extractTablesRelationalConfig
|
|
8
9
|
} from "drizzle-orm/relations";
|
|
9
|
-
import {
|
|
10
|
-
getTableColumns
|
|
11
|
-
} from "drizzle-orm/utils";
|
|
12
10
|
|
|
13
11
|
// src/session.ts
|
|
14
12
|
import { entityKind } from "drizzle-orm/entity";
|
|
15
13
|
import { NoopLogger } from "drizzle-orm/logger";
|
|
16
14
|
import { PgTransaction } from "drizzle-orm/pg-core";
|
|
17
15
|
import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
|
|
18
|
-
import { fillPlaceholders, sql
|
|
16
|
+
import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
|
|
19
17
|
|
|
20
|
-
// src/
|
|
18
|
+
// src/sql/result-mapper.ts
|
|
21
19
|
import {
|
|
22
|
-
is,
|
|
23
20
|
Column,
|
|
24
21
|
SQL,
|
|
25
22
|
getTableName,
|
|
26
|
-
|
|
23
|
+
is
|
|
27
24
|
} from "drizzle-orm";
|
|
25
|
+
import {
|
|
26
|
+
PgCustomColumn,
|
|
27
|
+
PgDate,
|
|
28
|
+
PgDateString,
|
|
29
|
+
PgInterval,
|
|
30
|
+
PgTime,
|
|
31
|
+
PgTimestamp,
|
|
32
|
+
PgTimestampString
|
|
33
|
+
} from "drizzle-orm/pg-core";
|
|
34
|
+
function toDecoderInput(decoder, value) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function normalizeInet(value) {
|
|
38
|
+
if (value && typeof value === "object" && "address" in value && typeof value.address !== "undefined") {
|
|
39
|
+
const { address, mask } = value;
|
|
40
|
+
if (typeof address === "bigint" || typeof address === "number") {
|
|
41
|
+
const inet = typeof address === "number" ? BigInt(address) : address;
|
|
42
|
+
const maxIpv4 = (1n << 32n) - 1n;
|
|
43
|
+
if (inet >= 0 && inet <= maxIpv4) {
|
|
44
|
+
const num = Number(inet);
|
|
45
|
+
const octets = [
|
|
46
|
+
num >>> 24 & 255,
|
|
47
|
+
num >>> 16 & 255,
|
|
48
|
+
num >>> 8 & 255,
|
|
49
|
+
num & 255
|
|
50
|
+
];
|
|
51
|
+
const suffix = typeof mask === "number" && mask !== 32 ? `/${mask}` : "";
|
|
52
|
+
return `${octets.join(".")}${suffix}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const fallback = value.toString?.();
|
|
56
|
+
if (fallback && fallback !== "[object Object]") {
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function normalizeTimestampString(value, withTimezone) {
|
|
63
|
+
if (value instanceof Date) {
|
|
64
|
+
const iso = value.toISOString().replace("T", " ");
|
|
65
|
+
return withTimezone ? iso.replace("Z", "+00") : iso.replace("Z", "");
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
const normalized = value.replace("T", " ");
|
|
69
|
+
if (withTimezone) {
|
|
70
|
+
return normalized.includes("+") ? normalized : `${normalized}+00`;
|
|
71
|
+
}
|
|
72
|
+
return normalized.replace(/\+00$/, "");
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
function normalizeTimestamp(value, withTimezone) {
|
|
77
|
+
if (value instanceof Date) {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
const hasOffset = value.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(value.trim());
|
|
82
|
+
const spaced = value.replace(" ", "T");
|
|
83
|
+
const normalized = withTimezone || hasOffset ? spaced : `${spaced}+00`;
|
|
84
|
+
return new Date(normalized);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function normalizeDateString(value) {
|
|
89
|
+
if (value instanceof Date) {
|
|
90
|
+
return value.toISOString().slice(0, 10);
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === "string") {
|
|
93
|
+
return value.slice(0, 10);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
function normalizeDateValue(value) {
|
|
98
|
+
if (value instanceof Date) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === "string") {
|
|
102
|
+
return new Date(`${value.slice(0, 10)}T00:00:00Z`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function normalizeTime(value) {
|
|
107
|
+
if (typeof value === "bigint") {
|
|
108
|
+
const totalMillis = Number(value) / 1000;
|
|
109
|
+
const date = new Date(totalMillis);
|
|
110
|
+
return date.toISOString().split("T")[1].replace("Z", "");
|
|
111
|
+
}
|
|
112
|
+
if (value instanceof Date) {
|
|
113
|
+
return value.toISOString().split("T")[1].replace("Z", "");
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function normalizeInterval(value) {
|
|
118
|
+
if (value && typeof value === "object" && "days" in value && "months" in value) {
|
|
119
|
+
const { months, days, micros } = value;
|
|
120
|
+
if (months === 0 && days !== undefined) {
|
|
121
|
+
if (micros && Number(micros) !== 0) {
|
|
122
|
+
const seconds = Number(micros) / 1e6;
|
|
123
|
+
return `${days} day${days === 1 ? "" : "s"} ${seconds} seconds`.trim();
|
|
124
|
+
}
|
|
125
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
function mapDriverValue(decoder, rawValue) {
|
|
131
|
+
if (is(decoder, PgTimestampString)) {
|
|
132
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTimestampString(rawValue, decoder.withTimezone)));
|
|
133
|
+
}
|
|
134
|
+
if (is(decoder, PgTimestamp)) {
|
|
135
|
+
const normalized = normalizeTimestamp(rawValue, decoder.withTimezone);
|
|
136
|
+
if (normalized instanceof Date) {
|
|
137
|
+
return normalized;
|
|
138
|
+
}
|
|
139
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalized));
|
|
140
|
+
}
|
|
141
|
+
if (is(decoder, PgDateString)) {
|
|
142
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateString(rawValue)));
|
|
143
|
+
}
|
|
144
|
+
if (is(decoder, PgDate)) {
|
|
145
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateValue(rawValue)));
|
|
146
|
+
}
|
|
147
|
+
if (is(decoder, PgTime)) {
|
|
148
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTime(rawValue)));
|
|
149
|
+
}
|
|
150
|
+
if (is(decoder, PgInterval)) {
|
|
151
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeInterval(rawValue)));
|
|
152
|
+
}
|
|
153
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, rawValue));
|
|
154
|
+
}
|
|
28
155
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
29
156
|
const nullifyMap = {};
|
|
30
|
-
const result = columns.reduce((
|
|
157
|
+
const result = columns.reduce((acc, { path, field }, columnIndex) => {
|
|
31
158
|
let decoder;
|
|
32
159
|
if (is(field, Column)) {
|
|
33
160
|
decoder = field;
|
|
34
161
|
} else if (is(field, SQL)) {
|
|
35
162
|
decoder = field.decoder;
|
|
36
163
|
} else {
|
|
37
|
-
|
|
164
|
+
const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
|
|
165
|
+
if (is(col, PgCustomColumn)) {
|
|
166
|
+
decoder = col;
|
|
167
|
+
} else {
|
|
168
|
+
decoder = field.sql.decoder;
|
|
169
|
+
}
|
|
38
170
|
}
|
|
39
|
-
let node =
|
|
171
|
+
let node = acc;
|
|
40
172
|
for (const [pathChunkIndex, pathChunk] of path.entries()) {
|
|
41
173
|
if (pathChunkIndex < path.length - 1) {
|
|
42
174
|
if (!(pathChunk in node)) {
|
|
@@ -45,8 +177,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
45
177
|
node = node[pathChunk];
|
|
46
178
|
continue;
|
|
47
179
|
}
|
|
48
|
-
const rawValue = row[columnIndex];
|
|
49
|
-
const value = node[pathChunk] = rawValue === null ? null : decoder
|
|
180
|
+
const rawValue = normalizeInet(row[columnIndex]);
|
|
181
|
+
const value = node[pathChunk] = rawValue === null ? null : mapDriverValue(decoder, rawValue);
|
|
50
182
|
if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
|
|
51
183
|
const objectName = path[0];
|
|
52
184
|
if (!(objectName in nullifyMap)) {
|
|
@@ -73,7 +205,7 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
73
205
|
continue;
|
|
74
206
|
}
|
|
75
207
|
}
|
|
76
|
-
return
|
|
208
|
+
return acc;
|
|
77
209
|
}, {});
|
|
78
210
|
if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
|
|
79
211
|
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
|
|
@@ -84,87 +216,493 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
84
216
|
}
|
|
85
217
|
return result;
|
|
86
218
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
219
|
+
|
|
220
|
+
// src/session.ts
|
|
221
|
+
import { TransactionRollbackError } from "drizzle-orm/errors";
|
|
222
|
+
|
|
223
|
+
// src/client.ts
|
|
224
|
+
import {
|
|
225
|
+
listValue as listValue2,
|
|
226
|
+
timestampValue as timestampValue2
|
|
227
|
+
} from "@duckdb/node-api";
|
|
228
|
+
|
|
229
|
+
// src/value-wrappers.ts
|
|
230
|
+
import {
|
|
231
|
+
listValue,
|
|
232
|
+
arrayValue,
|
|
233
|
+
structValue,
|
|
234
|
+
mapValue,
|
|
235
|
+
blobValue,
|
|
236
|
+
timestampValue,
|
|
237
|
+
timestampTZValue
|
|
238
|
+
} from "@duckdb/node-api";
|
|
239
|
+
|
|
240
|
+
// src/value-wrappers-core.ts
|
|
241
|
+
var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
|
|
242
|
+
function isDuckDBWrapper(value) {
|
|
243
|
+
return value !== null && typeof value === "object" && DUCKDB_VALUE_MARKER in value && value[DUCKDB_VALUE_MARKER] === true;
|
|
244
|
+
}
|
|
245
|
+
function wrapList(data, elementType) {
|
|
246
|
+
return {
|
|
247
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
248
|
+
kind: "list",
|
|
249
|
+
data,
|
|
250
|
+
elementType
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function wrapArray(data, elementType, fixedLength) {
|
|
254
|
+
return {
|
|
255
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
256
|
+
kind: "array",
|
|
257
|
+
data,
|
|
258
|
+
elementType,
|
|
259
|
+
fixedLength
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function wrapStruct(data, schema) {
|
|
263
|
+
return {
|
|
264
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
265
|
+
kind: "struct",
|
|
266
|
+
data,
|
|
267
|
+
schema
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function wrapMap(data, valueType) {
|
|
271
|
+
return {
|
|
272
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
273
|
+
kind: "map",
|
|
274
|
+
data,
|
|
275
|
+
valueType
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function wrapTimestamp(data, withTimezone, precision) {
|
|
279
|
+
return {
|
|
280
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
281
|
+
kind: "timestamp",
|
|
282
|
+
data,
|
|
283
|
+
withTimezone,
|
|
284
|
+
precision
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function wrapBlob(data) {
|
|
288
|
+
return {
|
|
289
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
290
|
+
kind: "blob",
|
|
291
|
+
data
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function wrapJson(data) {
|
|
295
|
+
return {
|
|
296
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
297
|
+
kind: "json",
|
|
298
|
+
data
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/value-wrappers.ts
|
|
303
|
+
function dateToMicros(value) {
|
|
304
|
+
if (value instanceof Date) {
|
|
305
|
+
return BigInt(value.getTime()) * 1000n;
|
|
306
|
+
}
|
|
307
|
+
if (typeof value === "bigint") {
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
if (typeof value === "number") {
|
|
311
|
+
return BigInt(Math.trunc(value)) * 1000n;
|
|
312
|
+
}
|
|
313
|
+
let normalized = value;
|
|
314
|
+
if (!value.includes("T") && value.includes(" ")) {
|
|
315
|
+
normalized = value.replace(" ", "T");
|
|
316
|
+
}
|
|
317
|
+
if (!normalized.endsWith("Z") && !/[+-]\d{2}:?\d{2}$/.test(normalized)) {
|
|
318
|
+
normalized += "Z";
|
|
319
|
+
}
|
|
320
|
+
const date = new Date(normalized);
|
|
321
|
+
if (isNaN(date.getTime())) {
|
|
322
|
+
throw new Error(`Invalid timestamp string: ${value}`);
|
|
323
|
+
}
|
|
324
|
+
return BigInt(date.getTime()) * 1000n;
|
|
325
|
+
}
|
|
326
|
+
function toUint8Array(data) {
|
|
327
|
+
return data instanceof Uint8Array && !(data instanceof Buffer) ? data : new Uint8Array(data);
|
|
328
|
+
}
|
|
329
|
+
function convertStructEntries(data, toValue) {
|
|
330
|
+
const entries = {};
|
|
331
|
+
for (const [key, val] of Object.entries(data)) {
|
|
332
|
+
entries[key] = toValue(val);
|
|
333
|
+
}
|
|
334
|
+
return entries;
|
|
335
|
+
}
|
|
336
|
+
function convertMapEntries(data, toValue) {
|
|
337
|
+
return Object.entries(data).map(([key, val]) => ({
|
|
338
|
+
key,
|
|
339
|
+
value: toValue(val)
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
function wrapperToNodeApiValue(wrapper, toValue) {
|
|
343
|
+
switch (wrapper.kind) {
|
|
344
|
+
case "list":
|
|
345
|
+
return listValue(wrapper.data.map(toValue));
|
|
346
|
+
case "array":
|
|
347
|
+
return arrayValue(wrapper.data.map(toValue));
|
|
348
|
+
case "struct":
|
|
349
|
+
return structValue(convertStructEntries(wrapper.data, toValue));
|
|
350
|
+
case "map":
|
|
351
|
+
return mapValue(convertMapEntries(wrapper.data, toValue));
|
|
352
|
+
case "timestamp":
|
|
353
|
+
return wrapper.withTimezone ? timestampTZValue(dateToMicros(wrapper.data)) : timestampValue(dateToMicros(wrapper.data));
|
|
354
|
+
case "blob":
|
|
355
|
+
return blobValue(toUint8Array(wrapper.data));
|
|
356
|
+
case "json":
|
|
357
|
+
return JSON.stringify(wrapper.data);
|
|
358
|
+
default: {
|
|
359
|
+
const _exhaustive = wrapper;
|
|
360
|
+
throw new Error(`Unknown wrapper kind: ${_exhaustive.kind}`);
|
|
94
361
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/client.ts
|
|
366
|
+
function isPool(client) {
|
|
367
|
+
return typeof client.acquire === "function";
|
|
368
|
+
}
|
|
369
|
+
var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
|
|
370
|
+
function isPgArrayLiteral(value) {
|
|
371
|
+
return value.startsWith("{") && value.endsWith("}");
|
|
372
|
+
}
|
|
373
|
+
function parsePgArrayLiteral(value) {
|
|
374
|
+
const json = value.replace(/{/g, "[").replace(/}/g, "]");
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(json);
|
|
377
|
+
} catch {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function prepareParams(params, options = {}) {
|
|
382
|
+
return params.map((param) => {
|
|
383
|
+
if (typeof param === "string" && param.length > 0) {
|
|
384
|
+
const firstChar = param[0];
|
|
385
|
+
const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
|
|
386
|
+
if (maybeArrayLiteral) {
|
|
387
|
+
const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
|
|
388
|
+
if (trimmed && isPgArrayLiteral(trimmed)) {
|
|
389
|
+
if (options.rejectStringArrayLiterals) {
|
|
390
|
+
throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
|
|
391
|
+
}
|
|
392
|
+
if (options.warnOnStringArrayLiteral) {
|
|
393
|
+
options.warnOnStringArrayLiteral();
|
|
394
|
+
}
|
|
395
|
+
return parsePgArrayLiteral(trimmed);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
99
398
|
}
|
|
100
|
-
|
|
101
|
-
|
|
399
|
+
return param;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function toNodeApiValue(value) {
|
|
403
|
+
if (value == null)
|
|
404
|
+
return null;
|
|
405
|
+
const t = typeof value;
|
|
406
|
+
if (t === "string" || t === "number" || t === "bigint" || t === "boolean") {
|
|
407
|
+
return value;
|
|
408
|
+
}
|
|
409
|
+
if (t === "object" && DUCKDB_VALUE_MARKER in value) {
|
|
410
|
+
return wrapperToNodeApiValue(value, toNodeApiValue);
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(value)) {
|
|
413
|
+
return listValue2(value.map((inner) => toNodeApiValue(inner)));
|
|
414
|
+
}
|
|
415
|
+
if (value instanceof Date) {
|
|
416
|
+
return timestampValue2(BigInt(value.getTime()) * 1000n);
|
|
417
|
+
}
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
function deduplicateColumns(columns) {
|
|
421
|
+
const counts = new Map;
|
|
422
|
+
let hasDuplicates = false;
|
|
423
|
+
for (const column of columns) {
|
|
424
|
+
const next = (counts.get(column) ?? 0) + 1;
|
|
425
|
+
counts.set(column, next);
|
|
426
|
+
if (next > 1) {
|
|
427
|
+
hasDuplicates = true;
|
|
428
|
+
break;
|
|
102
429
|
}
|
|
103
|
-
|
|
104
|
-
|
|
430
|
+
}
|
|
431
|
+
if (!hasDuplicates) {
|
|
432
|
+
return columns;
|
|
433
|
+
}
|
|
434
|
+
counts.clear();
|
|
435
|
+
return columns.map((column) => {
|
|
436
|
+
const count = counts.get(column) ?? 0;
|
|
437
|
+
counts.set(column, count + 1);
|
|
438
|
+
return count === 0 ? column : `${column}_${count}`;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function destroyPreparedStatement(entry) {
|
|
442
|
+
if (!entry)
|
|
443
|
+
return;
|
|
444
|
+
try {
|
|
445
|
+
entry.statement.destroySync();
|
|
446
|
+
} catch {}
|
|
447
|
+
}
|
|
448
|
+
function getPreparedCache(connection, size) {
|
|
449
|
+
const store = connection;
|
|
450
|
+
const existing = store[PREPARED_CACHE];
|
|
451
|
+
if (existing) {
|
|
452
|
+
existing.size = size;
|
|
453
|
+
return existing;
|
|
454
|
+
}
|
|
455
|
+
const cache = { size, entries: new Map };
|
|
456
|
+
store[PREPARED_CACHE] = cache;
|
|
457
|
+
return cache;
|
|
458
|
+
}
|
|
459
|
+
function evictOldest(cache) {
|
|
460
|
+
const oldest = cache.entries.keys().next();
|
|
461
|
+
if (!oldest.done) {
|
|
462
|
+
const key = oldest.value;
|
|
463
|
+
const entry = cache.entries.get(key);
|
|
464
|
+
cache.entries.delete(key);
|
|
465
|
+
destroyPreparedStatement(entry);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function evictCacheEntry(cache, key) {
|
|
469
|
+
const entry = cache.entries.get(key);
|
|
470
|
+
cache.entries.delete(key);
|
|
471
|
+
destroyPreparedStatement(entry);
|
|
472
|
+
}
|
|
473
|
+
async function getOrPrepareStatement(connection, query, cacheConfig) {
|
|
474
|
+
const cache = getPreparedCache(connection, cacheConfig.size);
|
|
475
|
+
const cached = cache.entries.get(query);
|
|
476
|
+
if (cached) {
|
|
477
|
+
cache.entries.delete(query);
|
|
478
|
+
cache.entries.set(query, cached);
|
|
479
|
+
return cached.statement;
|
|
480
|
+
}
|
|
481
|
+
const statement = await connection.prepare(query);
|
|
482
|
+
cache.entries.set(query, { statement });
|
|
483
|
+
while (cache.entries.size > cache.size) {
|
|
484
|
+
evictOldest(cache);
|
|
485
|
+
}
|
|
486
|
+
return statement;
|
|
487
|
+
}
|
|
488
|
+
async function materializeResultRows(result) {
|
|
489
|
+
const rows = await result.getRowsJS() ?? [];
|
|
490
|
+
const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
491
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
|
|
492
|
+
return { columns, rows };
|
|
493
|
+
}
|
|
494
|
+
async function materializeRows(client, query, params, options = {}) {
|
|
495
|
+
if (isPool(client)) {
|
|
496
|
+
const connection2 = await client.acquire();
|
|
497
|
+
try {
|
|
498
|
+
return await materializeRows(connection2, query, params, options);
|
|
499
|
+
} finally {
|
|
500
|
+
await client.release(connection2);
|
|
105
501
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
502
|
+
}
|
|
503
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
504
|
+
const connection = client;
|
|
505
|
+
if (options.prepareCache && typeof connection.prepare === "function") {
|
|
506
|
+
const cache = getPreparedCache(connection, options.prepareCache.size);
|
|
507
|
+
try {
|
|
508
|
+
const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
|
|
509
|
+
if (values) {
|
|
510
|
+
statement.bind(values);
|
|
511
|
+
} else {
|
|
512
|
+
statement.clearBindings?.();
|
|
513
|
+
}
|
|
514
|
+
const result2 = await statement.run();
|
|
515
|
+
cache.entries.delete(query);
|
|
516
|
+
cache.entries.set(query, { statement });
|
|
517
|
+
return await materializeResultRows(result2);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
evictCacheEntry(cache, query);
|
|
520
|
+
throw error;
|
|
115
521
|
}
|
|
116
|
-
|
|
117
|
-
|
|
522
|
+
}
|
|
523
|
+
const result = await connection.run(query, values);
|
|
524
|
+
return await materializeResultRows(result);
|
|
525
|
+
}
|
|
526
|
+
function clearPreparedCache(connection) {
|
|
527
|
+
const store = connection;
|
|
528
|
+
const cache = store[PREPARED_CACHE];
|
|
529
|
+
if (!cache)
|
|
530
|
+
return;
|
|
531
|
+
for (const entry of cache.entries.values()) {
|
|
532
|
+
destroyPreparedStatement(entry);
|
|
533
|
+
}
|
|
534
|
+
cache.entries.clear();
|
|
535
|
+
}
|
|
536
|
+
function mapRowsToObjects(columns, rows) {
|
|
537
|
+
return rows.map((vals) => {
|
|
538
|
+
const obj = {};
|
|
539
|
+
columns.forEach((col, idx) => {
|
|
540
|
+
obj[col] = vals[idx];
|
|
541
|
+
});
|
|
542
|
+
return obj;
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async function closeClientConnection(connection) {
|
|
546
|
+
clearPreparedCache(connection);
|
|
547
|
+
if ("close" in connection && typeof connection.close === "function") {
|
|
548
|
+
await connection.close();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if ("closeSync" in connection && typeof connection.closeSync === "function") {
|
|
552
|
+
connection.closeSync();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if ("disconnectSync" in connection && typeof connection.disconnectSync === "function") {
|
|
556
|
+
connection.disconnectSync();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function executeOnClient(client, query, params, options = {}) {
|
|
560
|
+
const { columns, rows } = await materializeRows(client, query, params, options);
|
|
561
|
+
if (!rows || rows.length === 0) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
return mapRowsToObjects(columns, rows);
|
|
565
|
+
}
|
|
566
|
+
async function executeArraysOnClient(client, query, params, options = {}) {
|
|
567
|
+
return await materializeRows(client, query, params, options);
|
|
568
|
+
}
|
|
569
|
+
async function* executeInBatches(client, query, params, options = {}) {
|
|
570
|
+
if (isPool(client)) {
|
|
571
|
+
const connection = await client.acquire();
|
|
572
|
+
try {
|
|
573
|
+
yield* executeInBatches(connection, query, params, options);
|
|
574
|
+
return;
|
|
575
|
+
} finally {
|
|
576
|
+
await client.release(connection);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
580
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
581
|
+
const result = await client.stream(query, values);
|
|
582
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
583
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
584
|
+
let buffer = [];
|
|
585
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
586
|
+
const objects = mapRowsToObjects(columns, chunk);
|
|
587
|
+
for (const row of objects) {
|
|
588
|
+
buffer.push(row);
|
|
589
|
+
if (buffer.length >= rowsPerChunk) {
|
|
590
|
+
yield buffer;
|
|
591
|
+
buffer = [];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (buffer.length > 0) {
|
|
596
|
+
yield buffer;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
async function* executeInBatchesRaw(client, query, params, options = {}) {
|
|
600
|
+
if (isPool(client)) {
|
|
601
|
+
const connection = await client.acquire();
|
|
602
|
+
try {
|
|
603
|
+
yield* executeInBatchesRaw(connection, query, params, options);
|
|
604
|
+
return;
|
|
605
|
+
} finally {
|
|
606
|
+
await client.release(connection);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
610
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
611
|
+
const result = await client.stream(query, values);
|
|
612
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
613
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
614
|
+
let buffer = [];
|
|
615
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
616
|
+
for (const row of chunk) {
|
|
617
|
+
buffer.push(row);
|
|
618
|
+
if (buffer.length >= rowsPerChunk) {
|
|
619
|
+
yield { columns, rows: buffer };
|
|
620
|
+
buffer = [];
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (buffer.length > 0) {
|
|
625
|
+
yield { columns, rows: buffer };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async function executeArrowOnClient(client, query, params) {
|
|
629
|
+
if (isPool(client)) {
|
|
630
|
+
const connection = await client.acquire();
|
|
631
|
+
try {
|
|
632
|
+
return await executeArrowOnClient(connection, query, params);
|
|
633
|
+
} finally {
|
|
634
|
+
await client.release(connection);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
638
|
+
const result = await client.run(query, values);
|
|
639
|
+
const maybeArrow = result.toArrow ?? result.getArrowTable;
|
|
640
|
+
if (typeof maybeArrow === "function") {
|
|
641
|
+
return await maybeArrow.call(result);
|
|
642
|
+
}
|
|
643
|
+
return result.getColumnsObjectJS();
|
|
118
644
|
}
|
|
119
|
-
var tableIdPropSelectionRegex = new RegExp([
|
|
120
|
-
`("(.+)"\\."(.+)")`,
|
|
121
|
-
`(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
|
|
122
|
-
].join(""), "i");
|
|
123
645
|
|
|
124
646
|
// src/session.ts
|
|
125
|
-
|
|
647
|
+
function isSavepointSyntaxError(error) {
|
|
648
|
+
if (!(error instanceof Error) || !error.message) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
652
|
+
}
|
|
126
653
|
|
|
127
654
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
128
655
|
client;
|
|
656
|
+
dialect;
|
|
129
657
|
queryString;
|
|
130
658
|
params;
|
|
131
659
|
logger;
|
|
132
660
|
fields;
|
|
133
661
|
_isResponseInArrayMode;
|
|
134
662
|
customResultMapper;
|
|
663
|
+
rejectStringArrayLiterals;
|
|
664
|
+
prepareCache;
|
|
665
|
+
warnOnStringArrayLiteral;
|
|
135
666
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
136
|
-
constructor(client, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper) {
|
|
667
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
137
668
|
super({ sql: queryString, params });
|
|
138
669
|
this.client = client;
|
|
670
|
+
this.dialect = dialect;
|
|
139
671
|
this.queryString = queryString;
|
|
140
672
|
this.params = params;
|
|
141
673
|
this.logger = logger;
|
|
142
674
|
this.fields = fields;
|
|
143
675
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
144
676
|
this.customResultMapper = customResultMapper;
|
|
677
|
+
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
678
|
+
this.prepareCache = prepareCache;
|
|
679
|
+
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
145
680
|
}
|
|
146
681
|
async execute(placeholderValues = {}) {
|
|
147
|
-
|
|
682
|
+
this.dialect.assertNoPgJsonColumns();
|
|
683
|
+
const params = prepareParams(fillPlaceholders(this.params, placeholderValues), {
|
|
684
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
685
|
+
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
686
|
+
});
|
|
148
687
|
this.logger.logQuery(this.queryString, params);
|
|
149
|
-
const {
|
|
150
|
-
|
|
151
|
-
client,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
688
|
+
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
689
|
+
if (fields) {
|
|
690
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params, { prepareCache: this.prepareCache });
|
|
691
|
+
if (rows2.length === 0) {
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
695
|
+
}
|
|
696
|
+
const rows = await executeOnClient(this.client, this.queryString, params, {
|
|
697
|
+
prepareCache: this.prepareCache
|
|
698
|
+
});
|
|
699
|
+
return rows;
|
|
162
700
|
}
|
|
163
701
|
all(placeholderValues = {}) {
|
|
164
702
|
return this.execute(placeholderValues);
|
|
165
703
|
}
|
|
166
704
|
isResponseInArrayMode() {
|
|
167
|
-
return
|
|
705
|
+
return this._isResponseInArrayMode;
|
|
168
706
|
}
|
|
169
707
|
}
|
|
170
708
|
|
|
@@ -173,32 +711,117 @@ class DuckDBSession extends PgSession {
|
|
|
173
711
|
schema;
|
|
174
712
|
options;
|
|
175
713
|
static [entityKind] = "DuckDBSession";
|
|
714
|
+
dialect;
|
|
176
715
|
logger;
|
|
716
|
+
rejectStringArrayLiterals;
|
|
717
|
+
prepareCache;
|
|
718
|
+
hasWarnedArrayLiteral = false;
|
|
719
|
+
rollbackOnly = false;
|
|
177
720
|
constructor(client, dialect, schema, options = {}) {
|
|
178
721
|
super(dialect);
|
|
179
722
|
this.client = client;
|
|
180
723
|
this.schema = schema;
|
|
181
724
|
this.options = options;
|
|
725
|
+
this.dialect = dialect;
|
|
182
726
|
this.logger = options.logger ?? new NoopLogger;
|
|
727
|
+
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
728
|
+
this.prepareCache = options.prepareCache;
|
|
729
|
+
this.options = {
|
|
730
|
+
...options,
|
|
731
|
+
prepareCache: this.prepareCache
|
|
732
|
+
};
|
|
183
733
|
}
|
|
184
734
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
185
|
-
return new DuckDBPreparedQuery(this.client, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper);
|
|
735
|
+
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rejectStringArrayLiterals, this.prepareCache, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
|
|
186
736
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
737
|
+
execute(query) {
|
|
738
|
+
this.dialect.resetPgJsonFlag();
|
|
739
|
+
return super.execute(query);
|
|
740
|
+
}
|
|
741
|
+
all(query) {
|
|
742
|
+
this.dialect.resetPgJsonFlag();
|
|
743
|
+
return super.all(query);
|
|
744
|
+
}
|
|
745
|
+
async transaction(transaction, config) {
|
|
746
|
+
let pinnedConnection;
|
|
747
|
+
let pool;
|
|
748
|
+
let clientForTx = this.client;
|
|
749
|
+
if (isPool(this.client)) {
|
|
750
|
+
pool = this.client;
|
|
751
|
+
pinnedConnection = await pool.acquire();
|
|
752
|
+
clientForTx = pinnedConnection;
|
|
753
|
+
}
|
|
754
|
+
const session = new DuckDBSession(clientForTx, this.dialect, this.schema, this.options);
|
|
190
755
|
const tx = new DuckDBTransaction(this.dialect, session, this.schema);
|
|
191
|
-
await tx.execute(sql2`BEGIN TRANSACTION;`);
|
|
192
756
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
757
|
+
await tx.execute(sql`BEGIN TRANSACTION;`);
|
|
758
|
+
if (config) {
|
|
759
|
+
await tx.setTransaction(config);
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const result = await transaction(tx);
|
|
763
|
+
if (session.isRollbackOnly()) {
|
|
764
|
+
await tx.execute(sql`rollback`);
|
|
765
|
+
throw new TransactionRollbackError;
|
|
766
|
+
}
|
|
767
|
+
await tx.execute(sql`commit`);
|
|
768
|
+
return result;
|
|
769
|
+
} catch (error) {
|
|
770
|
+
await tx.execute(sql`rollback`);
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
199
773
|
} finally {
|
|
200
|
-
|
|
774
|
+
if (pinnedConnection && pool) {
|
|
775
|
+
await pool.release(pinnedConnection);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
warnOnStringArrayLiteral = (query) => {
|
|
780
|
+
if (this.hasWarnedArrayLiteral) {
|
|
781
|
+
return;
|
|
201
782
|
}
|
|
783
|
+
this.hasWarnedArrayLiteral = true;
|
|
784
|
+
this.logger.logQuery(`[duckdb] ${arrayLiteralWarning}
|
|
785
|
+
query: ${query}`, []);
|
|
786
|
+
};
|
|
787
|
+
executeBatches(query, options = {}) {
|
|
788
|
+
this.dialect.resetPgJsonFlag();
|
|
789
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
790
|
+
this.dialect.assertNoPgJsonColumns();
|
|
791
|
+
const params = prepareParams(builtQuery.params, {
|
|
792
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
793
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
794
|
+
});
|
|
795
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
796
|
+
return executeInBatches(this.client, builtQuery.sql, params, options);
|
|
797
|
+
}
|
|
798
|
+
executeBatchesRaw(query, options = {}) {
|
|
799
|
+
this.dialect.resetPgJsonFlag();
|
|
800
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
801
|
+
this.dialect.assertNoPgJsonColumns();
|
|
802
|
+
const params = prepareParams(builtQuery.params, {
|
|
803
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
804
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
805
|
+
});
|
|
806
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
807
|
+
return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
|
|
808
|
+
}
|
|
809
|
+
async executeArrow(query) {
|
|
810
|
+
this.dialect.resetPgJsonFlag();
|
|
811
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
812
|
+
this.dialect.assertNoPgJsonColumns();
|
|
813
|
+
const params = prepareParams(builtQuery.params, {
|
|
814
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
815
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
816
|
+
});
|
|
817
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
818
|
+
return executeArrowOnClient(this.client, builtQuery.sql, params);
|
|
819
|
+
}
|
|
820
|
+
markRollbackOnly() {
|
|
821
|
+
this.rollbackOnly = true;
|
|
822
|
+
}
|
|
823
|
+
isRollbackOnly() {
|
|
824
|
+
return this.rollbackOnly;
|
|
202
825
|
}
|
|
203
826
|
}
|
|
204
827
|
|
|
@@ -218,117 +841,1299 @@ class DuckDBTransaction extends PgTransaction {
|
|
|
218
841
|
if (typeof config.deferrable === "boolean") {
|
|
219
842
|
chunks.push(config.deferrable ? "deferrable" : "not deferrable");
|
|
220
843
|
}
|
|
221
|
-
return
|
|
844
|
+
return sql.raw(chunks.join(" "));
|
|
222
845
|
}
|
|
223
846
|
setTransaction(config) {
|
|
224
|
-
return this.session.execute(
|
|
847
|
+
return this.session.execute(sql`set transaction ${this.getTransactionConfigSQL(config)}`);
|
|
848
|
+
}
|
|
849
|
+
executeBatches(query, options = {}) {
|
|
850
|
+
return this.session.executeBatches(query, options);
|
|
851
|
+
}
|
|
852
|
+
executeBatchesRaw(query, options = {}) {
|
|
853
|
+
return this.session.executeBatchesRaw(query, options);
|
|
854
|
+
}
|
|
855
|
+
executeArrow(query) {
|
|
856
|
+
return this.session.executeArrow(query);
|
|
225
857
|
}
|
|
226
858
|
async transaction(transaction) {
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
859
|
+
const internals = this;
|
|
860
|
+
const savepoint = `drizzle_savepoint_${this.nestedIndex + 1}`;
|
|
861
|
+
const savepointSql = sql.raw(`savepoint ${savepoint}`);
|
|
862
|
+
const releaseSql = sql.raw(`release savepoint ${savepoint}`);
|
|
863
|
+
const rollbackSql = sql.raw(`rollback to savepoint ${savepoint}`);
|
|
864
|
+
const nestedTx = new DuckDBTransaction(internals.dialect, internals.session, this.schema, this.nestedIndex + 1);
|
|
865
|
+
if (internals.dialect.areSavepointsUnsupported()) {
|
|
866
|
+
return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
|
|
867
|
+
}
|
|
868
|
+
let createdSavepoint = false;
|
|
869
|
+
try {
|
|
870
|
+
await internals.session.execute(savepointSql);
|
|
871
|
+
internals.dialect.markSavepointsSupported();
|
|
872
|
+
createdSavepoint = true;
|
|
873
|
+
} catch (error) {
|
|
874
|
+
if (!isSavepointSyntaxError(error)) {
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
internals.dialect.markSavepointsUnsupported();
|
|
878
|
+
return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
|
|
879
|
+
}
|
|
230
880
|
try {
|
|
231
|
-
const result = await transaction(
|
|
232
|
-
|
|
881
|
+
const result = await transaction(nestedTx);
|
|
882
|
+
if (createdSavepoint) {
|
|
883
|
+
await internals.session.execute(releaseSql);
|
|
884
|
+
}
|
|
233
885
|
return result;
|
|
234
|
-
} catch (
|
|
235
|
-
|
|
236
|
-
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (createdSavepoint) {
|
|
888
|
+
await internals.session.execute(rollbackSql);
|
|
889
|
+
}
|
|
890
|
+
internals.session.markRollbackOnly();
|
|
891
|
+
throw error;
|
|
237
892
|
}
|
|
238
893
|
}
|
|
894
|
+
runNestedWithoutSavepoint(transaction, nestedTx, internals) {
|
|
895
|
+
return transaction(nestedTx).catch((error) => {
|
|
896
|
+
internals.session.markRollbackOnly();
|
|
897
|
+
throw error;
|
|
898
|
+
});
|
|
899
|
+
}
|
|
239
900
|
}
|
|
901
|
+
var arrayLiteralWarning = "Received a stringified Postgres-style array literal. Use duckDbList()/duckDbArray() or pass native arrays instead. You can also set rejectStringArrayLiterals=true to throw.";
|
|
240
902
|
|
|
241
903
|
// src/dialect.ts
|
|
242
904
|
import { entityKind as entityKind2, is as is2 } from "drizzle-orm/entity";
|
|
243
905
|
import {
|
|
244
|
-
PgDate,
|
|
245
|
-
PgDateString,
|
|
906
|
+
PgDate as PgDate2,
|
|
907
|
+
PgDateString as PgDateString2,
|
|
246
908
|
PgDialect,
|
|
247
909
|
PgJson,
|
|
248
910
|
PgJsonb,
|
|
249
911
|
PgNumeric,
|
|
250
|
-
PgTime,
|
|
251
|
-
PgTimestamp,
|
|
252
|
-
PgTimestampString,
|
|
912
|
+
PgTime as PgTime2,
|
|
913
|
+
PgTimestamp as PgTimestamp2,
|
|
914
|
+
PgTimestampString as PgTimestampString2,
|
|
253
915
|
PgUUID
|
|
254
916
|
} from "drizzle-orm/pg-core";
|
|
255
917
|
import {
|
|
256
|
-
sql as
|
|
918
|
+
sql as sql2
|
|
257
919
|
} from "drizzle-orm";
|
|
258
920
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
921
|
+
// src/sql/ast-transformer.ts
|
|
922
|
+
import nodeSqlParser from "node-sql-parser";
|
|
923
|
+
|
|
924
|
+
// src/sql/visitors/array-operators.ts
|
|
925
|
+
var OPERATOR_MAP = {
|
|
926
|
+
"@>": { fn: "array_has_all" },
|
|
927
|
+
"<@": { fn: "array_has_all", swap: true },
|
|
928
|
+
"&&": { fn: "array_has_any" }
|
|
929
|
+
};
|
|
930
|
+
function walkExpression(expr, parent, key) {
|
|
931
|
+
if (!expr || typeof expr !== "object")
|
|
932
|
+
return false;
|
|
933
|
+
let transformed = false;
|
|
934
|
+
const exprObj = expr;
|
|
935
|
+
if ("type" in expr && exprObj.type === "binary_expr") {
|
|
936
|
+
const binary = expr;
|
|
937
|
+
const mapping = OPERATOR_MAP[binary.operator];
|
|
938
|
+
if (mapping) {
|
|
939
|
+
const fnExpr = {
|
|
940
|
+
type: "function",
|
|
941
|
+
name: { name: [{ type: "default", value: mapping.fn }] },
|
|
942
|
+
args: {
|
|
943
|
+
type: "expr_list",
|
|
944
|
+
value: mapping.swap ? [binary.right, binary.left] : [binary.left, binary.right]
|
|
283
945
|
}
|
|
946
|
+
};
|
|
947
|
+
if (parent && key) {
|
|
948
|
+
parent[key] = fnExpr;
|
|
284
949
|
}
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
prepareTyping(encoder) {
|
|
288
|
-
if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
|
|
289
|
-
throw new Error("JSON and JSONB types are not supported in DuckDB");
|
|
290
|
-
} else if (is2(encoder, PgNumeric)) {
|
|
291
|
-
return "decimal";
|
|
292
|
-
} else if (is2(encoder, PgTime)) {
|
|
293
|
-
return "time";
|
|
294
|
-
} else if (is2(encoder, PgTimestamp) || is2(encoder, PgTimestampString)) {
|
|
295
|
-
return "timestamp";
|
|
296
|
-
} else if (is2(encoder, PgDate) || is2(encoder, PgDateString)) {
|
|
297
|
-
return "date";
|
|
298
|
-
} else if (is2(encoder, PgUUID)) {
|
|
299
|
-
return "uuid";
|
|
950
|
+
transformed = true;
|
|
300
951
|
} else {
|
|
301
|
-
|
|
952
|
+
transformed = walkExpression(binary.left, binary, "left") || transformed;
|
|
953
|
+
transformed = walkExpression(binary.right, binary, "right") || transformed;
|
|
302
954
|
}
|
|
303
955
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
PgSelectBase,
|
|
309
|
-
PgSelectBuilder
|
|
310
|
-
} from "drizzle-orm/pg-core/query-builders";
|
|
311
|
-
import { SQL as SQL3 } from "drizzle-orm/sql/sql";
|
|
312
|
-
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
313
|
-
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
314
|
-
class DuckDBDriver {
|
|
315
|
-
client;
|
|
316
|
-
dialect;
|
|
317
|
-
options;
|
|
318
|
-
static [entityKind3] = "DuckDBDriver";
|
|
319
|
-
constructor(client, dialect, options = {}) {
|
|
320
|
-
this.client = client;
|
|
321
|
-
this.dialect = dialect;
|
|
322
|
-
this.options = options;
|
|
956
|
+
if ("type" in expr && exprObj.type === "unary_expr") {
|
|
957
|
+
if ("expr" in exprObj) {
|
|
958
|
+
transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
|
|
959
|
+
}
|
|
323
960
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
961
|
+
if ("type" in expr && exprObj.type === "case") {
|
|
962
|
+
if ("expr" in exprObj && exprObj.expr) {
|
|
963
|
+
transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
|
|
964
|
+
}
|
|
965
|
+
if ("args" in exprObj && Array.isArray(exprObj.args)) {
|
|
966
|
+
for (let i = 0;i < exprObj.args.length; i++) {
|
|
967
|
+
const whenClause = exprObj.args[i];
|
|
968
|
+
if (whenClause.cond) {
|
|
969
|
+
transformed = walkExpression(whenClause.cond, whenClause, "cond") || transformed;
|
|
970
|
+
}
|
|
971
|
+
if (whenClause.result) {
|
|
972
|
+
transformed = walkExpression(whenClause.result, whenClause, "result") || transformed;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if ("args" in expr && exprObj.args) {
|
|
978
|
+
const args = exprObj.args;
|
|
979
|
+
if ("value" in args && Array.isArray(args.value)) {
|
|
980
|
+
for (let i = 0;i < args.value.length; i++) {
|
|
981
|
+
transformed = walkExpression(args.value[i], args.value, String(i)) || transformed;
|
|
982
|
+
}
|
|
983
|
+
} else if ("expr" in args) {
|
|
984
|
+
transformed = walkExpression(args.expr, args, "expr") || transformed;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if ("ast" in exprObj && exprObj.ast) {
|
|
988
|
+
const subAst = exprObj.ast;
|
|
989
|
+
if (subAst.type === "select") {
|
|
990
|
+
transformed = walkSelectImpl(subAst) || transformed;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if ("type" in expr && exprObj.type === "expr_list") {
|
|
994
|
+
if ("value" in exprObj && Array.isArray(exprObj.value)) {
|
|
995
|
+
for (let i = 0;i < exprObj.value.length; i++) {
|
|
996
|
+
transformed = walkExpression(exprObj.value[i], exprObj.value, String(i)) || transformed;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return transformed;
|
|
1001
|
+
}
|
|
1002
|
+
function walkFrom(from) {
|
|
1003
|
+
if (!from || !Array.isArray(from))
|
|
1004
|
+
return false;
|
|
1005
|
+
let transformed = false;
|
|
1006
|
+
for (const f of from) {
|
|
1007
|
+
if ("join" in f) {
|
|
1008
|
+
const join = f;
|
|
1009
|
+
transformed = walkExpression(join.on, join, "on") || transformed;
|
|
1010
|
+
}
|
|
1011
|
+
if ("expr" in f && f.expr && "ast" in f.expr) {
|
|
1012
|
+
transformed = walkSelectImpl(f.expr.ast) || transformed;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return transformed;
|
|
1016
|
+
}
|
|
1017
|
+
function walkSelectImpl(select) {
|
|
1018
|
+
let transformed = false;
|
|
1019
|
+
if (select.with) {
|
|
1020
|
+
for (const cte of select.with) {
|
|
1021
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1022
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1023
|
+
transformed = walkSelectImpl(cteSelect) || transformed;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (Array.isArray(select.from)) {
|
|
1028
|
+
transformed = walkFrom(select.from) || transformed;
|
|
1029
|
+
}
|
|
1030
|
+
transformed = walkExpression(select.where, select, "where") || transformed;
|
|
1031
|
+
if (select.having) {
|
|
1032
|
+
if (Array.isArray(select.having)) {
|
|
1033
|
+
for (let i = 0;i < select.having.length; i++) {
|
|
1034
|
+
transformed = walkExpression(select.having[i], select.having, String(i)) || transformed;
|
|
1035
|
+
}
|
|
1036
|
+
} else {
|
|
1037
|
+
transformed = walkExpression(select.having, select, "having") || transformed;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (Array.isArray(select.columns)) {
|
|
1041
|
+
for (const col of select.columns) {
|
|
1042
|
+
if ("expr" in col) {
|
|
1043
|
+
transformed = walkExpression(col.expr, col, "expr") || transformed;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (select._next) {
|
|
1048
|
+
transformed = walkSelectImpl(select._next) || transformed;
|
|
1049
|
+
}
|
|
1050
|
+
return transformed;
|
|
1051
|
+
}
|
|
1052
|
+
function transformArrayOperators(ast) {
|
|
1053
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1054
|
+
let transformed = false;
|
|
1055
|
+
for (const stmt of statements) {
|
|
1056
|
+
if (stmt.type === "select") {
|
|
1057
|
+
transformed = walkSelectImpl(stmt) || transformed;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return transformed;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/sql/visitors/column-qualifier.ts
|
|
1064
|
+
function getTableSource(from) {
|
|
1065
|
+
if ("table" in from && from.table) {
|
|
1066
|
+
return {
|
|
1067
|
+
name: from.table,
|
|
1068
|
+
alias: from.as ?? null,
|
|
1069
|
+
schema: "db" in from ? from.db ?? null : null
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if ("expr" in from && from.as) {
|
|
1073
|
+
return {
|
|
1074
|
+
name: from.as,
|
|
1075
|
+
alias: from.as,
|
|
1076
|
+
schema: null
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
function getQualifier(source) {
|
|
1082
|
+
return {
|
|
1083
|
+
table: source.alias ?? source.name,
|
|
1084
|
+
schema: source.schema
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function isUnqualifiedColumnRef(expr) {
|
|
1088
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && (!("table" in expr) || !expr.table);
|
|
1089
|
+
}
|
|
1090
|
+
function isQualifiedColumnRef(expr) {
|
|
1091
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && "table" in expr && !!expr.table;
|
|
1092
|
+
}
|
|
1093
|
+
function getColumnName(col) {
|
|
1094
|
+
if (typeof col.column === "string") {
|
|
1095
|
+
return col.column;
|
|
1096
|
+
}
|
|
1097
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1098
|
+
return String(col.column.expr.value);
|
|
1099
|
+
}
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
function applyQualifier(col, qualifier) {
|
|
1103
|
+
col.table = qualifier.table;
|
|
1104
|
+
if (!("schema" in col) || !col.schema) {
|
|
1105
|
+
col.schema = qualifier.schema;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function unwrapColumnRef(expr) {
|
|
1109
|
+
if (!expr || typeof expr !== "object")
|
|
1110
|
+
return null;
|
|
1111
|
+
if ("type" in expr && expr.type === "column_ref") {
|
|
1112
|
+
return expr;
|
|
1113
|
+
}
|
|
1114
|
+
if ("expr" in expr && expr.expr) {
|
|
1115
|
+
return unwrapColumnRef(expr.expr);
|
|
1116
|
+
}
|
|
1117
|
+
if ("ast" in expr && expr.ast && typeof expr.ast === "object") {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
if ("args" in expr && expr.args) {
|
|
1121
|
+
const args = expr.args;
|
|
1122
|
+
if (args.expr) {
|
|
1123
|
+
return unwrapColumnRef(args.expr);
|
|
1124
|
+
}
|
|
1125
|
+
if (args.value && args.value.length === 1) {
|
|
1126
|
+
return unwrapColumnRef(args.value[0]);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
function isBinaryExpr(expr) {
|
|
1132
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1133
|
+
}
|
|
1134
|
+
function walkOnClause(expr, leftQualifier, rightQualifier, ambiguousColumns) {
|
|
1135
|
+
if (!expr || typeof expr !== "object")
|
|
1136
|
+
return false;
|
|
1137
|
+
let transformed = false;
|
|
1138
|
+
if (isBinaryExpr(expr)) {
|
|
1139
|
+
const left = expr.left;
|
|
1140
|
+
const right = expr.right;
|
|
1141
|
+
const leftCol = unwrapColumnRef(left);
|
|
1142
|
+
const rightCol = unwrapColumnRef(right);
|
|
1143
|
+
const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
|
|
1144
|
+
const rightUnqualified = rightCol ? isUnqualifiedColumnRef(rightCol) : false;
|
|
1145
|
+
const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
|
|
1146
|
+
const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
|
|
1147
|
+
const leftColName = leftCol ? getColumnName(leftCol) : null;
|
|
1148
|
+
const rightColName = rightCol ? getColumnName(rightCol) : null;
|
|
1149
|
+
if (expr.operator === "=" && leftColName && rightColName && leftColName === rightColName) {
|
|
1150
|
+
if (leftUnqualified && rightUnqualified) {
|
|
1151
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1152
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1153
|
+
ambiguousColumns.add(leftColName);
|
|
1154
|
+
transformed = true;
|
|
1155
|
+
} else if (leftQualified && rightUnqualified) {
|
|
1156
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1157
|
+
ambiguousColumns.add(rightColName);
|
|
1158
|
+
transformed = true;
|
|
1159
|
+
} else if (leftUnqualified && rightQualified) {
|
|
1160
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1161
|
+
ambiguousColumns.add(leftColName);
|
|
1162
|
+
transformed = true;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (expr.operator === "=" && leftCol && rightCol && leftColName && rightColName && leftColName !== rightColName) {
|
|
1166
|
+
if (leftQualified && rightUnqualified && !rightColName.includes(".")) {
|
|
1167
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1168
|
+
transformed = true;
|
|
1169
|
+
} else if (leftUnqualified && rightQualified && !leftColName.includes(".")) {
|
|
1170
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1171
|
+
transformed = true;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1175
|
+
transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1176
|
+
}
|
|
1177
|
+
return transformed;
|
|
1178
|
+
}
|
|
1179
|
+
function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns) {
|
|
1180
|
+
if (!expr || typeof expr !== "object")
|
|
1181
|
+
return false;
|
|
1182
|
+
let transformed = false;
|
|
1183
|
+
if (isUnqualifiedColumnRef(expr)) {
|
|
1184
|
+
const colName = getColumnName(expr);
|
|
1185
|
+
if (colName && ambiguousColumns.has(colName)) {
|
|
1186
|
+
applyQualifier(expr, defaultQualifier);
|
|
1187
|
+
transformed = true;
|
|
1188
|
+
}
|
|
1189
|
+
return transformed;
|
|
1190
|
+
}
|
|
1191
|
+
if (isBinaryExpr(expr)) {
|
|
1192
|
+
const binary = expr;
|
|
1193
|
+
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1194
|
+
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
1195
|
+
return transformed;
|
|
1196
|
+
}
|
|
1197
|
+
if ("args" in expr && expr.args) {
|
|
1198
|
+
const args = expr.args;
|
|
1199
|
+
if (args.value && Array.isArray(args.value)) {
|
|
1200
|
+
for (const arg of args.value) {
|
|
1201
|
+
transformed = qualifyAmbiguousInExpression(arg, defaultQualifier, ambiguousColumns) || transformed;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (args.expr) {
|
|
1205
|
+
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if ("over" in expr && expr.over && typeof expr.over === "object") {
|
|
1209
|
+
const over = expr.over;
|
|
1210
|
+
if (Array.isArray(over.partition)) {
|
|
1211
|
+
for (const part of over.partition) {
|
|
1212
|
+
transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (Array.isArray(over.orderby)) {
|
|
1216
|
+
for (const order of over.orderby) {
|
|
1217
|
+
transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return transformed;
|
|
1222
|
+
}
|
|
1223
|
+
function hasUnqualifiedColumns(expr) {
|
|
1224
|
+
if (!expr || typeof expr !== "object")
|
|
1225
|
+
return false;
|
|
1226
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1227
|
+
const left = expr.left;
|
|
1228
|
+
const right = expr.right;
|
|
1229
|
+
const leftCol = unwrapColumnRef(left);
|
|
1230
|
+
const rightCol = unwrapColumnRef(right);
|
|
1231
|
+
if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
|
|
1235
|
+
return true;
|
|
1236
|
+
if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
if ("args" in expr && expr.args) {
|
|
1240
|
+
const args = expr.args;
|
|
1241
|
+
if (args.expr && isUnqualifiedColumnRef(args.expr))
|
|
1242
|
+
return true;
|
|
1243
|
+
if (args.value) {
|
|
1244
|
+
for (const arg of args.value) {
|
|
1245
|
+
if (isUnqualifiedColumnRef(arg))
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
function walkSelect(select) {
|
|
1253
|
+
let transformed = false;
|
|
1254
|
+
const ambiguousColumns = new Set;
|
|
1255
|
+
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1256
|
+
const firstSource = getTableSource(select.from[0]);
|
|
1257
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
|
|
1258
|
+
let prevSource = firstSource;
|
|
1259
|
+
let hasAnyUnqualified = false;
|
|
1260
|
+
for (const from of select.from) {
|
|
1261
|
+
if ("join" in from) {
|
|
1262
|
+
const join = from;
|
|
1263
|
+
if (join.on && hasUnqualifiedColumns(join.on)) {
|
|
1264
|
+
hasAnyUnqualified = true;
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (!hasAnyUnqualified) {
|
|
1270
|
+
for (const from of select.from) {
|
|
1271
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1272
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
for (const from of select.from) {
|
|
1277
|
+
if ("join" in from) {
|
|
1278
|
+
const join = from;
|
|
1279
|
+
const currentSource = getTableSource(join);
|
|
1280
|
+
if (join.on && prevSource && currentSource) {
|
|
1281
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1282
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1283
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1284
|
+
}
|
|
1285
|
+
if (join.using && prevSource && currentSource) {
|
|
1286
|
+
for (const usingCol of join.using) {
|
|
1287
|
+
if (typeof usingCol === "string") {
|
|
1288
|
+
ambiguousColumns.add(usingCol);
|
|
1289
|
+
} else if ("value" in usingCol) {
|
|
1290
|
+
ambiguousColumns.add(String(usingCol.value));
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
prevSource = currentSource;
|
|
1295
|
+
} else {
|
|
1296
|
+
const source = getTableSource(from);
|
|
1297
|
+
if (source) {
|
|
1298
|
+
prevSource = source;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1302
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1306
|
+
if (Array.isArray(select.columns)) {
|
|
1307
|
+
for (const col of select.columns) {
|
|
1308
|
+
if ("expr" in col) {
|
|
1309
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1314
|
+
if (Array.isArray(select.orderby)) {
|
|
1315
|
+
for (const order of select.orderby) {
|
|
1316
|
+
if (order.expr) {
|
|
1317
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (select.with) {
|
|
1325
|
+
for (const cte of select.with) {
|
|
1326
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1327
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1328
|
+
transformed = walkSelect(cteSelect) || transformed;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
if (select._next) {
|
|
1333
|
+
transformed = walkSelect(select._next) || transformed;
|
|
1334
|
+
}
|
|
1335
|
+
return transformed;
|
|
1336
|
+
}
|
|
1337
|
+
function qualifyJoinColumns(ast) {
|
|
1338
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1339
|
+
let transformed = false;
|
|
1340
|
+
for (const stmt of statements) {
|
|
1341
|
+
if (stmt.type === "select") {
|
|
1342
|
+
transformed = walkSelect(stmt) || transformed;
|
|
1343
|
+
} else if (stmt.type === "insert") {
|
|
1344
|
+
const insert = stmt;
|
|
1345
|
+
if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
|
|
1346
|
+
transformed = walkSelect(insert.values) || transformed;
|
|
1347
|
+
}
|
|
1348
|
+
} else if (stmt.type === "update") {
|
|
1349
|
+
const update = stmt;
|
|
1350
|
+
const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
|
|
1351
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1352
|
+
const fromSources = update.from ?? [];
|
|
1353
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1354
|
+
if (update.where && defaultQualifier && firstFrom) {
|
|
1355
|
+
const ambiguous = new Set;
|
|
1356
|
+
transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1357
|
+
transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
|
|
1358
|
+
}
|
|
1359
|
+
if (Array.isArray(update.returning) && defaultQualifier) {
|
|
1360
|
+
for (const ret of update.returning) {
|
|
1361
|
+
transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
} else if (stmt.type === "delete") {
|
|
1365
|
+
const del = stmt;
|
|
1366
|
+
const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
|
|
1367
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1368
|
+
const fromSources = del.from ?? [];
|
|
1369
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1370
|
+
if (del.where && defaultQualifier && firstFrom) {
|
|
1371
|
+
const ambiguous = new Set;
|
|
1372
|
+
transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1373
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
|
|
1374
|
+
} else if (del.where && defaultQualifier) {
|
|
1375
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return transformed;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/sql/visitors/generate-series-alias.ts
|
|
1383
|
+
function getColumnName2(col) {
|
|
1384
|
+
if (typeof col.column === "string") {
|
|
1385
|
+
return col.column;
|
|
1386
|
+
}
|
|
1387
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1388
|
+
return String(col.column.expr.value);
|
|
1389
|
+
}
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
function isColumnRef(expr) {
|
|
1393
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref";
|
|
1394
|
+
}
|
|
1395
|
+
function isBinaryExpr2(expr) {
|
|
1396
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1397
|
+
}
|
|
1398
|
+
function getGenerateSeriesAliases(from) {
|
|
1399
|
+
const aliases = new Set;
|
|
1400
|
+
if (!from || !Array.isArray(from))
|
|
1401
|
+
return aliases;
|
|
1402
|
+
for (const f of from) {
|
|
1403
|
+
if ("expr" in f && f.expr && typeof f.expr === "object") {
|
|
1404
|
+
const exprObj = f.expr;
|
|
1405
|
+
if (exprObj.type === "function" && "name" in exprObj) {
|
|
1406
|
+
const nameObj = exprObj.name;
|
|
1407
|
+
const nameParts = nameObj?.name;
|
|
1408
|
+
const fnName = nameParts?.[0]?.value;
|
|
1409
|
+
if (typeof fnName === "string" && fnName.toLowerCase() === "generate_series") {
|
|
1410
|
+
const alias = typeof f.as === "string" ? f.as : null;
|
|
1411
|
+
if (alias && !alias.includes("(")) {
|
|
1412
|
+
aliases.add(alias);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return aliases;
|
|
1419
|
+
}
|
|
1420
|
+
function rewriteAliasColumnRef(col, alias) {
|
|
1421
|
+
col.table = alias;
|
|
1422
|
+
col.column = { expr: { type: "default", value: "generate_series" } };
|
|
1423
|
+
}
|
|
1424
|
+
function walkExpression2(expr, aliases) {
|
|
1425
|
+
if (!expr || typeof expr !== "object")
|
|
1426
|
+
return false;
|
|
1427
|
+
let transformed = false;
|
|
1428
|
+
const exprObj = expr;
|
|
1429
|
+
if (isColumnRef(expr)) {
|
|
1430
|
+
if (!("table" in expr) || !expr.table) {
|
|
1431
|
+
const colName = getColumnName2(expr);
|
|
1432
|
+
if (colName && aliases.has(colName)) {
|
|
1433
|
+
rewriteAliasColumnRef(expr, colName);
|
|
1434
|
+
transformed = true;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return transformed;
|
|
1438
|
+
}
|
|
1439
|
+
if (isBinaryExpr2(expr)) {
|
|
1440
|
+
const binary = expr;
|
|
1441
|
+
transformed = walkExpression2(binary.left, aliases) || transformed;
|
|
1442
|
+
transformed = walkExpression2(binary.right, aliases) || transformed;
|
|
1443
|
+
return transformed;
|
|
1444
|
+
}
|
|
1445
|
+
if (exprObj.type === "unary_expr" && exprObj.expr) {
|
|
1446
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1447
|
+
}
|
|
1448
|
+
if (exprObj.type === "cast" && exprObj.expr) {
|
|
1449
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1450
|
+
}
|
|
1451
|
+
if (exprObj.type === "case") {
|
|
1452
|
+
if (exprObj.expr) {
|
|
1453
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(exprObj.args)) {
|
|
1456
|
+
for (const whenClause of exprObj.args) {
|
|
1457
|
+
if (whenClause.cond) {
|
|
1458
|
+
transformed = walkExpression2(whenClause.cond, aliases) || transformed;
|
|
1459
|
+
}
|
|
1460
|
+
if (whenClause.result) {
|
|
1461
|
+
transformed = walkExpression2(whenClause.result, aliases) || transformed;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if ("args" in exprObj && exprObj.args) {
|
|
1467
|
+
const args = exprObj.args;
|
|
1468
|
+
if (Array.isArray(args.value)) {
|
|
1469
|
+
for (const arg of args.value) {
|
|
1470
|
+
transformed = walkExpression2(arg, aliases) || transformed;
|
|
1471
|
+
}
|
|
1472
|
+
} else if (args.expr) {
|
|
1473
|
+
transformed = walkExpression2(args.expr, aliases) || transformed;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if ("over" in exprObj && exprObj.over && typeof exprObj.over === "object") {
|
|
1477
|
+
const over = exprObj.over;
|
|
1478
|
+
if (Array.isArray(over.partition)) {
|
|
1479
|
+
for (const part of over.partition) {
|
|
1480
|
+
transformed = walkExpression2(part, aliases) || transformed;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (Array.isArray(over.orderby)) {
|
|
1484
|
+
for (const order of over.orderby) {
|
|
1485
|
+
transformed = walkExpression2(order, aliases) || transformed;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if ("ast" in exprObj && exprObj.ast) {
|
|
1490
|
+
const subAst = exprObj.ast;
|
|
1491
|
+
if (subAst.type === "select") {
|
|
1492
|
+
transformed = walkSelect2(subAst) || transformed;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (exprObj.type === "expr_list" && Array.isArray(exprObj.value)) {
|
|
1496
|
+
for (const item of exprObj.value) {
|
|
1497
|
+
transformed = walkExpression2(item, aliases) || transformed;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return transformed;
|
|
1501
|
+
}
|
|
1502
|
+
function walkFrom2(from, aliases) {
|
|
1503
|
+
if (!from || !Array.isArray(from))
|
|
1504
|
+
return false;
|
|
1505
|
+
let transformed = false;
|
|
1506
|
+
for (const f of from) {
|
|
1507
|
+
if ("join" in f) {
|
|
1508
|
+
const join = f;
|
|
1509
|
+
transformed = walkExpression2(join.on, aliases) || transformed;
|
|
1510
|
+
}
|
|
1511
|
+
if ("expr" in f && f.expr && "ast" in f.expr) {
|
|
1512
|
+
transformed = walkSelect2(f.expr.ast) || transformed;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return transformed;
|
|
1516
|
+
}
|
|
1517
|
+
function walkSelect2(select) {
|
|
1518
|
+
let transformed = false;
|
|
1519
|
+
const aliases = getGenerateSeriesAliases(select.from);
|
|
1520
|
+
if (select.with) {
|
|
1521
|
+
for (const cte of select.with) {
|
|
1522
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1523
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1524
|
+
transformed = walkSelect2(cteSelect) || transformed;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
transformed = walkFrom2(select.from, aliases) || transformed;
|
|
1529
|
+
transformed = walkExpression2(select.where, aliases) || transformed;
|
|
1530
|
+
if (select.having) {
|
|
1531
|
+
if (Array.isArray(select.having)) {
|
|
1532
|
+
for (const h of select.having) {
|
|
1533
|
+
transformed = walkExpression2(h, aliases) || transformed;
|
|
1534
|
+
}
|
|
1535
|
+
} else {
|
|
1536
|
+
transformed = walkExpression2(select.having, aliases) || transformed;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (Array.isArray(select.columns)) {
|
|
1540
|
+
for (const col of select.columns) {
|
|
1541
|
+
if ("expr" in col) {
|
|
1542
|
+
transformed = walkExpression2(col.expr, aliases) || transformed;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (Array.isArray(select.groupby)) {
|
|
1547
|
+
for (const g of select.groupby) {
|
|
1548
|
+
transformed = walkExpression2(g, aliases) || transformed;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (Array.isArray(select.orderby)) {
|
|
1552
|
+
for (const order of select.orderby) {
|
|
1553
|
+
if (order.expr) {
|
|
1554
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if (select._orderby) {
|
|
1559
|
+
for (const order of select._orderby) {
|
|
1560
|
+
if (order.expr) {
|
|
1561
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (select._next) {
|
|
1566
|
+
transformed = walkSelect2(select._next) || transformed;
|
|
1567
|
+
}
|
|
1568
|
+
return transformed;
|
|
1569
|
+
}
|
|
1570
|
+
function rewriteGenerateSeriesAliases(ast) {
|
|
1571
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1572
|
+
let transformed = false;
|
|
1573
|
+
for (const stmt of statements) {
|
|
1574
|
+
if (stmt.type === "select") {
|
|
1575
|
+
transformed = walkSelect2(stmt) || transformed;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return transformed;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/sql/visitors/union-with-hoister.ts
|
|
1582
|
+
function getCteName(cte) {
|
|
1583
|
+
const nameObj = cte.name;
|
|
1584
|
+
if (!nameObj)
|
|
1585
|
+
return null;
|
|
1586
|
+
const value = nameObj.value;
|
|
1587
|
+
if (typeof value === "string")
|
|
1588
|
+
return value;
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
function hoistWithInSelect(select) {
|
|
1592
|
+
if (!select.set_op || !select._next)
|
|
1593
|
+
return false;
|
|
1594
|
+
const arms = [];
|
|
1595
|
+
let current = select;
|
|
1596
|
+
while (current && current.type === "select") {
|
|
1597
|
+
arms.push(current);
|
|
1598
|
+
current = current._next;
|
|
1599
|
+
}
|
|
1600
|
+
const mergedWith = [];
|
|
1601
|
+
const seen = new Set;
|
|
1602
|
+
let hasWithBeyondFirst = false;
|
|
1603
|
+
for (const arm of arms) {
|
|
1604
|
+
if (arm.with && arm.with.length > 0) {
|
|
1605
|
+
if (arm !== arms[0]) {
|
|
1606
|
+
hasWithBeyondFirst = true;
|
|
1607
|
+
}
|
|
1608
|
+
for (const cte of arm.with) {
|
|
1609
|
+
const cteName = getCteName(cte);
|
|
1610
|
+
if (!cteName)
|
|
1611
|
+
return false;
|
|
1612
|
+
if (seen.has(cteName)) {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
seen.add(cteName);
|
|
1616
|
+
mergedWith.push(cte);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (!hasWithBeyondFirst)
|
|
1621
|
+
return false;
|
|
1622
|
+
arms[0].with = mergedWith;
|
|
1623
|
+
if ("parentheses_symbol" in arms[0]) {
|
|
1624
|
+
arms[0].parentheses_symbol = false;
|
|
1625
|
+
}
|
|
1626
|
+
for (let i = 1;i < arms.length; i++) {
|
|
1627
|
+
arms[i].with = null;
|
|
1628
|
+
}
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
function walkSelect3(select) {
|
|
1632
|
+
let transformed = false;
|
|
1633
|
+
if (select.with) {
|
|
1634
|
+
for (const cte of select.with) {
|
|
1635
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1636
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1637
|
+
transformed = walkSelect3(cteSelect) || transformed;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (Array.isArray(select.from)) {
|
|
1642
|
+
for (const from of select.from) {
|
|
1643
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1644
|
+
transformed = walkSelect3(from.expr.ast) || transformed;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
transformed = hoistWithInSelect(select) || transformed;
|
|
1649
|
+
if (select._next) {
|
|
1650
|
+
transformed = walkSelect3(select._next) || transformed;
|
|
1651
|
+
}
|
|
1652
|
+
return transformed;
|
|
1653
|
+
}
|
|
1654
|
+
function hoistUnionWith(ast) {
|
|
1655
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1656
|
+
let transformed = false;
|
|
1657
|
+
for (const stmt of statements) {
|
|
1658
|
+
if (stmt.type === "select") {
|
|
1659
|
+
transformed = walkSelect3(stmt) || transformed;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return transformed;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/sql/ast-transformer.ts
|
|
1666
|
+
var { Parser } = nodeSqlParser;
|
|
1667
|
+
var parser = new Parser;
|
|
1668
|
+
var CACHE_SIZE = 500;
|
|
1669
|
+
var transformCache = new Map;
|
|
1670
|
+
function getCachedOrTransform(query, transform) {
|
|
1671
|
+
const cached = transformCache.get(query);
|
|
1672
|
+
if (cached) {
|
|
1673
|
+
transformCache.delete(query);
|
|
1674
|
+
transformCache.set(query, cached);
|
|
1675
|
+
return cached;
|
|
1676
|
+
}
|
|
1677
|
+
const result = transform();
|
|
1678
|
+
if (transformCache.size >= CACHE_SIZE) {
|
|
1679
|
+
const oldestKey = transformCache.keys().next().value;
|
|
1680
|
+
if (oldestKey) {
|
|
1681
|
+
transformCache.delete(oldestKey);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
transformCache.set(query, result);
|
|
1685
|
+
return result;
|
|
1686
|
+
}
|
|
1687
|
+
var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
|
|
1688
|
+
function hasJoin(query) {
|
|
1689
|
+
return /\bjoin\b/i.test(query);
|
|
1690
|
+
}
|
|
1691
|
+
function debugLog(message, payload) {
|
|
1692
|
+
if (process?.env?.[DEBUG_ENV]) {
|
|
1693
|
+
console.debug("[duckdb-ast]", message, payload ?? "");
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
function transformSQL(query) {
|
|
1697
|
+
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1698
|
+
const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
|
|
1699
|
+
const needsUnionTransform = /\bunion\b/i.test(query) || /\bintersect\b/i.test(query) || /\bexcept\b/i.test(query);
|
|
1700
|
+
const needsGenerateSeriesTransform = /\bgenerate_series\b/i.test(query);
|
|
1701
|
+
if (!needsArrayTransform && !needsJoinTransform && !needsUnionTransform && !needsGenerateSeriesTransform) {
|
|
1702
|
+
return { sql: query, transformed: false };
|
|
1703
|
+
}
|
|
1704
|
+
return getCachedOrTransform(query, () => {
|
|
1705
|
+
try {
|
|
1706
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1707
|
+
let transformed = false;
|
|
1708
|
+
if (needsArrayTransform) {
|
|
1709
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1710
|
+
}
|
|
1711
|
+
if (needsJoinTransform) {
|
|
1712
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1713
|
+
}
|
|
1714
|
+
if (needsGenerateSeriesTransform) {
|
|
1715
|
+
transformed = rewriteGenerateSeriesAliases(ast) || transformed;
|
|
1716
|
+
}
|
|
1717
|
+
if (needsUnionTransform) {
|
|
1718
|
+
transformed = hoistUnionWith(ast) || transformed;
|
|
1719
|
+
}
|
|
1720
|
+
if (!transformed) {
|
|
1721
|
+
debugLog("AST parsed but no transformation applied", {
|
|
1722
|
+
join: needsJoinTransform
|
|
1723
|
+
});
|
|
1724
|
+
return { sql: query, transformed: false };
|
|
1725
|
+
}
|
|
1726
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1727
|
+
return { sql: transformedSql, transformed: true };
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
debugLog("AST transform failed; returning original SQL", {
|
|
1730
|
+
error: err.message
|
|
1731
|
+
});
|
|
1732
|
+
return { sql: query, transformed: false };
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/dialect.ts
|
|
1738
|
+
class DuckDBDialect extends PgDialect {
|
|
1739
|
+
static [entityKind2] = "DuckDBPgDialect";
|
|
1740
|
+
hasPgJsonColumn = false;
|
|
1741
|
+
savepointsSupported = 0 /* Unknown */;
|
|
1742
|
+
resetPgJsonFlag() {
|
|
1743
|
+
this.hasPgJsonColumn = false;
|
|
1744
|
+
}
|
|
1745
|
+
markPgJsonDetected() {
|
|
1746
|
+
this.hasPgJsonColumn = true;
|
|
1747
|
+
}
|
|
1748
|
+
assertNoPgJsonColumns() {
|
|
1749
|
+
if (this.hasPgJsonColumn) {
|
|
1750
|
+
throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
areSavepointsUnsupported() {
|
|
1754
|
+
return this.savepointsSupported === 2 /* No */;
|
|
1755
|
+
}
|
|
1756
|
+
markSavepointsSupported() {
|
|
1757
|
+
this.savepointsSupported = 1 /* Yes */;
|
|
1758
|
+
}
|
|
1759
|
+
markSavepointsUnsupported() {
|
|
1760
|
+
this.savepointsSupported = 2 /* No */;
|
|
1761
|
+
}
|
|
1762
|
+
async migrate(migrations, session, config) {
|
|
1763
|
+
const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
|
|
1764
|
+
const migrationsSchema = migrationConfig.migrationsSchema ?? "drizzle";
|
|
1765
|
+
const migrationsTable = migrationConfig.migrationsTable ?? "__drizzle_migrations";
|
|
1766
|
+
const migrationsSequence = `${migrationsTable}_id_seq`;
|
|
1767
|
+
const legacySequence = "migrations_pk_seq";
|
|
1768
|
+
const escapeIdentifier = (value) => value.replace(/"/g, '""');
|
|
1769
|
+
const sequenceLiteral = `"${escapeIdentifier(migrationsSchema)}"."${escapeIdentifier(migrationsSequence)}"`;
|
|
1770
|
+
const migrationTableCreate = sql2`
|
|
1771
|
+
CREATE TABLE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} (
|
|
1772
|
+
id integer PRIMARY KEY default nextval('${sql2.raw(sequenceLiteral)}'),
|
|
1773
|
+
hash text NOT NULL,
|
|
1774
|
+
created_at bigint
|
|
1775
|
+
)
|
|
1776
|
+
`;
|
|
1777
|
+
await session.execute(sql2`CREATE SCHEMA IF NOT EXISTS ${sql2.identifier(migrationsSchema)}`);
|
|
1778
|
+
await session.execute(sql2`CREATE SEQUENCE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsSequence)}`);
|
|
1779
|
+
if (legacySequence !== migrationsSequence) {
|
|
1780
|
+
await session.execute(sql2`CREATE SEQUENCE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(legacySequence)}`);
|
|
1781
|
+
}
|
|
1782
|
+
await session.execute(migrationTableCreate);
|
|
1783
|
+
const dbMigrations = await session.all(sql2`select id, hash, created_at from ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} order by created_at desc limit 1`);
|
|
1784
|
+
const lastDbMigration = dbMigrations[0];
|
|
1785
|
+
await session.transaction(async (tx) => {
|
|
1786
|
+
for await (const migration of migrations) {
|
|
1787
|
+
if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
|
|
1788
|
+
for (const stmt of migration.sql) {
|
|
1789
|
+
await tx.execute(sql2.raw(stmt));
|
|
1790
|
+
}
|
|
1791
|
+
await tx.execute(sql2`insert into ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} ("hash", "created_at") values(${migration.hash}, ${migration.folderMillis})`);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
327
1794
|
});
|
|
328
1795
|
}
|
|
1796
|
+
prepareTyping(encoder) {
|
|
1797
|
+
if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
|
|
1798
|
+
this.markPgJsonDetected();
|
|
1799
|
+
throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
|
|
1800
|
+
} else if (is2(encoder, PgNumeric)) {
|
|
1801
|
+
return "decimal";
|
|
1802
|
+
} else if (is2(encoder, PgTime2)) {
|
|
1803
|
+
return "time";
|
|
1804
|
+
} else if (is2(encoder, PgTimestamp2) || is2(encoder, PgTimestampString2)) {
|
|
1805
|
+
return "timestamp";
|
|
1806
|
+
} else if (is2(encoder, PgDate2) || is2(encoder, PgDateString2)) {
|
|
1807
|
+
return "date";
|
|
1808
|
+
} else if (is2(encoder, PgUUID)) {
|
|
1809
|
+
return "uuid";
|
|
1810
|
+
} else {
|
|
1811
|
+
return "none";
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
sqlToQuery(sqlObj, invokeSource) {
|
|
1815
|
+
const result = super.sqlToQuery(sqlObj, invokeSource);
|
|
1816
|
+
const transformed = transformSQL(result.sql);
|
|
1817
|
+
return {
|
|
1818
|
+
...result,
|
|
1819
|
+
sql: transformed.sql
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// src/select-builder.ts
|
|
1825
|
+
import { is as is4 } from "drizzle-orm/entity";
|
|
1826
|
+
import {
|
|
1827
|
+
PgSelectBase,
|
|
1828
|
+
PgSelectBuilder
|
|
1829
|
+
} from "drizzle-orm/pg-core/query-builders";
|
|
1830
|
+
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
1831
|
+
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
1832
|
+
import { SQL as SQL5 } from "drizzle-orm/sql/sql";
|
|
1833
|
+
|
|
1834
|
+
// src/sql/selection.ts
|
|
1835
|
+
import { Column as Column2, SQL as SQL4, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
1836
|
+
function mapEntries(obj, prefix, fullJoin = false) {
|
|
1837
|
+
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
1838
|
+
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
1839
|
+
if (fullJoin && is3(value, Column2)) {
|
|
1840
|
+
return [
|
|
1841
|
+
key,
|
|
1842
|
+
sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
|
|
1843
|
+
];
|
|
1844
|
+
}
|
|
1845
|
+
if (fullJoin && is3(value, SQL4)) {
|
|
1846
|
+
const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
|
|
1847
|
+
const tableName = col?.table && getTableName2(col?.table);
|
|
1848
|
+
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
1849
|
+
}
|
|
1850
|
+
if (is3(value, SQL4) || is3(value, Column2)) {
|
|
1851
|
+
const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
|
|
1852
|
+
return [key, aliased.as(qualified)];
|
|
1853
|
+
}
|
|
1854
|
+
if (is3(value, SQL4.Aliased)) {
|
|
1855
|
+
return [key, value];
|
|
1856
|
+
}
|
|
1857
|
+
if (typeof value === "object" && value !== null) {
|
|
1858
|
+
return [
|
|
1859
|
+
key,
|
|
1860
|
+
mapEntries(value, qualified, fullJoin)
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
return [key, value];
|
|
1864
|
+
}));
|
|
1865
|
+
}
|
|
1866
|
+
function aliasFields(fields, fullJoin = false) {
|
|
1867
|
+
return mapEntries(fields, undefined, fullJoin);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// src/select-builder.ts
|
|
1871
|
+
import { getTableColumns } from "drizzle-orm/utils";
|
|
1872
|
+
|
|
1873
|
+
class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
1874
|
+
_fields;
|
|
1875
|
+
_session;
|
|
1876
|
+
_dialect;
|
|
1877
|
+
_withList = [];
|
|
1878
|
+
_distinct;
|
|
1879
|
+
constructor(config) {
|
|
1880
|
+
super(config);
|
|
1881
|
+
this._fields = config.fields;
|
|
1882
|
+
this._session = config.session;
|
|
1883
|
+
this._dialect = config.dialect;
|
|
1884
|
+
if (config.withList) {
|
|
1885
|
+
this._withList = config.withList;
|
|
1886
|
+
}
|
|
1887
|
+
this._distinct = config.distinct;
|
|
1888
|
+
}
|
|
1889
|
+
from(source) {
|
|
1890
|
+
const isPartialSelect = !!this._fields;
|
|
1891
|
+
const src = source;
|
|
1892
|
+
let fields;
|
|
1893
|
+
if (this._fields) {
|
|
1894
|
+
fields = this._fields;
|
|
1895
|
+
} else if (is4(src, Subquery)) {
|
|
1896
|
+
fields = Object.fromEntries(Object.keys(src._.selectedFields).map((key) => [
|
|
1897
|
+
key,
|
|
1898
|
+
src[key]
|
|
1899
|
+
]));
|
|
1900
|
+
} else if (is4(src, PgViewBase)) {
|
|
1901
|
+
fields = src[ViewBaseConfig]?.selectedFields;
|
|
1902
|
+
} else if (is4(src, SQL5)) {
|
|
1903
|
+
fields = {};
|
|
1904
|
+
} else {
|
|
1905
|
+
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
1906
|
+
}
|
|
1907
|
+
return new PgSelectBase({
|
|
1908
|
+
table: src,
|
|
1909
|
+
fields,
|
|
1910
|
+
isPartialSelect,
|
|
1911
|
+
session: this._session,
|
|
1912
|
+
dialect: this._dialect,
|
|
1913
|
+
withList: this._withList,
|
|
1914
|
+
distinct: this._distinct
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// src/pool.ts
|
|
1920
|
+
import { DuckDBConnection } from "@duckdb/node-api";
|
|
1921
|
+
var POOL_PRESETS = {
|
|
1922
|
+
pulse: 4,
|
|
1923
|
+
standard: 6,
|
|
1924
|
+
jumbo: 8,
|
|
1925
|
+
mega: 12,
|
|
1926
|
+
giga: 16,
|
|
1927
|
+
local: 8,
|
|
1928
|
+
memory: 4
|
|
1929
|
+
};
|
|
1930
|
+
function resolvePoolSize(pool) {
|
|
1931
|
+
if (pool === false)
|
|
1932
|
+
return false;
|
|
1933
|
+
if (pool === undefined)
|
|
1934
|
+
return 4;
|
|
1935
|
+
if (typeof pool === "string")
|
|
1936
|
+
return POOL_PRESETS[pool];
|
|
1937
|
+
return pool.size ?? 4;
|
|
1938
|
+
}
|
|
1939
|
+
function createDuckDBConnectionPool(instance, options = {}) {
|
|
1940
|
+
const size = options.size && options.size > 0 ? options.size : 4;
|
|
1941
|
+
const acquireTimeout = options.acquireTimeout ?? 30000;
|
|
1942
|
+
const maxWaitingRequests = options.maxWaitingRequests ?? 100;
|
|
1943
|
+
const maxLifetimeMs = options.maxLifetimeMs;
|
|
1944
|
+
const idleTimeoutMs = options.idleTimeoutMs;
|
|
1945
|
+
const metadata = new WeakMap;
|
|
1946
|
+
const idle = [];
|
|
1947
|
+
const waiting = [];
|
|
1948
|
+
let total = 0;
|
|
1949
|
+
let closed = false;
|
|
1950
|
+
let pendingAcquires = 0;
|
|
1951
|
+
const shouldRecycle = (conn, now) => {
|
|
1952
|
+
if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
|
|
1953
|
+
return true;
|
|
1954
|
+
}
|
|
1955
|
+
if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
|
|
1956
|
+
return true;
|
|
1957
|
+
}
|
|
1958
|
+
return false;
|
|
1959
|
+
};
|
|
1960
|
+
const acquire = async () => {
|
|
1961
|
+
if (closed) {
|
|
1962
|
+
throw new Error("DuckDB connection pool is closed");
|
|
1963
|
+
}
|
|
1964
|
+
while (idle.length > 0) {
|
|
1965
|
+
const pooled = idle.pop();
|
|
1966
|
+
const now = Date.now();
|
|
1967
|
+
if (shouldRecycle(pooled, now)) {
|
|
1968
|
+
await closeClientConnection(pooled.connection);
|
|
1969
|
+
total = Math.max(0, total - 1);
|
|
1970
|
+
metadata.delete(pooled.connection);
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
pooled.lastUsedAt = now;
|
|
1974
|
+
metadata.set(pooled.connection, {
|
|
1975
|
+
createdAt: pooled.createdAt,
|
|
1976
|
+
lastUsedAt: pooled.lastUsedAt
|
|
1977
|
+
});
|
|
1978
|
+
return pooled.connection;
|
|
1979
|
+
}
|
|
1980
|
+
if (total < size) {
|
|
1981
|
+
pendingAcquires += 1;
|
|
1982
|
+
total += 1;
|
|
1983
|
+
try {
|
|
1984
|
+
const connection = await DuckDBConnection.create(instance);
|
|
1985
|
+
if (closed) {
|
|
1986
|
+
await closeClientConnection(connection);
|
|
1987
|
+
total -= 1;
|
|
1988
|
+
throw new Error("DuckDB connection pool is closed");
|
|
1989
|
+
}
|
|
1990
|
+
const now = Date.now();
|
|
1991
|
+
metadata.set(connection, { createdAt: now, lastUsedAt: now });
|
|
1992
|
+
return connection;
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
total -= 1;
|
|
1995
|
+
throw error;
|
|
1996
|
+
} finally {
|
|
1997
|
+
pendingAcquires -= 1;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (waiting.length >= maxWaitingRequests) {
|
|
2001
|
+
throw new Error(`DuckDB connection pool queue is full (max ${maxWaitingRequests} waiting requests)`);
|
|
2002
|
+
}
|
|
2003
|
+
return await new Promise((resolve, reject) => {
|
|
2004
|
+
const timeoutId = setTimeout(() => {
|
|
2005
|
+
const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
|
|
2006
|
+
if (idx !== -1) {
|
|
2007
|
+
waiting.splice(idx, 1);
|
|
2008
|
+
}
|
|
2009
|
+
reject(new Error(`DuckDB connection pool acquire timeout after ${acquireTimeout}ms`));
|
|
2010
|
+
}, acquireTimeout);
|
|
2011
|
+
waiting.push({ resolve, reject, timeoutId });
|
|
2012
|
+
});
|
|
2013
|
+
};
|
|
2014
|
+
const release = async (connection) => {
|
|
2015
|
+
const waiter = waiting.shift();
|
|
2016
|
+
if (waiter) {
|
|
2017
|
+
clearTimeout(waiter.timeoutId);
|
|
2018
|
+
const now2 = Date.now();
|
|
2019
|
+
const meta = metadata.get(connection) ?? { createdAt: now2, lastUsedAt: now2 };
|
|
2020
|
+
const expired = maxLifetimeMs !== undefined && now2 - meta.createdAt >= maxLifetimeMs;
|
|
2021
|
+
if (closed) {
|
|
2022
|
+
await closeClientConnection(connection);
|
|
2023
|
+
total = Math.max(0, total - 1);
|
|
2024
|
+
metadata.delete(connection);
|
|
2025
|
+
waiter.reject(new Error("DuckDB connection pool is closed"));
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (expired) {
|
|
2029
|
+
await closeClientConnection(connection);
|
|
2030
|
+
total = Math.max(0, total - 1);
|
|
2031
|
+
metadata.delete(connection);
|
|
2032
|
+
try {
|
|
2033
|
+
const replacement = await acquire();
|
|
2034
|
+
waiter.resolve(replacement);
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
waiter.reject(error);
|
|
2037
|
+
}
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
meta.lastUsedAt = now2;
|
|
2041
|
+
metadata.set(connection, meta);
|
|
2042
|
+
waiter.resolve(connection);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
if (closed) {
|
|
2046
|
+
await closeClientConnection(connection);
|
|
2047
|
+
metadata.delete(connection);
|
|
2048
|
+
total = Math.max(0, total - 1);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
const now = Date.now();
|
|
2052
|
+
const existingMeta = metadata.get(connection) ?? { createdAt: now, lastUsedAt: now };
|
|
2053
|
+
existingMeta.lastUsedAt = now;
|
|
2054
|
+
metadata.set(connection, existingMeta);
|
|
2055
|
+
if (maxLifetimeMs !== undefined && now - existingMeta.createdAt >= maxLifetimeMs) {
|
|
2056
|
+
await closeClientConnection(connection);
|
|
2057
|
+
total -= 1;
|
|
2058
|
+
metadata.delete(connection);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
idle.push({
|
|
2062
|
+
connection,
|
|
2063
|
+
createdAt: existingMeta.createdAt,
|
|
2064
|
+
lastUsedAt: existingMeta.lastUsedAt
|
|
2065
|
+
});
|
|
2066
|
+
};
|
|
2067
|
+
const close = async () => {
|
|
2068
|
+
closed = true;
|
|
2069
|
+
const waiters = waiting.splice(0, waiting.length);
|
|
2070
|
+
for (const waiter of waiters) {
|
|
2071
|
+
clearTimeout(waiter.timeoutId);
|
|
2072
|
+
waiter.reject(new Error("DuckDB connection pool is closed"));
|
|
2073
|
+
}
|
|
2074
|
+
const toClose = idle.splice(0, idle.length);
|
|
2075
|
+
await Promise.allSettled(toClose.map((item) => closeClientConnection(item.connection)));
|
|
2076
|
+
total = Math.max(0, total - toClose.length);
|
|
2077
|
+
toClose.forEach((item) => metadata.delete(item.connection));
|
|
2078
|
+
const maxWait = 5000;
|
|
2079
|
+
const start = Date.now();
|
|
2080
|
+
while (pendingAcquires > 0 && Date.now() - start < maxWait) {
|
|
2081
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
return {
|
|
2085
|
+
acquire,
|
|
2086
|
+
release,
|
|
2087
|
+
close,
|
|
2088
|
+
size
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/options.ts
|
|
2093
|
+
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
2094
|
+
function resolvePrepareCacheOption(option) {
|
|
2095
|
+
if (!option)
|
|
2096
|
+
return;
|
|
2097
|
+
if (option === true) {
|
|
2098
|
+
return { size: DEFAULT_PREPARED_CACHE_SIZE };
|
|
2099
|
+
}
|
|
2100
|
+
if (typeof option === "number") {
|
|
2101
|
+
const size2 = Math.max(1, Math.floor(option));
|
|
2102
|
+
return { size: size2 };
|
|
2103
|
+
}
|
|
2104
|
+
const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
|
|
2105
|
+
return { size: Math.max(1, Math.floor(size)) };
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// src/driver.ts
|
|
2109
|
+
class DuckDBDriver {
|
|
2110
|
+
client;
|
|
2111
|
+
dialect;
|
|
2112
|
+
options;
|
|
2113
|
+
static [entityKind3] = "DuckDBDriver";
|
|
2114
|
+
constructor(client, dialect, options = {}) {
|
|
2115
|
+
this.client = client;
|
|
2116
|
+
this.dialect = dialect;
|
|
2117
|
+
this.options = options;
|
|
2118
|
+
}
|
|
2119
|
+
createSession(schema) {
|
|
2120
|
+
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
2121
|
+
logger: this.options.logger,
|
|
2122
|
+
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
2123
|
+
prepareCache: this.options.prepareCache
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function isConfigObject(data) {
|
|
2128
|
+
if (typeof data !== "object" || data === null)
|
|
2129
|
+
return false;
|
|
2130
|
+
if (data.constructor?.name !== "Object")
|
|
2131
|
+
return false;
|
|
2132
|
+
return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
|
|
329
2133
|
}
|
|
330
|
-
function
|
|
2134
|
+
function createFromClient(client, config = {}, instance) {
|
|
331
2135
|
const dialect = new DuckDBDialect;
|
|
2136
|
+
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
332
2137
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
333
2138
|
let schema;
|
|
334
2139
|
if (config.schema) {
|
|
@@ -339,98 +2144,1155 @@ function drizzle(client, config = {}) {
|
|
|
339
2144
|
tableNamesMap: tablesConfig.tableNamesMap
|
|
340
2145
|
};
|
|
341
2146
|
}
|
|
342
|
-
const driver = new DuckDBDriver(client, dialect, {
|
|
2147
|
+
const driver = new DuckDBDriver(client, dialect, {
|
|
2148
|
+
logger,
|
|
2149
|
+
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
2150
|
+
prepareCache
|
|
2151
|
+
});
|
|
343
2152
|
const session = driver.createSession(schema);
|
|
344
|
-
|
|
2153
|
+
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
2154
|
+
return db;
|
|
2155
|
+
}
|
|
2156
|
+
async function createFromConnectionString(path, instanceOptions, config = {}) {
|
|
2157
|
+
const instance = await DuckDBInstance2.create(path, instanceOptions);
|
|
2158
|
+
const poolSize = resolvePoolSize(config.pool);
|
|
2159
|
+
if (poolSize === false) {
|
|
2160
|
+
const connection = await instance.connect();
|
|
2161
|
+
return createFromClient(connection, config, instance);
|
|
2162
|
+
}
|
|
2163
|
+
const pool = createDuckDBConnectionPool(instance, { size: poolSize });
|
|
2164
|
+
return createFromClient(pool, config, instance);
|
|
2165
|
+
}
|
|
2166
|
+
function drizzle(clientOrConfigOrPath, config) {
|
|
2167
|
+
if (typeof clientOrConfigOrPath === "string") {
|
|
2168
|
+
return createFromConnectionString(clientOrConfigOrPath, undefined, config);
|
|
2169
|
+
}
|
|
2170
|
+
if (isConfigObject(clientOrConfigOrPath)) {
|
|
2171
|
+
const configObj = clientOrConfigOrPath;
|
|
2172
|
+
if ("connection" in configObj) {
|
|
2173
|
+
const connConfig = configObj;
|
|
2174
|
+
const { connection, ...restConfig } = connConfig;
|
|
2175
|
+
if (typeof connection === "string") {
|
|
2176
|
+
return createFromConnectionString(connection, undefined, restConfig);
|
|
2177
|
+
}
|
|
2178
|
+
return createFromConnectionString(connection.path, connection.options, restConfig);
|
|
2179
|
+
}
|
|
2180
|
+
if ("client" in configObj) {
|
|
2181
|
+
const clientConfig = configObj;
|
|
2182
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
2183
|
+
return createFromClient(clientValue, restConfig);
|
|
2184
|
+
}
|
|
2185
|
+
throw new Error("Invalid drizzle config: either connection or client must be provided");
|
|
2186
|
+
}
|
|
2187
|
+
return createFromClient(clientOrConfigOrPath, config);
|
|
345
2188
|
}
|
|
346
2189
|
|
|
347
2190
|
class DuckDBDatabase extends PgDatabase {
|
|
348
2191
|
dialect;
|
|
349
2192
|
session;
|
|
350
2193
|
static [entityKind3] = "DuckDBDatabase";
|
|
351
|
-
|
|
2194
|
+
$client;
|
|
2195
|
+
$instance;
|
|
2196
|
+
constructor(dialect, session, schema, client, instance) {
|
|
352
2197
|
super(dialect, session, schema);
|
|
353
2198
|
this.dialect = dialect;
|
|
354
2199
|
this.session = session;
|
|
2200
|
+
this.$client = client;
|
|
2201
|
+
this.$instance = instance;
|
|
355
2202
|
}
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
fields: fields ?? undefined,
|
|
360
|
-
session: this.session,
|
|
361
|
-
dialect: this.dialect
|
|
362
|
-
});
|
|
2203
|
+
async close() {
|
|
2204
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
2205
|
+
await this.$client.close();
|
|
363
2206
|
}
|
|
364
|
-
|
|
2207
|
+
if (!isPool(this.$client)) {
|
|
2208
|
+
await closeClientConnection(this.$client);
|
|
2209
|
+
}
|
|
2210
|
+
if (this.$instance) {
|
|
2211
|
+
const maybeClosable = this.$instance;
|
|
2212
|
+
if (typeof maybeClosable.close === "function") {
|
|
2213
|
+
await maybeClosable.close();
|
|
2214
|
+
} else if (typeof maybeClosable.closeSync === "function") {
|
|
2215
|
+
maybeClosable.closeSync();
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
select(fields) {
|
|
2220
|
+
const selectedFields = fields ? aliasFields(fields) : undefined;
|
|
365
2221
|
return new DuckDBSelectBuilder({
|
|
366
|
-
fields:
|
|
2222
|
+
fields: selectedFields ?? undefined,
|
|
367
2223
|
session: this.session,
|
|
368
2224
|
dialect: this.dialect
|
|
369
2225
|
});
|
|
370
2226
|
}
|
|
2227
|
+
executeBatches(query, options = {}) {
|
|
2228
|
+
return this.session.executeBatches(query, options);
|
|
2229
|
+
}
|
|
2230
|
+
executeBatchesRaw(query, options = {}) {
|
|
2231
|
+
return this.session.executeBatchesRaw(query, options);
|
|
2232
|
+
}
|
|
2233
|
+
executeArrow(query) {
|
|
2234
|
+
return this.session.executeArrow(query);
|
|
2235
|
+
}
|
|
371
2236
|
async transaction(transaction) {
|
|
372
2237
|
return await this.session.transaction(transaction);
|
|
373
2238
|
}
|
|
374
2239
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this._withList = config.withList;
|
|
2240
|
+
// src/columns.ts
|
|
2241
|
+
import { sql as sql4 } from "drizzle-orm";
|
|
2242
|
+
import { customType } from "drizzle-orm/pg-core";
|
|
2243
|
+
function coerceArrayString(value) {
|
|
2244
|
+
const trimmed = value.trim();
|
|
2245
|
+
if (!trimmed) {
|
|
2246
|
+
return [];
|
|
2247
|
+
}
|
|
2248
|
+
if (trimmed.startsWith("[")) {
|
|
2249
|
+
try {
|
|
2250
|
+
return JSON.parse(trimmed);
|
|
2251
|
+
} catch {
|
|
2252
|
+
return;
|
|
389
2253
|
}
|
|
390
|
-
this._distinct = config.distinct;
|
|
391
2254
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
} else if (is3(src, Subquery)) {
|
|
399
|
-
fields = Object.fromEntries(Object.keys(src._.selectedFields).map((key) => [
|
|
400
|
-
key,
|
|
401
|
-
src[key]
|
|
402
|
-
]));
|
|
403
|
-
} else if (is3(src, PgViewBase)) {
|
|
404
|
-
fields = src[ViewBaseConfig]?.selectedFields;
|
|
405
|
-
} else if (is3(src, SQL3)) {
|
|
406
|
-
fields = {};
|
|
407
|
-
} else {
|
|
408
|
-
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
2255
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
2256
|
+
try {
|
|
2257
|
+
const json = trimmed.replace(/{/g, "[").replace(/}/g, "]");
|
|
2258
|
+
return JSON.parse(json);
|
|
2259
|
+
} catch {
|
|
2260
|
+
return;
|
|
409
2261
|
}
|
|
410
|
-
return new PgSelectBase({
|
|
411
|
-
table: src,
|
|
412
|
-
fields,
|
|
413
|
-
isPartialSelect,
|
|
414
|
-
session: this._session,
|
|
415
|
-
dialect: this._dialect,
|
|
416
|
-
withList: this._withList,
|
|
417
|
-
distinct: this._distinct
|
|
418
|
-
});
|
|
419
2262
|
}
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
function formatLiteral(value, typeHint) {
|
|
2266
|
+
if (value === null || value === undefined) {
|
|
2267
|
+
return "NULL";
|
|
2268
|
+
}
|
|
2269
|
+
const upperType = typeHint?.toUpperCase() ?? "";
|
|
2270
|
+
if (value instanceof Date) {
|
|
2271
|
+
return `'${value.toISOString()}'`;
|
|
2272
|
+
}
|
|
2273
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
2274
|
+
return value.toString();
|
|
2275
|
+
}
|
|
2276
|
+
if (typeof value === "boolean") {
|
|
2277
|
+
return value ? "TRUE" : "FALSE";
|
|
2278
|
+
}
|
|
2279
|
+
const str = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
|
|
2280
|
+
const escaped = str.replace(/'/g, "''");
|
|
2281
|
+
if (upperType.includes("CHAR") || upperType.includes("TEXT") || upperType.includes("STRING") || upperType.includes("VARCHAR")) {
|
|
2282
|
+
return `'${escaped}'`;
|
|
2283
|
+
}
|
|
2284
|
+
return `'${escaped}'`;
|
|
2285
|
+
}
|
|
2286
|
+
function buildListLiteral(values, elementType) {
|
|
2287
|
+
if (values.length === 0) {
|
|
2288
|
+
return sql4`[]`;
|
|
2289
|
+
}
|
|
2290
|
+
const chunks = values.map((v) => typeof v === "object" && !Array.isArray(v) ? sql4`${v}` : sql4.raw(formatLiteral(v, elementType)));
|
|
2291
|
+
return sql4`list_value(${sql4.join(chunks, sql4.raw(", "))})`;
|
|
2292
|
+
}
|
|
2293
|
+
function buildStructLiteral(value, schema) {
|
|
2294
|
+
const parts = Object.entries(value).map(([key, val]) => {
|
|
2295
|
+
const typeHint = schema?.[key];
|
|
2296
|
+
if (Array.isArray(val)) {
|
|
2297
|
+
const inner = typeof typeHint === "string" && typeHint.endsWith("[]") ? typeHint.slice(0, -2) : undefined;
|
|
2298
|
+
return sql4`${sql4.identifier(key)} := ${buildListLiteral(val, inner)}`;
|
|
2299
|
+
}
|
|
2300
|
+
return sql4`${sql4.identifier(key)} := ${val}`;
|
|
2301
|
+
});
|
|
2302
|
+
return sql4`struct_pack(${sql4.join(parts, sql4.raw(", "))})`;
|
|
2303
|
+
}
|
|
2304
|
+
function buildMapLiteral(value, valueType) {
|
|
2305
|
+
const keys = Object.keys(value);
|
|
2306
|
+
const vals = Object.values(value);
|
|
2307
|
+
const keyList = buildListLiteral(keys, "TEXT");
|
|
2308
|
+
const valList = buildListLiteral(vals, valueType?.endsWith("[]") ? valueType.slice(0, -2) : valueType);
|
|
2309
|
+
return sql4`map(${keyList}, ${valList})`;
|
|
2310
|
+
}
|
|
2311
|
+
var duckDbList = (name, elementType) => customType({
|
|
2312
|
+
dataType() {
|
|
2313
|
+
return `${elementType}[]`;
|
|
2314
|
+
},
|
|
2315
|
+
toDriver(value) {
|
|
2316
|
+
return wrapList(value, elementType);
|
|
2317
|
+
},
|
|
2318
|
+
fromDriver(value) {
|
|
2319
|
+
if (Array.isArray(value)) {
|
|
2320
|
+
return value;
|
|
2321
|
+
}
|
|
2322
|
+
if (typeof value === "string") {
|
|
2323
|
+
const parsed = coerceArrayString(value);
|
|
2324
|
+
if (parsed !== undefined) {
|
|
2325
|
+
return parsed;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return value;
|
|
2329
|
+
}
|
|
2330
|
+
})(name);
|
|
2331
|
+
var duckDbArray = (name, elementType, fixedLength) => customType({
|
|
2332
|
+
dataType() {
|
|
2333
|
+
return fixedLength ? `${elementType}[${fixedLength}]` : `${elementType}[]`;
|
|
2334
|
+
},
|
|
2335
|
+
toDriver(value) {
|
|
2336
|
+
return wrapArray(value, elementType, fixedLength);
|
|
2337
|
+
},
|
|
2338
|
+
fromDriver(value) {
|
|
2339
|
+
if (Array.isArray(value)) {
|
|
2340
|
+
return value;
|
|
2341
|
+
}
|
|
2342
|
+
if (typeof value === "string") {
|
|
2343
|
+
const parsed = coerceArrayString(value);
|
|
2344
|
+
if (parsed !== undefined) {
|
|
2345
|
+
return parsed;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return value;
|
|
2349
|
+
}
|
|
2350
|
+
})(name);
|
|
2351
|
+
var duckDbMap = (name, valueType) => customType({
|
|
2352
|
+
dataType() {
|
|
2353
|
+
return `MAP (STRING, ${valueType})`;
|
|
2354
|
+
},
|
|
2355
|
+
toDriver(value) {
|
|
2356
|
+
if (Object.keys(value).length === 0) {
|
|
2357
|
+
return buildMapLiteral(value, valueType);
|
|
2358
|
+
}
|
|
2359
|
+
return wrapMap(value, valueType);
|
|
2360
|
+
},
|
|
2361
|
+
fromDriver(value) {
|
|
2362
|
+
return value;
|
|
2363
|
+
}
|
|
2364
|
+
})(name);
|
|
2365
|
+
var duckDbStruct = (name, schema) => customType({
|
|
2366
|
+
dataType() {
|
|
2367
|
+
const fields = Object.entries(schema).map(([key, type]) => `${key} ${type}`);
|
|
2368
|
+
return `STRUCT (${fields.join(", ")})`;
|
|
2369
|
+
},
|
|
2370
|
+
toDriver(value) {
|
|
2371
|
+
return buildStructLiteral(value, schema);
|
|
2372
|
+
},
|
|
2373
|
+
fromDriver(value) {
|
|
2374
|
+
if (typeof value === "string") {
|
|
2375
|
+
try {
|
|
2376
|
+
return JSON.parse(value);
|
|
2377
|
+
} catch {
|
|
2378
|
+
return value;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
return value;
|
|
2382
|
+
}
|
|
2383
|
+
})(name);
|
|
2384
|
+
var duckDbJson = (name) => customType({
|
|
2385
|
+
dataType() {
|
|
2386
|
+
return "JSON";
|
|
2387
|
+
},
|
|
2388
|
+
toDriver(value) {
|
|
2389
|
+
if (typeof value === "string") {
|
|
2390
|
+
return value;
|
|
2391
|
+
}
|
|
2392
|
+
if (value !== null && typeof value === "object" && "queryChunks" in value) {
|
|
2393
|
+
return value;
|
|
2394
|
+
}
|
|
2395
|
+
return wrapJson(value);
|
|
2396
|
+
},
|
|
2397
|
+
fromDriver(value) {
|
|
2398
|
+
if (typeof value !== "string") {
|
|
2399
|
+
return value;
|
|
2400
|
+
}
|
|
2401
|
+
const trimmed = value.trim();
|
|
2402
|
+
if (!trimmed) {
|
|
2403
|
+
return value;
|
|
2404
|
+
}
|
|
2405
|
+
try {
|
|
2406
|
+
return JSON.parse(trimmed);
|
|
2407
|
+
} catch {
|
|
2408
|
+
return value;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
})(name);
|
|
2412
|
+
var duckDbBlob = customType({
|
|
2413
|
+
dataType() {
|
|
2414
|
+
return "BLOB";
|
|
2415
|
+
},
|
|
2416
|
+
toDriver(value) {
|
|
2417
|
+
return wrapBlob(value);
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
var duckDbInet = (name) => customType({
|
|
2421
|
+
dataType() {
|
|
2422
|
+
return "INET";
|
|
2423
|
+
},
|
|
2424
|
+
toDriver(value) {
|
|
2425
|
+
return value;
|
|
2426
|
+
}
|
|
2427
|
+
})(name);
|
|
2428
|
+
var duckDbInterval = (name) => customType({
|
|
2429
|
+
dataType() {
|
|
2430
|
+
return "INTERVAL";
|
|
2431
|
+
},
|
|
2432
|
+
toDriver(value) {
|
|
2433
|
+
return value;
|
|
2434
|
+
}
|
|
2435
|
+
})(name);
|
|
2436
|
+
function shouldBindTimestamp(options) {
|
|
2437
|
+
const bindMode = options.bindMode ?? "auto";
|
|
2438
|
+
if (bindMode === "bind")
|
|
2439
|
+
return true;
|
|
2440
|
+
if (bindMode === "literal")
|
|
2441
|
+
return false;
|
|
2442
|
+
const isBun = typeof process !== "undefined" && typeof process.versions?.bun !== "undefined";
|
|
2443
|
+
if (isBun)
|
|
2444
|
+
return false;
|
|
2445
|
+
const forceLiteral = typeof process !== "undefined" ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS : undefined;
|
|
2446
|
+
if (forceLiteral && forceLiteral !== "0") {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
return true;
|
|
2450
|
+
}
|
|
2451
|
+
var duckDbTimestamp = (name, options = {}) => customType({
|
|
2452
|
+
dataType() {
|
|
2453
|
+
if (options.withTimezone) {
|
|
2454
|
+
return "TIMESTAMPTZ";
|
|
2455
|
+
}
|
|
2456
|
+
const precision = options.precision ? `(${options.precision})` : "";
|
|
2457
|
+
return `TIMESTAMP${precision}`;
|
|
2458
|
+
},
|
|
2459
|
+
toDriver(value) {
|
|
2460
|
+
if (shouldBindTimestamp(options)) {
|
|
2461
|
+
return wrapTimestamp(value, options.withTimezone ?? false, options.precision);
|
|
2462
|
+
}
|
|
2463
|
+
const iso = value instanceof Date ? value.toISOString() : value;
|
|
2464
|
+
const normalized = iso.replace("T", " ").replace("Z", "+00");
|
|
2465
|
+
const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
|
|
2466
|
+
return sql4.raw(`${typeKeyword} '${normalized}'`);
|
|
2467
|
+
},
|
|
2468
|
+
fromDriver(value) {
|
|
2469
|
+
if (value && typeof value === "object" && "kind" in value && value.kind === "timestamp") {
|
|
2470
|
+
const wrapped = value;
|
|
2471
|
+
return wrapped.data instanceof Date ? wrapped.data : typeof wrapped.data === "number" || typeof wrapped.data === "bigint" ? new Date(Number(wrapped.data) / 1000) : wrapped.data;
|
|
2472
|
+
}
|
|
2473
|
+
if (options.mode === "string") {
|
|
2474
|
+
if (value instanceof Date) {
|
|
2475
|
+
return value.toISOString().replace("T", " ").replace("Z", "+00");
|
|
2476
|
+
}
|
|
2477
|
+
return typeof value === "string" ? value : value.toString();
|
|
2478
|
+
}
|
|
2479
|
+
if (value instanceof Date) {
|
|
2480
|
+
return value;
|
|
2481
|
+
}
|
|
2482
|
+
const stringValue = typeof value === "string" ? value : value.toString();
|
|
2483
|
+
const hasOffset = stringValue.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(stringValue);
|
|
2484
|
+
const normalized = hasOffset ? stringValue.replace(" ", "T") : `${stringValue.replace(" ", "T")}Z`;
|
|
2485
|
+
return new Date(normalized);
|
|
2486
|
+
}
|
|
2487
|
+
})(name);
|
|
2488
|
+
var duckDbDate = (name) => customType({
|
|
2489
|
+
dataType() {
|
|
2490
|
+
return "DATE";
|
|
2491
|
+
},
|
|
2492
|
+
toDriver(value) {
|
|
2493
|
+
return value;
|
|
2494
|
+
},
|
|
2495
|
+
fromDriver(value) {
|
|
2496
|
+
const str = value instanceof Date ? value.toISOString().slice(0, 10) : value;
|
|
2497
|
+
return str;
|
|
2498
|
+
}
|
|
2499
|
+
})(name);
|
|
2500
|
+
var duckDbTime = (name) => customType({
|
|
2501
|
+
dataType() {
|
|
2502
|
+
return "TIME";
|
|
2503
|
+
},
|
|
2504
|
+
toDriver(value) {
|
|
2505
|
+
return value;
|
|
2506
|
+
},
|
|
2507
|
+
fromDriver(value) {
|
|
2508
|
+
if (typeof value === "bigint") {
|
|
2509
|
+
const totalMillis = Number(value) / 1000;
|
|
2510
|
+
const date = new Date(totalMillis);
|
|
2511
|
+
return date.toISOString().split("T")[1].replace("Z", "");
|
|
2512
|
+
}
|
|
2513
|
+
return value;
|
|
2514
|
+
}
|
|
2515
|
+
})(name);
|
|
2516
|
+
function toListValue(values) {
|
|
2517
|
+
return buildListLiteral(values);
|
|
2518
|
+
}
|
|
2519
|
+
function duckDbArrayContains(column, values) {
|
|
2520
|
+
const rhs = Array.isArray(values) ? toListValue(values) : values;
|
|
2521
|
+
return sql4`array_has_all(${column}, ${rhs})`;
|
|
2522
|
+
}
|
|
2523
|
+
function duckDbArrayContained(column, values) {
|
|
2524
|
+
const rhs = Array.isArray(values) ? toListValue(values) : values;
|
|
2525
|
+
return sql4`array_has_all(${rhs}, ${column})`;
|
|
2526
|
+
}
|
|
2527
|
+
function duckDbArrayOverlaps(column, values) {
|
|
2528
|
+
const rhs = Array.isArray(values) ? toListValue(values) : values;
|
|
2529
|
+
return sql4`array_has_any(${column}, ${rhs})`;
|
|
420
2530
|
}
|
|
421
2531
|
// src/migrator.ts
|
|
422
2532
|
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
423
2533
|
async function migrate(db, config) {
|
|
424
|
-
const
|
|
425
|
-
|
|
2534
|
+
const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
|
|
2535
|
+
const migrations = readMigrationFiles(migrationConfig);
|
|
2536
|
+
await db.dialect.migrate(migrations, db.session, migrationConfig);
|
|
2537
|
+
}
|
|
2538
|
+
// src/introspect.ts
|
|
2539
|
+
import { sql as sql5 } from "drizzle-orm";
|
|
2540
|
+
var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
|
|
2541
|
+
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
|
|
2542
|
+
async function introspect(db, opts = {}) {
|
|
2543
|
+
const database = await resolveDatabase(db, opts.database, opts.allDatabases);
|
|
2544
|
+
const schemas = await resolveSchemas(db, database, opts.schemas);
|
|
2545
|
+
const includeViews = opts.includeViews ?? false;
|
|
2546
|
+
const tables = await loadTables(db, database, schemas, includeViews);
|
|
2547
|
+
const columns = await loadColumns(db, database, schemas);
|
|
2548
|
+
const constraints = await loadConstraints(db, database, schemas);
|
|
2549
|
+
const indexes = await loadIndexes(db, database, schemas);
|
|
2550
|
+
const grouped = buildTables(tables, columns, constraints, indexes);
|
|
2551
|
+
const schemaTs = emitSchema(grouped, {
|
|
2552
|
+
useCustomTimeTypes: opts.useCustomTimeTypes ?? true,
|
|
2553
|
+
mapJsonAsDuckDbJson: opts.mapJsonAsDuckDbJson ?? true,
|
|
2554
|
+
importBasePath: opts.importBasePath ?? DEFAULT_IMPORT_BASE
|
|
2555
|
+
});
|
|
2556
|
+
return {
|
|
2557
|
+
files: {
|
|
2558
|
+
schemaTs,
|
|
2559
|
+
metaJson: grouped
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
async function resolveDatabase(db, targetDatabase, allDatabases) {
|
|
2564
|
+
if (allDatabases) {
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
if (targetDatabase) {
|
|
2568
|
+
return targetDatabase;
|
|
2569
|
+
}
|
|
2570
|
+
const rows = await db.execute(sql5`SELECT current_database() as current_database`);
|
|
2571
|
+
return rows[0]?.current_database ?? null;
|
|
2572
|
+
}
|
|
2573
|
+
async function resolveSchemas(db, database, targetSchemas) {
|
|
2574
|
+
if (targetSchemas?.length) {
|
|
2575
|
+
return targetSchemas;
|
|
2576
|
+
}
|
|
2577
|
+
const databaseFilter = database ? sql5`catalog_name = ${database}` : sql5`1 = 1`;
|
|
2578
|
+
const rows = await db.execute(sql5`SELECT schema_name FROM information_schema.schemata WHERE ${databaseFilter}`);
|
|
2579
|
+
return rows.map((row) => row.schema_name).filter((name) => !SYSTEM_SCHEMAS.has(name));
|
|
2580
|
+
}
|
|
2581
|
+
async function loadTables(db, database, schemas, includeViews) {
|
|
2582
|
+
const schemaFragments = schemas.map((schema) => sql5`${schema}`);
|
|
2583
|
+
const databaseFilter = database ? sql5`table_catalog = ${database}` : sql5`1 = 1`;
|
|
2584
|
+
return await db.execute(sql5`
|
|
2585
|
+
SELECT table_schema as schema_name, table_name, table_type
|
|
2586
|
+
FROM information_schema.tables
|
|
2587
|
+
WHERE ${databaseFilter}
|
|
2588
|
+
AND table_schema IN (${sql5.join(schemaFragments, sql5.raw(", "))})
|
|
2589
|
+
AND ${includeViews ? sql5`1 = 1` : sql5`table_type = 'BASE TABLE'`}
|
|
2590
|
+
ORDER BY table_schema, table_name
|
|
2591
|
+
`);
|
|
2592
|
+
}
|
|
2593
|
+
async function loadColumns(db, database, schemas) {
|
|
2594
|
+
const schemaFragments = schemas.map((schema) => sql5`${schema}`);
|
|
2595
|
+
const databaseFilter = database ? sql5`database_name = ${database}` : sql5`1 = 1`;
|
|
2596
|
+
return await db.execute(sql5`
|
|
2597
|
+
SELECT
|
|
2598
|
+
schema_name,
|
|
2599
|
+
table_name,
|
|
2600
|
+
column_name,
|
|
2601
|
+
column_index,
|
|
2602
|
+
column_default,
|
|
2603
|
+
is_nullable,
|
|
2604
|
+
data_type,
|
|
2605
|
+
character_maximum_length,
|
|
2606
|
+
numeric_precision,
|
|
2607
|
+
numeric_scale,
|
|
2608
|
+
internal
|
|
2609
|
+
FROM duckdb_columns()
|
|
2610
|
+
WHERE ${databaseFilter}
|
|
2611
|
+
AND schema_name IN (${sql5.join(schemaFragments, sql5.raw(", "))})
|
|
2612
|
+
ORDER BY schema_name, table_name, column_index
|
|
2613
|
+
`);
|
|
2614
|
+
}
|
|
2615
|
+
async function loadConstraints(db, database, schemas) {
|
|
2616
|
+
const schemaFragments = schemas.map((schema) => sql5`${schema}`);
|
|
2617
|
+
const databaseFilter = database ? sql5`database_name = ${database}` : sql5`1 = 1`;
|
|
2618
|
+
return await db.execute(sql5`
|
|
2619
|
+
SELECT
|
|
2620
|
+
schema_name,
|
|
2621
|
+
table_name,
|
|
2622
|
+
constraint_name,
|
|
2623
|
+
constraint_type,
|
|
2624
|
+
constraint_text,
|
|
2625
|
+
constraint_column_names,
|
|
2626
|
+
referenced_table,
|
|
2627
|
+
referenced_column_names
|
|
2628
|
+
FROM duckdb_constraints()
|
|
2629
|
+
WHERE ${databaseFilter}
|
|
2630
|
+
AND schema_name IN (${sql5.join(schemaFragments, sql5.raw(", "))})
|
|
2631
|
+
ORDER BY schema_name, table_name, constraint_index
|
|
2632
|
+
`);
|
|
2633
|
+
}
|
|
2634
|
+
async function loadIndexes(db, database, schemas) {
|
|
2635
|
+
const schemaFragments = schemas.map((schema) => sql5`${schema}`);
|
|
2636
|
+
const databaseFilter = database ? sql5`database_name = ${database}` : sql5`1 = 1`;
|
|
2637
|
+
return await db.execute(sql5`
|
|
2638
|
+
SELECT
|
|
2639
|
+
schema_name,
|
|
2640
|
+
table_name,
|
|
2641
|
+
index_name,
|
|
2642
|
+
is_unique,
|
|
2643
|
+
expressions
|
|
2644
|
+
FROM duckdb_indexes()
|
|
2645
|
+
WHERE ${databaseFilter}
|
|
2646
|
+
AND schema_name IN (${sql5.join(schemaFragments, sql5.raw(", "))})
|
|
2647
|
+
ORDER BY schema_name, table_name, index_name
|
|
2648
|
+
`);
|
|
2649
|
+
}
|
|
2650
|
+
function buildTables(tables, columns, constraints, indexes) {
|
|
2651
|
+
const byTable = {};
|
|
2652
|
+
for (const table of tables) {
|
|
2653
|
+
const key = tableKey(table.schema_name, table.table_name);
|
|
2654
|
+
byTable[key] = {
|
|
2655
|
+
schema: table.schema_name,
|
|
2656
|
+
name: table.table_name,
|
|
2657
|
+
kind: table.table_type === "VIEW" ? "view" : "table",
|
|
2658
|
+
columns: [],
|
|
2659
|
+
constraints: [],
|
|
2660
|
+
indexes: []
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
for (const column of columns) {
|
|
2664
|
+
if (column.internal) {
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
const key = tableKey(column.schema_name, column.table_name);
|
|
2668
|
+
const table = byTable[key];
|
|
2669
|
+
if (!table) {
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
table.columns.push({
|
|
2673
|
+
name: column.column_name,
|
|
2674
|
+
dataType: column.data_type,
|
|
2675
|
+
columnDefault: column.column_default,
|
|
2676
|
+
nullable: column.is_nullable,
|
|
2677
|
+
characterLength: column.character_maximum_length,
|
|
2678
|
+
numericPrecision: column.numeric_precision,
|
|
2679
|
+
numericScale: column.numeric_scale
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
for (const constraint of constraints) {
|
|
2683
|
+
const key = tableKey(constraint.schema_name, constraint.table_name);
|
|
2684
|
+
const table = byTable[key];
|
|
2685
|
+
if (!table) {
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
if (!constraint.constraint_column_names?.length) {
|
|
2689
|
+
continue;
|
|
2690
|
+
}
|
|
2691
|
+
table.constraints.push({
|
|
2692
|
+
name: constraint.constraint_name,
|
|
2693
|
+
type: constraint.constraint_type,
|
|
2694
|
+
columns: constraint.constraint_column_names ?? [],
|
|
2695
|
+
referencedTable: constraint.referenced_table && constraint.referenced_column_names ? {
|
|
2696
|
+
schema: constraint.schema_name,
|
|
2697
|
+
name: constraint.referenced_table,
|
|
2698
|
+
columns: constraint.referenced_column_names
|
|
2699
|
+
} : undefined,
|
|
2700
|
+
rawExpression: constraint.constraint_text
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
for (const index of indexes) {
|
|
2704
|
+
const key = tableKey(index.schema_name, index.table_name);
|
|
2705
|
+
const table = byTable[key];
|
|
2706
|
+
if (!table) {
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
table.indexes.push(index);
|
|
2710
|
+
}
|
|
2711
|
+
return Object.values(byTable);
|
|
2712
|
+
}
|
|
2713
|
+
function emitSchema(catalog, options) {
|
|
2714
|
+
const imports = {
|
|
2715
|
+
drizzle: new Set,
|
|
2716
|
+
pgCore: new Set,
|
|
2717
|
+
local: new Set
|
|
2718
|
+
};
|
|
2719
|
+
imports.pgCore.add("pgSchema");
|
|
2720
|
+
const sorted = [...catalog].sort((a, b) => a.schema === b.schema ? a.name.localeCompare(b.name) : a.schema.localeCompare(b.schema));
|
|
2721
|
+
const lines = [];
|
|
2722
|
+
for (const schema of uniqueSchemas(sorted)) {
|
|
2723
|
+
imports.pgCore.add("pgSchema");
|
|
2724
|
+
const schemaVar = toSchemaIdentifier(schema);
|
|
2725
|
+
lines.push(`export const ${schemaVar} = pgSchema(${JSON.stringify(schema)});`, "");
|
|
2726
|
+
const tables = sorted.filter((table) => table.schema === schema);
|
|
2727
|
+
for (const table of tables) {
|
|
2728
|
+
lines.push(...emitTable(schemaVar, table, imports, options));
|
|
2729
|
+
lines.push("");
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
const importsBlock = renderImports(imports, options.importBasePath);
|
|
2733
|
+
return [importsBlock, ...lines].join(`
|
|
2734
|
+
`).trim() + `
|
|
2735
|
+
`;
|
|
2736
|
+
}
|
|
2737
|
+
function emitTable(schemaVar, table, imports, options) {
|
|
2738
|
+
const tableVar = toIdentifier(table.name);
|
|
2739
|
+
const columnLines = [];
|
|
2740
|
+
for (const column of table.columns) {
|
|
2741
|
+
columnLines.push(` ${columnProperty(column.name)}: ${emitColumn(column, imports, options)},`);
|
|
2742
|
+
}
|
|
2743
|
+
const constraintBlock = emitConstraints(table, imports);
|
|
2744
|
+
const tableLines = [];
|
|
2745
|
+
tableLines.push(`export const ${tableVar} = ${schemaVar}.table(${JSON.stringify(table.name)}, {`);
|
|
2746
|
+
tableLines.push(...columnLines);
|
|
2747
|
+
tableLines.push(`}${constraintBlock ? "," : ""}${constraintBlock ? ` ${constraintBlock}` : ""});`);
|
|
2748
|
+
return tableLines;
|
|
2749
|
+
}
|
|
2750
|
+
function emitConstraints(table, imports) {
|
|
2751
|
+
const constraints = table.constraints.filter((constraint) => ["PRIMARY KEY", "FOREIGN KEY", "UNIQUE"].includes(constraint.type));
|
|
2752
|
+
if (!constraints.length) {
|
|
2753
|
+
return "";
|
|
2754
|
+
}
|
|
2755
|
+
const entries = [];
|
|
2756
|
+
for (const constraint of constraints) {
|
|
2757
|
+
const key = toIdentifier(constraint.name || `${table.name}_constraint`);
|
|
2758
|
+
if (constraint.type === "PRIMARY KEY") {
|
|
2759
|
+
imports.pgCore.add("primaryKey");
|
|
2760
|
+
entries.push(`${key}: primaryKey({ columns: [${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")}], name: ${JSON.stringify(constraint.name)} })`);
|
|
2761
|
+
} else if (constraint.type === "UNIQUE" && constraint.columns.length > 1) {
|
|
2762
|
+
imports.pgCore.add("unique");
|
|
2763
|
+
entries.push(`${key}: unique(${JSON.stringify(constraint.name)}).on(${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")})`);
|
|
2764
|
+
} else if (constraint.type === "FOREIGN KEY" && constraint.referencedTable) {
|
|
2765
|
+
imports.pgCore.add("foreignKey");
|
|
2766
|
+
const targetTable = toIdentifier(constraint.referencedTable.name);
|
|
2767
|
+
entries.push(`${key}: foreignKey({ columns: [${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")}], foreignColumns: [${constraint.referencedTable.columns.map((col) => `${targetTable}.${toIdentifier(col)}`).join(", ")}], name: ${JSON.stringify(constraint.name)} })`);
|
|
2768
|
+
} else if (constraint.type === "UNIQUE" && constraint.columns.length === 1) {
|
|
2769
|
+
const columnName = constraint.columns[0];
|
|
2770
|
+
entries.push(`${key}: t.${toIdentifier(columnName)}.unique(${JSON.stringify(constraint.name)})`);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
if (!entries.length) {
|
|
2774
|
+
return "";
|
|
2775
|
+
}
|
|
2776
|
+
const lines = ["(t) => ({"];
|
|
2777
|
+
for (const entry of entries) {
|
|
2778
|
+
lines.push(` ${entry},`);
|
|
2779
|
+
}
|
|
2780
|
+
lines.push("})");
|
|
2781
|
+
return lines.join(`
|
|
2782
|
+
`);
|
|
2783
|
+
}
|
|
2784
|
+
function emitColumn(column, imports, options) {
|
|
2785
|
+
const mapping = mapDuckDbType(column, imports, options);
|
|
2786
|
+
let builder = mapping.builder;
|
|
2787
|
+
if (!column.nullable) {
|
|
2788
|
+
builder += ".notNull()";
|
|
2789
|
+
}
|
|
2790
|
+
const defaultFragment = buildDefault(column.columnDefault);
|
|
2791
|
+
if (defaultFragment) {
|
|
2792
|
+
imports.drizzle.add("sql");
|
|
2793
|
+
builder += defaultFragment;
|
|
2794
|
+
}
|
|
2795
|
+
return builder;
|
|
2796
|
+
}
|
|
2797
|
+
function buildDefault(defaultValue) {
|
|
2798
|
+
if (!defaultValue) {
|
|
2799
|
+
return "";
|
|
2800
|
+
}
|
|
2801
|
+
const trimmed = defaultValue.trim();
|
|
2802
|
+
if (!trimmed || trimmed.toUpperCase() === "NULL") {
|
|
2803
|
+
return "";
|
|
2804
|
+
}
|
|
2805
|
+
if (/^nextval\(/i.test(trimmed)) {
|
|
2806
|
+
return `.default(sql\`${trimmed}\`)`;
|
|
2807
|
+
}
|
|
2808
|
+
if (/^current_timestamp(?:\(\))?$/i.test(trimmed) || /^now\(\)$/i.test(trimmed)) {
|
|
2809
|
+
return `.defaultNow()`;
|
|
2810
|
+
}
|
|
2811
|
+
if (trimmed === "true" || trimmed === "false") {
|
|
2812
|
+
return `.default(${trimmed})`;
|
|
2813
|
+
}
|
|
2814
|
+
const numberValue = Number(trimmed);
|
|
2815
|
+
if (!Number.isNaN(numberValue)) {
|
|
2816
|
+
return `.default(${trimmed})`;
|
|
2817
|
+
}
|
|
2818
|
+
const stringLiteralMatch = /^'(.*)'$/.exec(trimmed);
|
|
2819
|
+
if (stringLiteralMatch) {
|
|
2820
|
+
const value = stringLiteralMatch[1]?.replace(/''/g, "'");
|
|
2821
|
+
return `.default(${JSON.stringify(value)})`;
|
|
2822
|
+
}
|
|
2823
|
+
return "";
|
|
2824
|
+
}
|
|
2825
|
+
function mapDuckDbType(column, imports, options) {
|
|
2826
|
+
const raw = column.dataType.trim();
|
|
2827
|
+
const upper = raw.toUpperCase();
|
|
2828
|
+
if (upper === "BOOLEAN" || upper === "BOOL") {
|
|
2829
|
+
imports.pgCore.add("boolean");
|
|
2830
|
+
return { builder: `boolean(${columnName(column.name)})` };
|
|
2831
|
+
}
|
|
2832
|
+
if (upper === "SMALLINT" || upper === "INT2" || upper === "INT16" || upper === "TINYINT") {
|
|
2833
|
+
imports.pgCore.add("integer");
|
|
2834
|
+
return { builder: `integer(${columnName(column.name)})` };
|
|
2835
|
+
}
|
|
2836
|
+
if (upper === "INTEGER" || upper === "INT" || upper === "INT4" || upper === "SIGNED") {
|
|
2837
|
+
imports.pgCore.add("integer");
|
|
2838
|
+
return { builder: `integer(${columnName(column.name)})` };
|
|
2839
|
+
}
|
|
2840
|
+
if (upper === "BIGINT" || upper === "INT8" || upper === "UBIGINT") {
|
|
2841
|
+
imports.pgCore.add("bigint");
|
|
2842
|
+
return {
|
|
2843
|
+
builder: `bigint(${columnName(column.name)}, { mode: 'number' })`
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
|
|
2847
|
+
const numericMatch = /^NUMERIC\((\d+),(\d+)\)/i.exec(upper);
|
|
2848
|
+
if (decimalMatch || numericMatch) {
|
|
2849
|
+
imports.pgCore.add("numeric");
|
|
2850
|
+
const [, precision, scale] = decimalMatch ?? numericMatch;
|
|
2851
|
+
return {
|
|
2852
|
+
builder: `numeric(${columnName(column.name)}, { precision: ${precision}, scale: ${scale} })`
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
if (upper.startsWith("DECIMAL") || upper.startsWith("NUMERIC")) {
|
|
2856
|
+
imports.pgCore.add("numeric");
|
|
2857
|
+
const precision = column.numericPrecision;
|
|
2858
|
+
const scale = column.numericScale;
|
|
2859
|
+
const options2 = [];
|
|
2860
|
+
if (precision !== null && precision !== undefined) {
|
|
2861
|
+
options2.push(`precision: ${precision}`);
|
|
2862
|
+
}
|
|
2863
|
+
if (scale !== null && scale !== undefined) {
|
|
2864
|
+
options2.push(`scale: ${scale}`);
|
|
2865
|
+
}
|
|
2866
|
+
const suffix = options2.length ? `, { ${options2.join(", ")} }` : "";
|
|
2867
|
+
return { builder: `numeric(${columnName(column.name)}${suffix})` };
|
|
2868
|
+
}
|
|
2869
|
+
if (upper === "REAL" || upper === "FLOAT4") {
|
|
2870
|
+
imports.pgCore.add("real");
|
|
2871
|
+
return { builder: `real(${columnName(column.name)})` };
|
|
2872
|
+
}
|
|
2873
|
+
if (upper === "DOUBLE" || upper === "DOUBLE PRECISION" || upper === "FLOAT") {
|
|
2874
|
+
imports.pgCore.add("doublePrecision");
|
|
2875
|
+
return { builder: `doublePrecision(${columnName(column.name)})` };
|
|
2876
|
+
}
|
|
2877
|
+
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
2878
|
+
if (arrayMatch) {
|
|
2879
|
+
imports.local.add("duckDbArray");
|
|
2880
|
+
const [, base, length] = arrayMatch;
|
|
2881
|
+
return {
|
|
2882
|
+
builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
2886
|
+
if (listMatch) {
|
|
2887
|
+
imports.local.add("duckDbList");
|
|
2888
|
+
const [, base] = listMatch;
|
|
2889
|
+
return {
|
|
2890
|
+
builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
if (upper.startsWith("CHAR(") || upper === "CHAR") {
|
|
2894
|
+
imports.pgCore.add("char");
|
|
2895
|
+
const length = column.characterLength;
|
|
2896
|
+
const lengthPart = typeof length === "number" ? `, { length: ${length} }` : "";
|
|
2897
|
+
return { builder: `char(${columnName(column.name)}${lengthPart})` };
|
|
2898
|
+
}
|
|
2899
|
+
if (upper.startsWith("VARCHAR")) {
|
|
2900
|
+
imports.pgCore.add("varchar");
|
|
2901
|
+
const length = column.characterLength;
|
|
2902
|
+
const lengthPart = typeof length === "number" ? `, { length: ${length} }` : "";
|
|
2903
|
+
return { builder: `varchar(${columnName(column.name)}${lengthPart})` };
|
|
2904
|
+
}
|
|
2905
|
+
if (upper === "TEXT" || upper === "STRING") {
|
|
2906
|
+
imports.pgCore.add("text");
|
|
2907
|
+
return { builder: `text(${columnName(column.name)})` };
|
|
2908
|
+
}
|
|
2909
|
+
if (upper === "UUID") {
|
|
2910
|
+
imports.pgCore.add("uuid");
|
|
2911
|
+
return { builder: `uuid(${columnName(column.name)})` };
|
|
2912
|
+
}
|
|
2913
|
+
if (upper === "JSON") {
|
|
2914
|
+
if (options.mapJsonAsDuckDbJson) {
|
|
2915
|
+
imports.local.add("duckDbJson");
|
|
2916
|
+
return { builder: `duckDbJson(${columnName(column.name)})` };
|
|
2917
|
+
}
|
|
2918
|
+
imports.pgCore.add("text");
|
|
2919
|
+
return { builder: `text(${columnName(column.name)}) /* JSON */` };
|
|
2920
|
+
}
|
|
2921
|
+
if (upper.startsWith("ENUM")) {
|
|
2922
|
+
imports.pgCore.add("text");
|
|
2923
|
+
const enumLiteral = raw.replace(/^ENUM\s*/i, "").trim();
|
|
2924
|
+
return {
|
|
2925
|
+
builder: `text(${columnName(column.name)}) /* ENUM ${enumLiteral} */`
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
if (upper.startsWith("UNION")) {
|
|
2929
|
+
imports.pgCore.add("text");
|
|
2930
|
+
const unionLiteral = raw.replace(/^UNION\s*/i, "").trim();
|
|
2931
|
+
return {
|
|
2932
|
+
builder: `text(${columnName(column.name)}) /* UNION ${unionLiteral} */`
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
if (upper === "INET") {
|
|
2936
|
+
imports.local.add("duckDbInet");
|
|
2937
|
+
return { builder: `duckDbInet(${columnName(column.name)})` };
|
|
2938
|
+
}
|
|
2939
|
+
if (upper === "INTERVAL") {
|
|
2940
|
+
imports.local.add("duckDbInterval");
|
|
2941
|
+
return { builder: `duckDbInterval(${columnName(column.name)})` };
|
|
2942
|
+
}
|
|
2943
|
+
if (upper === "BLOB" || upper === "BYTEA" || upper === "VARBINARY") {
|
|
2944
|
+
imports.local.add("duckDbBlob");
|
|
2945
|
+
return { builder: `duckDbBlob(${columnName(column.name)})` };
|
|
2946
|
+
}
|
|
2947
|
+
if (upper.startsWith("STRUCT")) {
|
|
2948
|
+
imports.local.add("duckDbStruct");
|
|
2949
|
+
const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
|
|
2950
|
+
const fields = parseStructFields(inner);
|
|
2951
|
+
const entries = fields.map(({ name, type }) => `${JSON.stringify(name)}: ${JSON.stringify(type)}`);
|
|
2952
|
+
return {
|
|
2953
|
+
builder: `duckDbStruct(${columnName(column.name)}, { ${entries.join(", ")} })`
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
if (upper.startsWith("MAP(")) {
|
|
2957
|
+
imports.local.add("duckDbMap");
|
|
2958
|
+
const valueType = parseMapValue(upper);
|
|
2959
|
+
return {
|
|
2960
|
+
builder: `duckDbMap(${columnName(column.name)}, ${JSON.stringify(valueType)})`
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
if (upper.startsWith("TIMESTAMP WITH TIME ZONE")) {
|
|
2964
|
+
if (options.useCustomTimeTypes) {
|
|
2965
|
+
imports.local.add("duckDbTimestamp");
|
|
2966
|
+
} else {
|
|
2967
|
+
imports.pgCore.add("timestamp");
|
|
2968
|
+
}
|
|
2969
|
+
const factory = options.useCustomTimeTypes ? `duckDbTimestamp(${columnName(column.name)}, { withTimezone: true })` : `timestamp(${columnName(column.name)}, { withTimezone: true })`;
|
|
2970
|
+
return { builder: factory };
|
|
2971
|
+
}
|
|
2972
|
+
if (upper.startsWith("TIMESTAMP")) {
|
|
2973
|
+
if (options.useCustomTimeTypes) {
|
|
2974
|
+
imports.local.add("duckDbTimestamp");
|
|
2975
|
+
return {
|
|
2976
|
+
builder: `duckDbTimestamp(${columnName(column.name)})`
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
imports.pgCore.add("timestamp");
|
|
2980
|
+
return { builder: `timestamp(${columnName(column.name)})` };
|
|
2981
|
+
}
|
|
2982
|
+
if (upper === "TIME") {
|
|
2983
|
+
if (options.useCustomTimeTypes) {
|
|
2984
|
+
imports.local.add("duckDbTime");
|
|
2985
|
+
return { builder: `duckDbTime(${columnName(column.name)})` };
|
|
2986
|
+
}
|
|
2987
|
+
imports.pgCore.add("time");
|
|
2988
|
+
return { builder: `time(${columnName(column.name)})` };
|
|
2989
|
+
}
|
|
2990
|
+
if (upper === "DATE") {
|
|
2991
|
+
if (options.useCustomTimeTypes) {
|
|
2992
|
+
imports.local.add("duckDbDate");
|
|
2993
|
+
return { builder: `duckDbDate(${columnName(column.name)})` };
|
|
2994
|
+
}
|
|
2995
|
+
imports.pgCore.add("date");
|
|
2996
|
+
return { builder: `date(${columnName(column.name)})` };
|
|
2997
|
+
}
|
|
2998
|
+
imports.pgCore.add("text");
|
|
2999
|
+
return {
|
|
3000
|
+
builder: `text(${columnName(column.name)}) /* unsupported DuckDB type: ${upper} */`
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function parseStructFields(inner) {
|
|
3004
|
+
const result = [];
|
|
3005
|
+
for (const part of splitTopLevel(inner, ",")) {
|
|
3006
|
+
const trimmed = part.trim();
|
|
3007
|
+
if (!trimmed)
|
|
3008
|
+
continue;
|
|
3009
|
+
const match = /^"?([^"]+)"?\s+(.*)$/i.exec(trimmed);
|
|
3010
|
+
if (!match) {
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
const [, name, type] = match;
|
|
3014
|
+
result.push({ name, type: type.trim() });
|
|
3015
|
+
}
|
|
3016
|
+
return result;
|
|
3017
|
+
}
|
|
3018
|
+
function parseMapValue(raw) {
|
|
3019
|
+
const inner = raw.replace(/^MAP\(/i, "").replace(/\)$/, "");
|
|
3020
|
+
const parts = splitTopLevel(inner, ",");
|
|
3021
|
+
if (parts.length < 2) {
|
|
3022
|
+
return "TEXT";
|
|
3023
|
+
}
|
|
3024
|
+
return parts[1]?.trim() ?? "TEXT";
|
|
3025
|
+
}
|
|
3026
|
+
function splitTopLevel(input, delimiter) {
|
|
3027
|
+
const parts = [];
|
|
3028
|
+
let depth = 0;
|
|
3029
|
+
let current = "";
|
|
3030
|
+
for (let i = 0;i < input.length; i += 1) {
|
|
3031
|
+
const char = input[i];
|
|
3032
|
+
if (char === "(")
|
|
3033
|
+
depth += 1;
|
|
3034
|
+
if (char === ")")
|
|
3035
|
+
depth = Math.max(0, depth - 1);
|
|
3036
|
+
if (char === delimiter && depth === 0) {
|
|
3037
|
+
parts.push(current);
|
|
3038
|
+
current = "";
|
|
3039
|
+
continue;
|
|
3040
|
+
}
|
|
3041
|
+
current += char;
|
|
3042
|
+
}
|
|
3043
|
+
if (current) {
|
|
3044
|
+
parts.push(current);
|
|
3045
|
+
}
|
|
3046
|
+
return parts;
|
|
3047
|
+
}
|
|
3048
|
+
function tableKey(schema, table) {
|
|
3049
|
+
return `${schema}.${table}`;
|
|
3050
|
+
}
|
|
3051
|
+
function toIdentifier(name) {
|
|
3052
|
+
const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
3053
|
+
const parts = cleaned.split("_").filter(Boolean);
|
|
3054
|
+
const base = parts.map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part.toLowerCase())).join("");
|
|
3055
|
+
const candidate = base || "item";
|
|
3056
|
+
return /^[A-Za-z_]/.test(candidate) ? candidate : `t${candidate}`;
|
|
3057
|
+
}
|
|
3058
|
+
function toSchemaIdentifier(schema) {
|
|
3059
|
+
const base = toIdentifier(schema);
|
|
3060
|
+
return base.endsWith("Schema") ? base : `${base}Schema`;
|
|
3061
|
+
}
|
|
3062
|
+
function columnProperty(column) {
|
|
3063
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
|
|
3064
|
+
return toIdentifier(column);
|
|
3065
|
+
}
|
|
3066
|
+
return JSON.stringify(column);
|
|
3067
|
+
}
|
|
3068
|
+
function columnName(name) {
|
|
3069
|
+
return JSON.stringify(name);
|
|
3070
|
+
}
|
|
3071
|
+
function capitalize(value) {
|
|
3072
|
+
if (!value)
|
|
3073
|
+
return value;
|
|
3074
|
+
return value[0].toUpperCase() + value.slice(1);
|
|
3075
|
+
}
|
|
3076
|
+
function uniqueSchemas(tables) {
|
|
3077
|
+
const seen = new Set;
|
|
3078
|
+
const result = [];
|
|
3079
|
+
for (const table of tables) {
|
|
3080
|
+
if (!seen.has(table.schema)) {
|
|
3081
|
+
seen.add(table.schema);
|
|
3082
|
+
result.push(table.schema);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
return result;
|
|
3086
|
+
}
|
|
3087
|
+
function renderImports(imports, importBasePath) {
|
|
3088
|
+
const lines = [];
|
|
3089
|
+
const drizzle2 = [...imports.drizzle];
|
|
3090
|
+
if (drizzle2.length) {
|
|
3091
|
+
lines.push(`import { ${drizzle2.sort().join(", ")} } from 'drizzle-orm';`);
|
|
3092
|
+
}
|
|
3093
|
+
const pgCore = [...imports.pgCore];
|
|
3094
|
+
if (pgCore.length) {
|
|
3095
|
+
lines.push(`import { ${pgCore.sort().join(", ")} } from 'drizzle-orm/pg-core';`);
|
|
3096
|
+
}
|
|
3097
|
+
const local = [...imports.local];
|
|
3098
|
+
if (local.length) {
|
|
3099
|
+
lines.push(`import { ${local.sort().join(", ")} } from '${importBasePath}';`);
|
|
3100
|
+
}
|
|
3101
|
+
lines.push("");
|
|
3102
|
+
return lines.join(`
|
|
3103
|
+
`);
|
|
3104
|
+
}
|
|
3105
|
+
// src/olap.ts
|
|
3106
|
+
import { is as is5 } from "drizzle-orm/entity";
|
|
3107
|
+
import { sql as sql6 } from "drizzle-orm";
|
|
3108
|
+
import { SQL as SQL6 } from "drizzle-orm/sql/sql";
|
|
3109
|
+
import { Column as Column3, getTableName as getTableName3 } from "drizzle-orm";
|
|
3110
|
+
var countN = (expr = sql6`*`) => sql6`count(${expr})`.mapWith(Number);
|
|
3111
|
+
var sumN = (expr) => sql6`sum(${expr})`.mapWith(Number);
|
|
3112
|
+
var avgN = (expr) => sql6`avg(${expr})`.mapWith(Number);
|
|
3113
|
+
var sumDistinctN = (expr) => sql6`sum(distinct ${expr})`.mapWith(Number);
|
|
3114
|
+
var percentileCont = (p, expr) => sql6`percentile_cont(${p}) within group (order by ${expr})`.mapWith(Number);
|
|
3115
|
+
var median = (expr) => percentileCont(0.5, expr);
|
|
3116
|
+
var anyValue = (expr) => sql6`any_value(${expr})`;
|
|
3117
|
+
function normalizeArray(value) {
|
|
3118
|
+
if (!value)
|
|
3119
|
+
return [];
|
|
3120
|
+
return Array.isArray(value) ? value : [value];
|
|
3121
|
+
}
|
|
3122
|
+
function overClause(options) {
|
|
3123
|
+
const partitions = normalizeArray(options?.partitionBy);
|
|
3124
|
+
const orders = normalizeArray(options?.orderBy);
|
|
3125
|
+
const chunks = [];
|
|
3126
|
+
if (partitions.length > 0) {
|
|
3127
|
+
chunks.push(sql6`partition by ${sql6.join(partitions, sql6`, `)}`);
|
|
3128
|
+
}
|
|
3129
|
+
if (orders.length > 0) {
|
|
3130
|
+
chunks.push(sql6`order by ${sql6.join(orders, sql6`, `)}`);
|
|
3131
|
+
}
|
|
3132
|
+
if (chunks.length === 0) {
|
|
3133
|
+
return sql6``;
|
|
3134
|
+
}
|
|
3135
|
+
return sql6`over (${sql6.join(chunks, sql6` `)})`;
|
|
3136
|
+
}
|
|
3137
|
+
var rowNumber = (options) => sql6`row_number() ${overClause(options)}`.mapWith(Number);
|
|
3138
|
+
var rank = (options) => sql6`rank() ${overClause(options)}`.mapWith(Number);
|
|
3139
|
+
var denseRank = (options) => sql6`dense_rank() ${overClause(options)}`.mapWith(Number);
|
|
3140
|
+
var lag = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lag(${expr}, ${offset}) ${overClause(options)}`;
|
|
3141
|
+
var lead = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lead(${expr}, ${offset}) ${overClause(options)}`;
|
|
3142
|
+
function keyAlias(key, fallback) {
|
|
3143
|
+
if (is5(key, SQL6.Aliased)) {
|
|
3144
|
+
return key.fieldAlias ?? fallback;
|
|
3145
|
+
}
|
|
3146
|
+
if (is5(key, Column3)) {
|
|
3147
|
+
return `${getTableName3(key.table)}.${key.name}`;
|
|
3148
|
+
}
|
|
3149
|
+
return fallback;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
class OlapBuilder {
|
|
3153
|
+
db;
|
|
3154
|
+
source;
|
|
3155
|
+
keys = [];
|
|
3156
|
+
measureMap = {};
|
|
3157
|
+
nonAggregates = {};
|
|
3158
|
+
wrapNonAggWithAnyValue = false;
|
|
3159
|
+
orderByClauses = [];
|
|
3160
|
+
constructor(db) {
|
|
3161
|
+
this.db = db;
|
|
3162
|
+
}
|
|
3163
|
+
from(source) {
|
|
3164
|
+
this.source = source;
|
|
3165
|
+
return this;
|
|
3166
|
+
}
|
|
3167
|
+
groupBy(keys) {
|
|
3168
|
+
this.keys = keys;
|
|
3169
|
+
return this;
|
|
3170
|
+
}
|
|
3171
|
+
measures(measures) {
|
|
3172
|
+
this.measureMap = measures;
|
|
3173
|
+
return this;
|
|
3174
|
+
}
|
|
3175
|
+
selectNonAggregates(fields, options = {}) {
|
|
3176
|
+
this.nonAggregates = fields;
|
|
3177
|
+
this.wrapNonAggWithAnyValue = options.anyValue ?? false;
|
|
3178
|
+
return this;
|
|
3179
|
+
}
|
|
3180
|
+
orderBy(...clauses) {
|
|
3181
|
+
this.orderByClauses = clauses;
|
|
3182
|
+
return this;
|
|
3183
|
+
}
|
|
3184
|
+
build() {
|
|
3185
|
+
if (!this.source) {
|
|
3186
|
+
throw new Error("olap: .from() is required");
|
|
3187
|
+
}
|
|
3188
|
+
if (this.keys.length === 0) {
|
|
3189
|
+
throw new Error("olap: .groupBy() is required");
|
|
3190
|
+
}
|
|
3191
|
+
if (Object.keys(this.measureMap).length === 0) {
|
|
3192
|
+
throw new Error("olap: .measures() is required");
|
|
3193
|
+
}
|
|
3194
|
+
const selection = {};
|
|
3195
|
+
this.keys.forEach((key, idx) => {
|
|
3196
|
+
const alias = keyAlias(key, `key_${idx}`);
|
|
3197
|
+
selection[alias] = key;
|
|
3198
|
+
});
|
|
3199
|
+
Object.entries(this.nonAggregates).forEach(([alias, expr]) => {
|
|
3200
|
+
selection[alias] = this.wrapNonAggWithAnyValue ? anyValue(expr) : expr;
|
|
3201
|
+
});
|
|
3202
|
+
Object.assign(selection, this.measureMap);
|
|
3203
|
+
let query = this.db.select(selection).from(this.source).groupBy(...this.keys);
|
|
3204
|
+
if (this.orderByClauses.length > 0) {
|
|
3205
|
+
query = query.orderBy(...this.orderByClauses);
|
|
3206
|
+
}
|
|
3207
|
+
return query;
|
|
3208
|
+
}
|
|
3209
|
+
run() {
|
|
3210
|
+
return this.build();
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
var olap = (db) => new OlapBuilder(db);
|
|
3214
|
+
// src/operators.ts
|
|
3215
|
+
import { sql as sql7 } from "drizzle-orm";
|
|
3216
|
+
function arrayHasAll(column, values) {
|
|
3217
|
+
return sql7`array_has_all(${column}, ${values})`;
|
|
3218
|
+
}
|
|
3219
|
+
function arrayHasAny(column, values) {
|
|
3220
|
+
return sql7`array_has_any(${column}, ${values})`;
|
|
3221
|
+
}
|
|
3222
|
+
function arrayContainedBy(column, values) {
|
|
3223
|
+
return sql7`array_has_all(${values}, ${column})`;
|
|
426
3224
|
}
|
|
427
3225
|
export {
|
|
3226
|
+
wrapperToNodeApiValue,
|
|
3227
|
+
wrapTimestamp,
|
|
3228
|
+
wrapStruct,
|
|
3229
|
+
wrapMap,
|
|
3230
|
+
wrapList,
|
|
3231
|
+
wrapJson,
|
|
3232
|
+
wrapBlob,
|
|
3233
|
+
wrapArray,
|
|
3234
|
+
toIdentifier,
|
|
3235
|
+
sumN,
|
|
3236
|
+
sumDistinctN,
|
|
3237
|
+
splitTopLevel,
|
|
3238
|
+
rowNumber,
|
|
3239
|
+
resolvePrepareCacheOption,
|
|
3240
|
+
resolvePoolSize,
|
|
3241
|
+
rank,
|
|
3242
|
+
prepareParams,
|
|
3243
|
+
percentileCont,
|
|
3244
|
+
parseStructFields,
|
|
3245
|
+
parseMapValue,
|
|
3246
|
+
olap,
|
|
428
3247
|
migrate,
|
|
3248
|
+
median,
|
|
3249
|
+
lead,
|
|
3250
|
+
lag,
|
|
3251
|
+
isPool,
|
|
3252
|
+
isDuckDBWrapper,
|
|
3253
|
+
introspect,
|
|
3254
|
+
formatLiteral,
|
|
3255
|
+
executeOnClient,
|
|
3256
|
+
executeInBatchesRaw,
|
|
3257
|
+
executeInBatches,
|
|
3258
|
+
executeArrowOnClient,
|
|
3259
|
+
executeArraysOnClient,
|
|
3260
|
+
duckDbTimestamp,
|
|
3261
|
+
duckDbTime,
|
|
3262
|
+
duckDbStruct,
|
|
3263
|
+
duckDbMap,
|
|
3264
|
+
duckDbList,
|
|
3265
|
+
duckDbJson,
|
|
3266
|
+
duckDbInterval,
|
|
3267
|
+
duckDbInet,
|
|
3268
|
+
duckDbDate,
|
|
3269
|
+
duckDbBlob,
|
|
3270
|
+
duckDbArrayOverlaps,
|
|
3271
|
+
duckDbArrayContains,
|
|
3272
|
+
duckDbArrayContained,
|
|
3273
|
+
duckDbArray,
|
|
429
3274
|
drizzle,
|
|
3275
|
+
denseRank,
|
|
3276
|
+
createDuckDBConnectionPool,
|
|
3277
|
+
countN,
|
|
3278
|
+
coerceArrayString,
|
|
3279
|
+
closeClientConnection,
|
|
3280
|
+
buildStructLiteral,
|
|
3281
|
+
buildMapLiteral,
|
|
3282
|
+
buildListLiteral,
|
|
3283
|
+
buildDefault,
|
|
3284
|
+
avgN,
|
|
3285
|
+
arrayHasAny,
|
|
3286
|
+
arrayHasAll,
|
|
3287
|
+
arrayContainedBy,
|
|
3288
|
+
anyValue,
|
|
3289
|
+
POOL_PRESETS,
|
|
3290
|
+
OlapBuilder,
|
|
430
3291
|
DuckDBTransaction,
|
|
431
3292
|
DuckDBSession,
|
|
432
|
-
DuckDBSelectBuilder,
|
|
433
3293
|
DuckDBPreparedQuery,
|
|
434
3294
|
DuckDBDriver,
|
|
435
|
-
DuckDBDatabase
|
|
3295
|
+
DuckDBDatabase,
|
|
3296
|
+
DUCKDB_VALUE_MARKER,
|
|
3297
|
+
DEFAULT_IMPORT_BASE
|
|
436
3298
|
};
|