@duckdbfan/drizzle-duckdb 0.0.7 → 1.3.2

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.
Files changed (55) hide show
  1. package/README.md +349 -62
  2. package/dist/bin/duckdb-introspect.d.ts +2 -0
  3. package/dist/client.d.ts +42 -0
  4. package/dist/columns.d.ts +100 -9
  5. package/dist/dialect.d.ts +27 -2
  6. package/dist/driver.d.ts +53 -37
  7. package/dist/duckdb-introspect.mjs +2890 -0
  8. package/dist/helpers.d.ts +1 -0
  9. package/dist/helpers.mjs +360 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.mjs +3015 -228
  12. package/dist/introspect.d.ts +74 -0
  13. package/dist/migrator.d.ts +3 -2
  14. package/dist/olap.d.ts +46 -0
  15. package/dist/operators.d.ts +8 -0
  16. package/dist/options.d.ts +7 -0
  17. package/dist/pool.d.ts +30 -0
  18. package/dist/select-builder.d.ts +31 -0
  19. package/dist/session.d.ts +33 -8
  20. package/dist/sql/ast-transformer.d.ts +33 -0
  21. package/dist/sql/result-mapper.d.ts +9 -0
  22. package/dist/sql/selection.d.ts +2 -0
  23. package/dist/sql/visitors/array-operators.d.ts +5 -0
  24. package/dist/sql/visitors/column-qualifier.d.ts +10 -0
  25. package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
  26. package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
  27. package/dist/utils.d.ts +2 -5
  28. package/dist/value-wrappers-core.d.ts +42 -0
  29. package/dist/value-wrappers.d.ts +8 -0
  30. package/package.json +53 -16
  31. package/src/bin/duckdb-introspect.ts +181 -0
  32. package/src/client.ts +528 -0
  33. package/src/columns.ts +420 -65
  34. package/src/dialect.ts +111 -15
  35. package/src/driver.ts +266 -180
  36. package/src/helpers.ts +18 -0
  37. package/src/index.ts +8 -1
  38. package/src/introspect.ts +935 -0
  39. package/src/migrator.ts +10 -5
  40. package/src/olap.ts +190 -0
  41. package/src/operators.ts +27 -0
  42. package/src/options.ts +25 -0
  43. package/src/pool.ts +274 -0
  44. package/src/select-builder.ts +110 -0
  45. package/src/session.ts +306 -66
  46. package/src/sql/ast-transformer.ts +170 -0
  47. package/src/sql/result-mapper.ts +303 -0
  48. package/src/sql/selection.ts +60 -0
  49. package/src/sql/visitors/array-operators.ts +214 -0
  50. package/src/sql/visitors/column-qualifier.ts +586 -0
  51. package/src/sql/visitors/generate-series-alias.ts +291 -0
  52. package/src/sql/visitors/union-with-hoister.ts +106 -0
  53. package/src/utils.ts +2 -222
  54. package/src/value-wrappers-core.ts +168 -0
  55. package/src/value-wrappers.ts +165 -0
@@ -0,0 +1,586 @@
1
+ /**
2
+ * AST visitor to qualify unqualified column references in JOIN ON clauses.
3
+ *
4
+ * Performance optimizations:
5
+ * - Early exit when no unqualified columns found in ON clause
6
+ * - Skip processing if all columns are already qualified
7
+ * - Minimal tree traversal when possible
8
+ */
9
+
10
+ import type {
11
+ AST,
12
+ Binary,
13
+ ColumnRefItem,
14
+ ExpressionValue,
15
+ Select,
16
+ From,
17
+ Join,
18
+ OrderBy,
19
+ Column,
20
+ } from 'node-sql-parser';
21
+
22
+ type TableSource = {
23
+ name: string;
24
+ alias: string | null;
25
+ schema: string | null;
26
+ };
27
+
28
+ type Qualifier = {
29
+ table: string;
30
+ schema: string | null;
31
+ };
32
+
33
+ function getTableSource(from: From): TableSource | null {
34
+ if ('table' in from && from.table) {
35
+ return {
36
+ name: from.table,
37
+ alias: from.as ?? null,
38
+ schema: 'db' in from ? (from.db ?? null) : null,
39
+ };
40
+ }
41
+ if ('expr' in from && from.as) {
42
+ return {
43
+ name: from.as,
44
+ alias: from.as,
45
+ schema: null,
46
+ };
47
+ }
48
+ return null;
49
+ }
50
+
51
+ function getQualifier(source: TableSource): Qualifier {
52
+ return {
53
+ table: source.alias ?? source.name,
54
+ schema: source.schema,
55
+ };
56
+ }
57
+
58
+ function isUnqualifiedColumnRef(expr: ExpressionValue): expr is ColumnRefItem {
59
+ return (
60
+ typeof expr === 'object' &&
61
+ expr !== null &&
62
+ 'type' in expr &&
63
+ expr.type === 'column_ref' &&
64
+ (!('table' in expr) || !expr.table)
65
+ );
66
+ }
67
+
68
+ function isQualifiedColumnRef(expr: ExpressionValue): expr is ColumnRefItem {
69
+ return (
70
+ typeof expr === 'object' &&
71
+ expr !== null &&
72
+ 'type' in expr &&
73
+ expr.type === 'column_ref' &&
74
+ 'table' in expr &&
75
+ !!expr.table
76
+ );
77
+ }
78
+
79
+ function getColumnName(col: ColumnRefItem): string | null {
80
+ if (typeof col.column === 'string') {
81
+ return col.column;
82
+ }
83
+ if (col.column && 'expr' in col.column && col.column.expr?.value) {
84
+ return String(col.column.expr.value);
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function applyQualifier(col: ColumnRefItem, qualifier: Qualifier): void {
90
+ col.table = qualifier.table;
91
+ if (!('schema' in col) || !col.schema) {
92
+ (col as ColumnRefItem & { schema?: string | null }).schema =
93
+ qualifier.schema;
94
+ }
95
+ }
96
+
97
+ function unwrapColumnRef(
98
+ expr: ExpressionValue | undefined
99
+ ): ColumnRefItem | null {
100
+ if (!expr || typeof expr !== 'object') return null;
101
+ if ('type' in expr && expr.type === 'column_ref') {
102
+ return expr as ColumnRefItem;
103
+ }
104
+ if ('expr' in expr && expr.expr) {
105
+ return unwrapColumnRef(expr.expr as ExpressionValue);
106
+ }
107
+ if ('ast' in expr && expr.ast && typeof expr.ast === 'object') {
108
+ return null;
109
+ }
110
+ if ('args' in expr && expr.args) {
111
+ const args = expr.args as {
112
+ value?: ExpressionValue[];
113
+ expr?: ExpressionValue;
114
+ };
115
+ if (args.expr) {
116
+ return unwrapColumnRef(args.expr as ExpressionValue);
117
+ }
118
+ if (args.value && args.value.length === 1) {
119
+ return unwrapColumnRef(args.value[0] as ExpressionValue);
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+
125
+ function isBinaryExpr(
126
+ expr: ExpressionValue | Binary | null | undefined
127
+ ): expr is Binary {
128
+ return (
129
+ !!expr &&
130
+ typeof expr === 'object' &&
131
+ 'type' in expr &&
132
+ (expr as { type?: string }).type === 'binary_expr'
133
+ );
134
+ }
135
+
136
+ function walkOnClause(
137
+ expr: Binary | ExpressionValue | null | undefined,
138
+ leftQualifier: Qualifier,
139
+ rightQualifier: Qualifier,
140
+ ambiguousColumns: Set<string>
141
+ ): boolean {
142
+ if (!expr || typeof expr !== 'object') return false;
143
+
144
+ let transformed = false;
145
+
146
+ if (isBinaryExpr(expr)) {
147
+ const left = expr.left as ExpressionValue;
148
+ const right = expr.right as ExpressionValue;
149
+
150
+ const leftCol = unwrapColumnRef(left);
151
+ const rightCol = unwrapColumnRef(right);
152
+
153
+ const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
154
+ const rightUnqualified = rightCol
155
+ ? isUnqualifiedColumnRef(rightCol)
156
+ : false;
157
+ const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
158
+ const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
159
+ const leftColName = leftCol ? getColumnName(leftCol) : null;
160
+ const rightColName = rightCol ? getColumnName(rightCol) : null;
161
+
162
+ if (
163
+ expr.operator === '=' &&
164
+ leftColName &&
165
+ rightColName &&
166
+ leftColName === rightColName
167
+ ) {
168
+ if (leftUnqualified && rightUnqualified) {
169
+ applyQualifier(leftCol!, leftQualifier);
170
+ applyQualifier(rightCol!, rightQualifier);
171
+ ambiguousColumns.add(leftColName);
172
+ transformed = true;
173
+ } else if (leftQualified && rightUnqualified) {
174
+ applyQualifier(rightCol!, rightQualifier);
175
+ ambiguousColumns.add(rightColName);
176
+ transformed = true;
177
+ } else if (leftUnqualified && rightQualified) {
178
+ applyQualifier(leftCol!, leftQualifier);
179
+ ambiguousColumns.add(leftColName);
180
+ transformed = true;
181
+ }
182
+ }
183
+
184
+ if (
185
+ expr.operator === '=' &&
186
+ leftCol &&
187
+ rightCol &&
188
+ leftColName &&
189
+ rightColName &&
190
+ leftColName !== rightColName
191
+ ) {
192
+ if (leftQualified && rightUnqualified && !rightColName.includes('.')) {
193
+ applyQualifier(rightCol, rightQualifier);
194
+ transformed = true;
195
+ } else if (
196
+ leftUnqualified &&
197
+ rightQualified &&
198
+ !leftColName.includes('.')
199
+ ) {
200
+ applyQualifier(leftCol, leftQualifier);
201
+ transformed = true;
202
+ }
203
+ }
204
+
205
+ transformed =
206
+ walkOnClause(
207
+ isBinaryExpr(expr.left as Binary)
208
+ ? (expr.left as Binary)
209
+ : (expr.left as ExpressionValue),
210
+ leftQualifier,
211
+ rightQualifier,
212
+ ambiguousColumns
213
+ ) || transformed;
214
+ transformed =
215
+ walkOnClause(
216
+ isBinaryExpr(expr.right as Binary)
217
+ ? (expr.right as Binary)
218
+ : (expr.right as ExpressionValue),
219
+ leftQualifier,
220
+ rightQualifier,
221
+ ambiguousColumns
222
+ ) || transformed;
223
+ }
224
+
225
+ return transformed;
226
+ }
227
+
228
+ function qualifyAmbiguousInExpression(
229
+ expr: ExpressionValue | null | undefined,
230
+ defaultQualifier: Qualifier,
231
+ ambiguousColumns: Set<string>
232
+ ): boolean {
233
+ if (!expr || typeof expr !== 'object') return false;
234
+
235
+ let transformed = false;
236
+
237
+ if (isUnqualifiedColumnRef(expr)) {
238
+ const colName = getColumnName(expr);
239
+ if (colName && ambiguousColumns.has(colName)) {
240
+ applyQualifier(expr, defaultQualifier);
241
+ transformed = true;
242
+ }
243
+ return transformed;
244
+ }
245
+
246
+ if (isBinaryExpr(expr)) {
247
+ const binary = expr as Binary;
248
+ transformed =
249
+ qualifyAmbiguousInExpression(
250
+ binary.left as ExpressionValue,
251
+ defaultQualifier,
252
+ ambiguousColumns
253
+ ) || transformed;
254
+ transformed =
255
+ qualifyAmbiguousInExpression(
256
+ binary.right as ExpressionValue,
257
+ defaultQualifier,
258
+ ambiguousColumns
259
+ ) || transformed;
260
+ return transformed;
261
+ }
262
+
263
+ if ('args' in expr && expr.args) {
264
+ const args = expr.args as {
265
+ value?: ExpressionValue[];
266
+ expr?: ExpressionValue;
267
+ };
268
+ if (args.value && Array.isArray(args.value)) {
269
+ for (const arg of args.value) {
270
+ transformed =
271
+ qualifyAmbiguousInExpression(
272
+ arg,
273
+ defaultQualifier,
274
+ ambiguousColumns
275
+ ) || transformed;
276
+ }
277
+ }
278
+ if (args.expr) {
279
+ transformed =
280
+ qualifyAmbiguousInExpression(
281
+ args.expr,
282
+ defaultQualifier,
283
+ ambiguousColumns
284
+ ) || transformed;
285
+ }
286
+ }
287
+
288
+ if ('over' in expr && expr.over && typeof expr.over === 'object') {
289
+ const over = expr.over as {
290
+ partition?: ExpressionValue[];
291
+ orderby?: ExpressionValue[];
292
+ };
293
+ if (Array.isArray(over.partition)) {
294
+ for (const part of over.partition) {
295
+ transformed =
296
+ qualifyAmbiguousInExpression(
297
+ part,
298
+ defaultQualifier,
299
+ ambiguousColumns
300
+ ) || transformed;
301
+ }
302
+ }
303
+ if (Array.isArray(over.orderby)) {
304
+ for (const order of over.orderby) {
305
+ transformed =
306
+ qualifyAmbiguousInExpression(
307
+ order,
308
+ defaultQualifier,
309
+ ambiguousColumns
310
+ ) || transformed;
311
+ }
312
+ }
313
+ }
314
+
315
+ return transformed;
316
+ }
317
+
318
+ /**
319
+ * Quick check if an ON clause has any unqualified column references.
320
+ * Used for early exit optimization.
321
+ */
322
+ function hasUnqualifiedColumns(expr: Binary | null | undefined): boolean {
323
+ if (!expr || typeof expr !== 'object') return false;
324
+
325
+ if ('type' in expr && expr.type === 'binary_expr') {
326
+ const left = expr.left as ExpressionValue;
327
+ const right = expr.right as ExpressionValue;
328
+ const leftCol = unwrapColumnRef(left);
329
+ const rightCol = unwrapColumnRef(right);
330
+ if (
331
+ isUnqualifiedColumnRef(left) ||
332
+ isUnqualifiedColumnRef(right) ||
333
+ (leftCol && isUnqualifiedColumnRef(leftCol)) ||
334
+ (rightCol && isUnqualifiedColumnRef(rightCol))
335
+ ) {
336
+ return true;
337
+ }
338
+ if (
339
+ isBinaryExpr(expr.left as Binary) &&
340
+ hasUnqualifiedColumns(expr.left as Binary)
341
+ )
342
+ return true;
343
+ if (
344
+ isBinaryExpr(expr.right as Binary) &&
345
+ hasUnqualifiedColumns(expr.right as Binary)
346
+ )
347
+ return true;
348
+ }
349
+
350
+ if ('args' in expr && expr.args) {
351
+ const args = expr.args as {
352
+ value?: ExpressionValue[];
353
+ expr?: ExpressionValue;
354
+ };
355
+ if (args.expr && isUnqualifiedColumnRef(args.expr as ExpressionValue))
356
+ return true;
357
+ if (args.value) {
358
+ for (const arg of args.value) {
359
+ if (isUnqualifiedColumnRef(arg)) return true;
360
+ }
361
+ }
362
+ }
363
+
364
+ return false;
365
+ }
366
+
367
+ function walkSelect(select: Select): boolean {
368
+ let transformed = false;
369
+ const ambiguousColumns = new Set<string>();
370
+
371
+ if (Array.isArray(select.from) && select.from.length >= 2) {
372
+ const firstSource = getTableSource(select.from[0]);
373
+ const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
374
+ let prevSource = firstSource;
375
+
376
+ let hasAnyUnqualified = false;
377
+ for (const from of select.from) {
378
+ if ('join' in from) {
379
+ const join = from as Join;
380
+ if (join.on && hasUnqualifiedColumns(join.on)) {
381
+ hasAnyUnqualified = true;
382
+ break;
383
+ }
384
+ }
385
+ }
386
+
387
+ if (!hasAnyUnqualified) {
388
+ for (const from of select.from) {
389
+ if ('expr' in from && from.expr && 'ast' in from.expr) {
390
+ transformed = walkSelect(from.expr.ast) || transformed;
391
+ }
392
+ }
393
+ } else {
394
+ for (const from of select.from) {
395
+ if ('join' in from) {
396
+ const join = from as Join;
397
+ const currentSource = getTableSource(join);
398
+
399
+ if (join.on && prevSource && currentSource) {
400
+ const leftQualifier = getQualifier(prevSource);
401
+ const rightQualifier = getQualifier(currentSource);
402
+
403
+ transformed =
404
+ walkOnClause(
405
+ join.on,
406
+ leftQualifier,
407
+ rightQualifier,
408
+ ambiguousColumns
409
+ ) || transformed;
410
+ }
411
+
412
+ if (join.using && prevSource && currentSource) {
413
+ for (const usingCol of join.using) {
414
+ if (typeof usingCol === 'string') {
415
+ ambiguousColumns.add(usingCol);
416
+ } else if ('value' in usingCol) {
417
+ ambiguousColumns.add(
418
+ String((usingCol as { value: unknown }).value)
419
+ );
420
+ }
421
+ }
422
+ }
423
+
424
+ prevSource = currentSource;
425
+ } else {
426
+ const source = getTableSource(from);
427
+ if (source) {
428
+ prevSource = source;
429
+ }
430
+ }
431
+
432
+ if ('expr' in from && from.expr && 'ast' in from.expr) {
433
+ transformed = walkSelect(from.expr.ast) || transformed;
434
+ }
435
+ }
436
+
437
+ if (ambiguousColumns.size > 0 && defaultQualifier) {
438
+ if (Array.isArray(select.columns)) {
439
+ for (const col of select.columns as Column[]) {
440
+ if ('expr' in col) {
441
+ transformed =
442
+ qualifyAmbiguousInExpression(
443
+ col.expr,
444
+ defaultQualifier,
445
+ ambiguousColumns
446
+ ) || transformed;
447
+ }
448
+ }
449
+ }
450
+
451
+ transformed =
452
+ qualifyAmbiguousInExpression(
453
+ select.where,
454
+ defaultQualifier,
455
+ ambiguousColumns
456
+ ) || transformed;
457
+
458
+ if (Array.isArray(select.orderby)) {
459
+ for (const order of select.orderby as OrderBy[]) {
460
+ if (order.expr) {
461
+ transformed =
462
+ qualifyAmbiguousInExpression(
463
+ order.expr,
464
+ defaultQualifier,
465
+ ambiguousColumns
466
+ ) || transformed;
467
+ }
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ if (select.with) {
475
+ for (const cte of select.with) {
476
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
477
+ if (cteSelect && cteSelect.type === 'select') {
478
+ transformed = walkSelect(cteSelect as Select) || transformed;
479
+ }
480
+ }
481
+ }
482
+
483
+ if (select._next) {
484
+ transformed = walkSelect(select._next) || transformed;
485
+ }
486
+
487
+ return transformed;
488
+ }
489
+
490
+ export function qualifyJoinColumns(ast: AST | AST[]): boolean {
491
+ const statements = Array.isArray(ast) ? ast : [ast];
492
+ let transformed = false;
493
+
494
+ for (const stmt of statements) {
495
+ if (stmt.type === 'select') {
496
+ transformed = walkSelect(stmt as Select) || transformed;
497
+ } else if (stmt.type === 'insert') {
498
+ const insert = stmt as unknown as { values?: unknown };
499
+ if (
500
+ insert.values &&
501
+ typeof insert.values === 'object' &&
502
+ 'type' in insert.values &&
503
+ (insert.values as { type: string }).type === 'select'
504
+ ) {
505
+ transformed =
506
+ walkSelect(insert.values as unknown as Select) || transformed;
507
+ }
508
+ } else if (stmt.type === 'update') {
509
+ const update = stmt as unknown as {
510
+ table?: From[];
511
+ from?: From[];
512
+ where?: ExpressionValue;
513
+ returning?: ExpressionValue | ExpressionValue[];
514
+ };
515
+ const mainSource = update.table?.[0]
516
+ ? getTableSource(update.table[0] as From)
517
+ : null;
518
+ const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
519
+ const fromSources = update.from ?? [];
520
+ const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
521
+ if (update.where && defaultQualifier && firstFrom) {
522
+ const ambiguous = new Set<string>();
523
+ transformed =
524
+ walkOnClause(
525
+ update.where as Binary,
526
+ defaultQualifier,
527
+ getQualifier(firstFrom),
528
+ ambiguous
529
+ ) || transformed;
530
+ transformed =
531
+ qualifyAmbiguousInExpression(
532
+ update.where,
533
+ defaultQualifier,
534
+ ambiguous
535
+ ) || transformed;
536
+ }
537
+ if (Array.isArray(update.returning) && defaultQualifier) {
538
+ for (const ret of update.returning) {
539
+ transformed =
540
+ qualifyAmbiguousInExpression(
541
+ ret,
542
+ defaultQualifier,
543
+ new Set<string>()
544
+ ) || transformed;
545
+ }
546
+ }
547
+ } else if (stmt.type === 'delete') {
548
+ const del = stmt as unknown as {
549
+ table?: From[];
550
+ from?: From[];
551
+ where?: ExpressionValue;
552
+ };
553
+ const mainSource = del.table?.[0]
554
+ ? getTableSource(del.table[0] as From)
555
+ : null;
556
+ const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
557
+ const fromSources = del.from ?? [];
558
+ const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
559
+ if (del.where && defaultQualifier && firstFrom) {
560
+ const ambiguous = new Set<string>();
561
+ transformed =
562
+ walkOnClause(
563
+ del.where as Binary,
564
+ defaultQualifier,
565
+ getQualifier(firstFrom),
566
+ ambiguous
567
+ ) || transformed;
568
+ transformed =
569
+ qualifyAmbiguousInExpression(
570
+ del.where,
571
+ defaultQualifier,
572
+ ambiguous
573
+ ) || transformed;
574
+ } else if (del.where && defaultQualifier) {
575
+ transformed =
576
+ qualifyAmbiguousInExpression(
577
+ del.where,
578
+ defaultQualifier,
579
+ new Set<string>()
580
+ ) || transformed;
581
+ }
582
+ }
583
+ }
584
+
585
+ return transformed;
586
+ }