@leonardovida-md/drizzle-neo-duckdb 1.1.0 → 1.1.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.
@@ -24,83 +24,162 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
24
24
  import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
25
25
 
26
26
  // src/sql/query-rewriters.ts
27
- function adaptArrayOperators(query) {
28
- const operators = [
29
- { token: "@>", fn: "array_has_all" },
30
- { token: "<@", fn: "array_has_all", swap: true },
31
- { token: "&&", fn: "array_has_any" }
32
- ];
33
- const isWhitespace = (char) => char !== undefined && /\s/.test(char);
34
- const walkLeft = (source, start) => {
35
- let idx = start;
36
- while (idx >= 0 && isWhitespace(source[idx])) {
37
- idx--;
38
- }
39
- let depth = 0;
40
- let inString = false;
41
- for (;idx >= 0; idx--) {
42
- const ch = source[idx];
43
- if (ch === undefined)
44
- break;
45
- if (ch === "'" && source[idx - 1] !== "\\") {
46
- inString = !inString;
27
+ var OPERATORS = [
28
+ { token: "@>", fn: "array_has_all" },
29
+ { token: "<@", fn: "array_has_all", swap: true },
30
+ { token: "&&", fn: "array_has_any" }
31
+ ];
32
+ var isWhitespace = (char) => char !== undefined && /\s/.test(char);
33
+ function scrubForRewrite(query) {
34
+ let scrubbed = "";
35
+ let state = "code";
36
+ for (let i = 0;i < query.length; i += 1) {
37
+ const char = query[i];
38
+ const next = query[i + 1];
39
+ if (state === "code") {
40
+ if (char === "'") {
41
+ scrubbed += "'";
42
+ state = "single";
43
+ continue;
47
44
  }
48
- if (inString)
45
+ if (char === '"') {
46
+ scrubbed += '"';
47
+ state = "double";
48
+ continue;
49
+ }
50
+ if (char === "-" && next === "-") {
51
+ scrubbed += " ";
52
+ i += 1;
53
+ state = "lineComment";
49
54
  continue;
50
- if (ch === ")" || ch === "]") {
51
- depth++;
52
- } else if (ch === "(" || ch === "[") {
53
- depth--;
54
- if (depth < 0) {
55
- return [idx + 1, source.slice(idx + 1, start + 1)];
56
- }
57
- } else if (depth === 0 && isWhitespace(ch)) {
58
- return [idx + 1, source.slice(idx + 1, start + 1)];
59
55
  }
56
+ if (char === "/" && next === "*") {
57
+ scrubbed += " ";
58
+ i += 1;
59
+ state = "blockComment";
60
+ continue;
61
+ }
62
+ scrubbed += char;
63
+ continue;
60
64
  }
61
- return [0, source.slice(0, start + 1)];
62
- };
63
- const walkRight = (source, start) => {
64
- let idx = start;
65
- while (idx < source.length && isWhitespace(source[idx])) {
66
- idx++;
67
- }
68
- let depth = 0;
69
- let inString = false;
70
- for (;idx < source.length; idx++) {
71
- const ch = source[idx];
72
- if (ch === undefined)
73
- break;
74
- if (ch === "'" && source[idx - 1] !== "\\") {
75
- inString = !inString;
65
+ if (state === "single") {
66
+ if (char === "'" && next === "'") {
67
+ scrubbed += "''";
68
+ i += 1;
69
+ continue;
76
70
  }
77
- if (inString)
71
+ scrubbed += char === "'" ? "'" : ".";
72
+ if (char === "'") {
73
+ state = "code";
74
+ }
75
+ continue;
76
+ }
77
+ if (state === "double") {
78
+ if (char === '"' && next === '"') {
79
+ scrubbed += '""';
80
+ i += 1;
78
81
  continue;
79
- if (ch === "(" || ch === "[") {
80
- depth++;
81
- } else if (ch === ")" || ch === "]") {
82
- depth--;
83
- if (depth < 0) {
84
- return [idx, source.slice(start, idx)];
85
- }
86
- } else if (depth === 0 && isWhitespace(ch)) {
82
+ }
83
+ scrubbed += char === '"' ? '"' : ".";
84
+ if (char === '"') {
85
+ state = "code";
86
+ }
87
+ continue;
88
+ }
89
+ if (state === "lineComment") {
90
+ scrubbed += char === `
91
+ ` ? `
92
+ ` : " ";
93
+ if (char === `
94
+ `) {
95
+ state = "code";
96
+ }
97
+ continue;
98
+ }
99
+ if (state === "blockComment") {
100
+ if (char === "*" && next === "/") {
101
+ scrubbed += " ";
102
+ i += 1;
103
+ state = "code";
104
+ } else {
105
+ scrubbed += " ";
106
+ }
107
+ }
108
+ }
109
+ return scrubbed;
110
+ }
111
+ function findNextOperator(scrubbed, start) {
112
+ for (let idx = start;idx < scrubbed.length; idx += 1) {
113
+ for (const operator of OPERATORS) {
114
+ if (scrubbed.startsWith(operator.token, idx)) {
115
+ return { index: idx, operator };
116
+ }
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+ function walkLeft(source, scrubbed, start) {
122
+ let idx = start;
123
+ while (idx >= 0 && isWhitespace(scrubbed[idx])) {
124
+ idx -= 1;
125
+ }
126
+ let depth = 0;
127
+ for (;idx >= 0; idx -= 1) {
128
+ const ch = scrubbed[idx];
129
+ if (ch === ")" || ch === "]") {
130
+ depth += 1;
131
+ } else if (ch === "(" || ch === "[") {
132
+ if (depth === 0) {
133
+ return [idx + 1, source.slice(idx + 1, start + 1)];
134
+ }
135
+ depth = Math.max(0, depth - 1);
136
+ } else if (depth === 0 && isWhitespace(ch)) {
137
+ return [idx + 1, source.slice(idx + 1, start + 1)];
138
+ }
139
+ }
140
+ return [0, source.slice(0, start + 1)];
141
+ }
142
+ function walkRight(source, scrubbed, start) {
143
+ let idx = start;
144
+ while (idx < scrubbed.length && isWhitespace(scrubbed[idx])) {
145
+ idx += 1;
146
+ }
147
+ let depth = 0;
148
+ for (;idx < scrubbed.length; idx += 1) {
149
+ const ch = scrubbed[idx];
150
+ if (ch === "(" || ch === "[") {
151
+ depth += 1;
152
+ } else if (ch === ")" || ch === "]") {
153
+ if (depth === 0) {
87
154
  return [idx, source.slice(start, idx)];
88
155
  }
156
+ depth = Math.max(0, depth - 1);
157
+ } else if (depth === 0 && isWhitespace(ch)) {
158
+ return [idx, source.slice(start, idx)];
89
159
  }
90
- return [source.length, source.slice(start)];
91
- };
160
+ }
161
+ return [scrubbed.length, source.slice(start)];
162
+ }
163
+ function adaptArrayOperators(query) {
164
+ if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
165
+ return query;
166
+ }
92
167
  let rewritten = query;
93
- for (const { token, fn, swap } of operators) {
94
- let idx = rewritten.indexOf(token);
95
- while (idx !== -1) {
96
- const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
97
- const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
98
- const left = leftExpr.trim();
99
- const right = rightExpr.trim();
100
- const replacement = `${fn}(${swap ? right : left}, ${swap ? left : right})`;
101
- rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
102
- idx = rewritten.indexOf(token, leftStart + replacement.length);
103
- }
168
+ let scrubbed = scrubForRewrite(query);
169
+ let searchStart = 0;
170
+ while (true) {
171
+ const next = findNextOperator(scrubbed, searchStart);
172
+ if (!next)
173
+ break;
174
+ const { index, operator } = next;
175
+ const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
176
+ const [rightEnd, rightExpr] = walkRight(rewritten, scrubbed, index + operator.token.length);
177
+ const left = leftExpr.trim();
178
+ const right = rightExpr.trim();
179
+ const replacement = `${operator.fn}(${operator.swap ? right : left}, ${operator.swap ? left : right})`;
180
+ rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
181
+ scrubbed = scrubForRewrite(rewritten);
182
+ searchStart = leftStart + replacement.length;
104
183
  }
105
184
  return rewritten;
106
185
  }
@@ -335,6 +414,12 @@ function dateToMicros(value) {
335
414
  if (value instanceof Date) {
336
415
  return BigInt(value.getTime()) * 1000n;
337
416
  }
417
+ if (typeof value === "bigint") {
418
+ return value;
419
+ }
420
+ if (typeof value === "number") {
421
+ return BigInt(Math.trunc(value)) * 1000n;
422
+ }
338
423
  let normalized = value;
339
424
  if (!value.includes("T") && value.includes(" ")) {
340
425
  normalized = value.replace(" ", "T");
@@ -391,6 +476,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
391
476
  function isPool(client) {
392
477
  return typeof client.acquire === "function";
393
478
  }
479
+ var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
394
480
  function isPgArrayLiteral(value) {
395
481
  return value.startsWith("{") && value.endsWith("}");
396
482
  }
@@ -404,14 +490,21 @@ function parsePgArrayLiteral(value) {
404
490
  }
405
491
  function prepareParams(params, options = {}) {
406
492
  return params.map((param) => {
407
- if (typeof param === "string" && isPgArrayLiteral(param)) {
408
- if (options.rejectStringArrayLiterals) {
409
- throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
410
- }
411
- if (options.warnOnStringArrayLiteral) {
412
- options.warnOnStringArrayLiteral();
493
+ if (typeof param === "string" && param.length > 0) {
494
+ const firstChar = param[0];
495
+ const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
496
+ if (maybeArrayLiteral) {
497
+ const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
498
+ if (trimmed && isPgArrayLiteral(trimmed)) {
499
+ if (options.rejectStringArrayLiterals) {
500
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
501
+ }
502
+ if (options.warnOnStringArrayLiteral) {
503
+ options.warnOnStringArrayLiteral();
504
+ }
505
+ return parsePgArrayLiteral(trimmed);
506
+ }
413
507
  }
414
- return parsePgArrayLiteral(param);
415
508
  }
416
509
  return param;
417
510
  });
@@ -435,13 +528,121 @@ function toNodeApiValue(value) {
435
528
  return value;
436
529
  }
437
530
  function deduplicateColumns(columns) {
438
- const seen = {};
439
- return columns.map((col) => {
440
- const count = seen[col] ?? 0;
441
- seen[col] = count + 1;
442
- return count === 0 ? col : `${col}_${count}`;
531
+ const counts = new Map;
532
+ let hasDuplicates = false;
533
+ for (const column of columns) {
534
+ const next = (counts.get(column) ?? 0) + 1;
535
+ counts.set(column, next);
536
+ if (next > 1) {
537
+ hasDuplicates = true;
538
+ break;
539
+ }
540
+ }
541
+ if (!hasDuplicates) {
542
+ return columns;
543
+ }
544
+ counts.clear();
545
+ return columns.map((column) => {
546
+ const count = counts.get(column) ?? 0;
547
+ counts.set(column, count + 1);
548
+ return count === 0 ? column : `${column}_${count}`;
443
549
  });
444
550
  }
551
+ function destroyPreparedStatement(entry) {
552
+ if (!entry)
553
+ return;
554
+ try {
555
+ entry.statement.destroySync();
556
+ } catch {}
557
+ }
558
+ function getPreparedCache(connection, size) {
559
+ const store = connection;
560
+ const existing = store[PREPARED_CACHE];
561
+ if (existing) {
562
+ existing.size = size;
563
+ return existing;
564
+ }
565
+ const cache = { size, entries: new Map };
566
+ store[PREPARED_CACHE] = cache;
567
+ return cache;
568
+ }
569
+ function evictOldest(cache) {
570
+ const oldest = cache.entries.keys().next();
571
+ if (!oldest.done) {
572
+ const key = oldest.value;
573
+ const entry = cache.entries.get(key);
574
+ cache.entries.delete(key);
575
+ destroyPreparedStatement(entry);
576
+ }
577
+ }
578
+ function evictCacheEntry(cache, key) {
579
+ const entry = cache.entries.get(key);
580
+ cache.entries.delete(key);
581
+ destroyPreparedStatement(entry);
582
+ }
583
+ async function getOrPrepareStatement(connection, query, cacheConfig) {
584
+ const cache = getPreparedCache(connection, cacheConfig.size);
585
+ const cached = cache.entries.get(query);
586
+ if (cached) {
587
+ cache.entries.delete(query);
588
+ cache.entries.set(query, cached);
589
+ return cached.statement;
590
+ }
591
+ const statement = await connection.prepare(query);
592
+ cache.entries.set(query, { statement });
593
+ while (cache.entries.size > cache.size) {
594
+ evictOldest(cache);
595
+ }
596
+ return statement;
597
+ }
598
+ async function materializeResultRows(result) {
599
+ const rows = await result.getRowsJS() ?? [];
600
+ const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
601
+ const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
602
+ return { columns, rows };
603
+ }
604
+ async function materializeRows(client, query, params, options = {}) {
605
+ if (isPool(client)) {
606
+ const connection2 = await client.acquire();
607
+ try {
608
+ return await materializeRows(connection2, query, params, options);
609
+ } finally {
610
+ await client.release(connection2);
611
+ }
612
+ }
613
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
614
+ const connection = client;
615
+ if (options.prepareCache && typeof connection.prepare === "function") {
616
+ const cache = getPreparedCache(connection, options.prepareCache.size);
617
+ try {
618
+ const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
619
+ if (values) {
620
+ statement.bind(values);
621
+ } else {
622
+ statement.clearBindings?.();
623
+ }
624
+ const result2 = await statement.run();
625
+ cache.entries.delete(query);
626
+ cache.entries.set(query, { statement });
627
+ return await materializeResultRows(result2);
628
+ } catch (error) {
629
+ evictCacheEntry(cache, query);
630
+ throw error;
631
+ }
632
+ }
633
+ const result = await connection.run(query, values);
634
+ return await materializeResultRows(result);
635
+ }
636
+ function clearPreparedCache(connection) {
637
+ const store = connection;
638
+ const cache = store[PREPARED_CACHE];
639
+ if (!cache)
640
+ return;
641
+ for (const entry of cache.entries.values()) {
642
+ destroyPreparedStatement(entry);
643
+ }
644
+ cache.entries.clear();
645
+ }
445
646
  function mapRowsToObjects(columns, rows) {
446
647
  return rows.map((vals) => {
447
648
  const obj = {};
@@ -452,6 +653,7 @@ function mapRowsToObjects(columns, rows) {
452
653
  });
453
654
  }
454
655
  async function closeClientConnection(connection) {
656
+ clearPreparedCache(connection);
455
657
  if ("close" in connection && typeof connection.close === "function") {
456
658
  await connection.close();
457
659
  return;
@@ -464,27 +666,51 @@ async function closeClientConnection(connection) {
464
666
  connection.disconnectSync();
465
667
  }
466
668
  }
467
- async function executeOnClient(client, query, params) {
669
+ async function executeOnClient(client, query, params, options = {}) {
670
+ const { columns, rows } = await materializeRows(client, query, params, options);
671
+ if (!rows || rows.length === 0) {
672
+ return [];
673
+ }
674
+ return mapRowsToObjects(columns, rows);
675
+ }
676
+ async function executeArraysOnClient(client, query, params, options = {}) {
677
+ return await materializeRows(client, query, params, options);
678
+ }
679
+ async function* executeInBatches(client, query, params, options = {}) {
468
680
  if (isPool(client)) {
469
681
  const connection = await client.acquire();
470
682
  try {
471
- return await executeOnClient(connection, query, params);
683
+ yield* executeInBatches(connection, query, params, options);
684
+ return;
472
685
  } finally {
473
686
  await client.release(connection);
474
687
  }
475
688
  }
689
+ const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
476
690
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
477
- const result = await client.run(query, values);
478
- const rows = await result.getRowsJS();
479
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
480
- const uniqueColumns = deduplicateColumns(columns);
481
- return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
691
+ const result = await client.stream(query, values);
692
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
693
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
694
+ let buffer = [];
695
+ for await (const chunk of result.yieldRowsJs()) {
696
+ const objects = mapRowsToObjects(columns, chunk);
697
+ for (const row of objects) {
698
+ buffer.push(row);
699
+ if (buffer.length >= rowsPerChunk) {
700
+ yield buffer;
701
+ buffer = [];
702
+ }
703
+ }
704
+ }
705
+ if (buffer.length > 0) {
706
+ yield buffer;
707
+ }
482
708
  }
483
- async function* executeInBatches(client, query, params, options = {}) {
709
+ async function* executeInBatchesRaw(client, query, params, options = {}) {
484
710
  if (isPool(client)) {
485
711
  const connection = await client.acquire();
486
712
  try {
487
- yield* executeInBatches(connection, query, params, options);
713
+ yield* executeInBatchesRaw(connection, query, params, options);
488
714
  return;
489
715
  } finally {
490
716
  await client.release(connection);
@@ -493,21 +719,20 @@ async function* executeInBatches(client, query, params, options = {}) {
493
719
  const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
494
720
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
495
721
  const result = await client.stream(query, values);
496
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
497
- const uniqueColumns = deduplicateColumns(columns);
722
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
723
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
498
724
  let buffer = [];
499
725
  for await (const chunk of result.yieldRowsJs()) {
500
- const objects = mapRowsToObjects(uniqueColumns, chunk);
501
- for (const row of objects) {
726
+ for (const row of chunk) {
502
727
  buffer.push(row);
503
728
  if (buffer.length >= rowsPerChunk) {
504
- yield buffer;
729
+ yield { columns, rows: buffer };
505
730
  buffer = [];
506
731
  }
507
732
  }
508
733
  }
509
734
  if (buffer.length > 0) {
510
- yield buffer;
735
+ yield { columns, rows: buffer };
511
736
  }
512
737
  }
513
738
  async function executeArrowOnClient(client, query, params) {
@@ -529,6 +754,20 @@ async function executeArrowOnClient(client, query, params) {
529
754
  }
530
755
 
531
756
  // src/session.ts
757
+ function isSavepointSyntaxError(error) {
758
+ if (!(error instanceof Error) || !error.message) {
759
+ return false;
760
+ }
761
+ return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
762
+ }
763
+ function rewriteQuery(mode, query) {
764
+ if (mode === "never") {
765
+ return { sql: query, rewritten: false };
766
+ }
767
+ const rewritten = adaptArrayOperators(query);
768
+ return { sql: rewritten, rewritten: rewritten !== query };
769
+ }
770
+
532
771
  class DuckDBPreparedQuery extends PgPreparedQuery {
533
772
  client;
534
773
  dialect;
@@ -538,11 +777,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
538
777
  fields;
539
778
  _isResponseInArrayMode;
540
779
  customResultMapper;
541
- rewriteArrays;
780
+ rewriteArraysMode;
542
781
  rejectStringArrayLiterals;
782
+ prepareCache;
543
783
  warnOnStringArrayLiteral;
544
784
  static [entityKind] = "DuckDBPreparedQuery";
545
- constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArrays, rejectStringArrayLiterals, warnOnStringArrayLiteral) {
785
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
546
786
  super({ sql: queryString, params });
547
787
  this.client = client;
548
788
  this.dialect = dialect;
@@ -552,8 +792,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
552
792
  this.fields = fields;
553
793
  this._isResponseInArrayMode = _isResponseInArrayMode;
554
794
  this.customResultMapper = customResultMapper;
555
- this.rewriteArrays = rewriteArrays;
795
+ this.rewriteArraysMode = rewriteArraysMode;
556
796
  this.rejectStringArrayLiterals = rejectStringArrayLiterals;
797
+ this.prepareCache = prepareCache;
557
798
  this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
558
799
  }
559
800
  async execute(placeholderValues = {}) {
@@ -562,18 +803,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
562
803
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
563
804
  warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
564
805
  });
565
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(this.queryString) : this.queryString;
566
- if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
806
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
807
+ if (didRewrite) {
567
808
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
568
809
  }
569
810
  this.logger.logQuery(rewrittenQuery, params);
570
811
  const { fields, joinsNotNullableMap, customResultMapper } = this;
571
- const rows = await executeOnClient(this.client, rewrittenQuery, params);
572
- if (rows.length === 0 || !fields) {
573
- return rows;
812
+ if (fields) {
813
+ const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
814
+ if (rows2.length === 0) {
815
+ return [];
816
+ }
817
+ return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
574
818
  }
575
- const rowValues = rows.map((row) => Object.values(row));
576
- return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
819
+ const rows = await executeOnClient(this.client, rewrittenQuery, params, {
820
+ prepareCache: this.prepareCache
821
+ });
822
+ return rows;
577
823
  }
578
824
  all(placeholderValues = {}) {
579
825
  return this.execute(placeholderValues);
@@ -590,9 +836,11 @@ class DuckDBSession extends PgSession {
590
836
  static [entityKind] = "DuckDBSession";
591
837
  dialect;
592
838
  logger;
593
- rewriteArrays;
839
+ rewriteArraysMode;
594
840
  rejectStringArrayLiterals;
841
+ prepareCache;
595
842
  hasWarnedArrayLiteral = false;
843
+ rollbackOnly = false;
596
844
  constructor(client, dialect, schema, options = {}) {
597
845
  super(dialect);
598
846
  this.client = client;
@@ -600,13 +848,27 @@ class DuckDBSession extends PgSession {
600
848
  this.options = options;
601
849
  this.dialect = dialect;
602
850
  this.logger = options.logger ?? new NoopLogger;
603
- this.rewriteArrays = options.rewriteArrays ?? true;
851
+ this.rewriteArraysMode = options.rewriteArrays ?? "auto";
604
852
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
853
+ this.prepareCache = options.prepareCache;
854
+ this.options = {
855
+ ...options,
856
+ rewriteArrays: this.rewriteArraysMode,
857
+ prepareCache: this.prepareCache
858
+ };
605
859
  }
606
860
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
607
- return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rewriteArrays, this.rejectStringArrayLiterals, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
861
+ return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rewriteArraysMode, this.rejectStringArrayLiterals, this.prepareCache, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
608
862
  }
609
- async transaction(transaction) {
863
+ execute(query) {
864
+ this.dialect.resetPgJsonFlag();
865
+ return super.execute(query);
866
+ }
867
+ all(query) {
868
+ this.dialect.resetPgJsonFlag();
869
+ return super.all(query);
870
+ }
871
+ async transaction(transaction, config) {
610
872
  let pinnedConnection;
611
873
  let pool;
612
874
  let clientForTx = this.client;
@@ -619,8 +881,15 @@ class DuckDBSession extends PgSession {
619
881
  const tx = new DuckDBTransaction(this.dialect, session, this.schema);
620
882
  try {
621
883
  await tx.execute(sql`BEGIN TRANSACTION;`);
884
+ if (config) {
885
+ await tx.setTransaction(config);
886
+ }
622
887
  try {
623
888
  const result = await transaction(tx);
889
+ if (session.isRollbackOnly()) {
890
+ await tx.execute(sql`rollback`);
891
+ throw new TransactionRollbackError;
892
+ }
624
893
  await tx.execute(sql`commit`);
625
894
  return result;
626
895
  } catch (error) {
@@ -642,31 +911,56 @@ class DuckDBSession extends PgSession {
642
911
  query: ${query}`, []);
643
912
  };
644
913
  executeBatches(query, options = {}) {
914
+ this.dialect.resetPgJsonFlag();
645
915
  const builtQuery = this.dialect.sqlToQuery(query);
916
+ this.dialect.assertNoPgJsonColumns();
646
917
  const params = prepareParams(builtQuery.params, {
647
918
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
648
919
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
649
920
  });
650
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
651
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
921
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
922
+ if (didRewrite) {
652
923
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
653
924
  }
654
925
  this.logger.logQuery(rewrittenQuery, params);
655
926
  return executeInBatches(this.client, rewrittenQuery, params, options);
656
927
  }
928
+ executeBatchesRaw(query, options = {}) {
929
+ this.dialect.resetPgJsonFlag();
930
+ const builtQuery = this.dialect.sqlToQuery(query);
931
+ this.dialect.assertNoPgJsonColumns();
932
+ const params = prepareParams(builtQuery.params, {
933
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
934
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
935
+ });
936
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
937
+ if (didRewrite) {
938
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
939
+ }
940
+ this.logger.logQuery(rewrittenQuery, params);
941
+ return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
942
+ }
657
943
  async executeArrow(query) {
944
+ this.dialect.resetPgJsonFlag();
658
945
  const builtQuery = this.dialect.sqlToQuery(query);
946
+ this.dialect.assertNoPgJsonColumns();
659
947
  const params = prepareParams(builtQuery.params, {
660
948
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
661
949
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
662
950
  });
663
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
664
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
951
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
952
+ if (didRewrite) {
665
953
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
666
954
  }
667
955
  this.logger.logQuery(rewrittenQuery, params);
668
956
  return executeArrowOnClient(this.client, rewrittenQuery, params);
669
957
  }
958
+ markRollbackOnly() {
959
+ this.rollbackOnly = true;
960
+ }
961
+ isRollbackOnly() {
962
+ return this.rollbackOnly;
963
+ }
670
964
  }
671
965
 
672
966
  class DuckDBTransaction extends PgTransaction {
@@ -693,12 +987,53 @@ class DuckDBTransaction extends PgTransaction {
693
987
  executeBatches(query, options = {}) {
694
988
  return this.session.executeBatches(query, options);
695
989
  }
990
+ executeBatchesRaw(query, options = {}) {
991
+ return this.session.executeBatchesRaw(query, options);
992
+ }
696
993
  executeArrow(query) {
697
994
  return this.session.executeArrow(query);
698
995
  }
699
996
  async transaction(transaction) {
700
- const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
701
- return transaction(nestedTx);
997
+ const internals = this;
998
+ const savepoint = `drizzle_savepoint_${this.nestedIndex + 1}`;
999
+ const savepointSql = sql.raw(`savepoint ${savepoint}`);
1000
+ const releaseSql = sql.raw(`release savepoint ${savepoint}`);
1001
+ const rollbackSql = sql.raw(`rollback to savepoint ${savepoint}`);
1002
+ const nestedTx = new DuckDBTransaction(internals.dialect, internals.session, this.schema, this.nestedIndex + 1);
1003
+ if (internals.dialect.areSavepointsUnsupported()) {
1004
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
1005
+ }
1006
+ let createdSavepoint = false;
1007
+ try {
1008
+ await internals.session.execute(savepointSql);
1009
+ internals.dialect.markSavepointsSupported();
1010
+ createdSavepoint = true;
1011
+ } catch (error) {
1012
+ if (!isSavepointSyntaxError(error)) {
1013
+ throw error;
1014
+ }
1015
+ internals.dialect.markSavepointsUnsupported();
1016
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
1017
+ }
1018
+ try {
1019
+ const result = await transaction(nestedTx);
1020
+ if (createdSavepoint) {
1021
+ await internals.session.execute(releaseSql);
1022
+ }
1023
+ return result;
1024
+ } catch (error) {
1025
+ if (createdSavepoint) {
1026
+ await internals.session.execute(rollbackSql);
1027
+ }
1028
+ internals.session.markRollbackOnly();
1029
+ throw error;
1030
+ }
1031
+ }
1032
+ runNestedWithoutSavepoint(transaction, nestedTx, internals) {
1033
+ return transaction(nestedTx).catch((error) => {
1034
+ internals.session.markRollbackOnly();
1035
+ throw error;
1036
+ });
702
1037
  }
703
1038
  }
704
1039
  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.";
@@ -720,15 +1055,30 @@ import {
720
1055
  import {
721
1056
  sql as sql2
722
1057
  } from "drizzle-orm";
723
-
724
1058
  class DuckDBDialect extends PgDialect {
725
1059
  static [entityKind2] = "DuckDBPgDialect";
726
1060
  hasPgJsonColumn = false;
1061
+ savepointsSupported = 0 /* Unknown */;
1062
+ resetPgJsonFlag() {
1063
+ this.hasPgJsonColumn = false;
1064
+ }
1065
+ markPgJsonDetected() {
1066
+ this.hasPgJsonColumn = true;
1067
+ }
727
1068
  assertNoPgJsonColumns() {
728
1069
  if (this.hasPgJsonColumn) {
729
- throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDBs native JSON type.");
1070
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
730
1071
  }
731
1072
  }
1073
+ areSavepointsUnsupported() {
1074
+ return this.savepointsSupported === 2 /* No */;
1075
+ }
1076
+ markSavepointsSupported() {
1077
+ this.savepointsSupported = 1 /* Yes */;
1078
+ }
1079
+ markSavepointsUnsupported() {
1080
+ this.savepointsSupported = 2 /* No */;
1081
+ }
732
1082
  async migrate(migrations, session, config) {
733
1083
  const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
734
1084
  const migrationsSchema = migrationConfig.migrationsSchema ?? "drizzle";
@@ -765,8 +1115,8 @@ class DuckDBDialect extends PgDialect {
765
1115
  }
766
1116
  prepareTyping(encoder) {
767
1117
  if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
768
- this.hasPgJsonColumn = true;
769
- return "none";
1118
+ this.markPgJsonDetected();
1119
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
770
1120
  } else if (is2(encoder, PgNumeric)) {
771
1121
  return "decimal";
772
1122
  } else if (is2(encoder, PgTime2)) {
@@ -900,41 +1250,148 @@ function resolvePoolSize(pool) {
900
1250
  }
901
1251
  function createDuckDBConnectionPool(instance, options = {}) {
902
1252
  const size = options.size && options.size > 0 ? options.size : 4;
1253
+ const acquireTimeout = options.acquireTimeout ?? 30000;
1254
+ const maxWaitingRequests = options.maxWaitingRequests ?? 100;
1255
+ const maxLifetimeMs = options.maxLifetimeMs;
1256
+ const idleTimeoutMs = options.idleTimeoutMs;
1257
+ const metadata = new WeakMap;
903
1258
  const idle = [];
904
1259
  const waiting = [];
905
1260
  let total = 0;
906
1261
  let closed = false;
1262
+ let pendingAcquires = 0;
1263
+ const shouldRecycle = (conn, now) => {
1264
+ if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
1265
+ return true;
1266
+ }
1267
+ if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
1268
+ return true;
1269
+ }
1270
+ return false;
1271
+ };
907
1272
  const acquire = async () => {
908
1273
  if (closed) {
909
1274
  throw new Error("DuckDB connection pool is closed");
910
1275
  }
911
- if (idle.length > 0) {
912
- return idle.pop();
1276
+ while (idle.length > 0) {
1277
+ const pooled = idle.pop();
1278
+ const now = Date.now();
1279
+ if (shouldRecycle(pooled, now)) {
1280
+ await closeClientConnection(pooled.connection);
1281
+ total = Math.max(0, total - 1);
1282
+ metadata.delete(pooled.connection);
1283
+ continue;
1284
+ }
1285
+ pooled.lastUsedAt = now;
1286
+ metadata.set(pooled.connection, {
1287
+ createdAt: pooled.createdAt,
1288
+ lastUsedAt: pooled.lastUsedAt
1289
+ });
1290
+ return pooled.connection;
913
1291
  }
914
1292
  if (total < size) {
1293
+ pendingAcquires += 1;
915
1294
  total += 1;
916
- return await DuckDBConnection.create(instance);
1295
+ try {
1296
+ const connection = await DuckDBConnection.create(instance);
1297
+ if (closed) {
1298
+ await closeClientConnection(connection);
1299
+ total -= 1;
1300
+ throw new Error("DuckDB connection pool is closed");
1301
+ }
1302
+ const now = Date.now();
1303
+ metadata.set(connection, { createdAt: now, lastUsedAt: now });
1304
+ return connection;
1305
+ } catch (error) {
1306
+ total -= 1;
1307
+ throw error;
1308
+ } finally {
1309
+ pendingAcquires -= 1;
1310
+ }
1311
+ }
1312
+ if (waiting.length >= maxWaitingRequests) {
1313
+ throw new Error(`DuckDB connection pool queue is full (max ${maxWaitingRequests} waiting requests)`);
917
1314
  }
918
- return await new Promise((resolve) => {
919
- waiting.push(resolve);
1315
+ return await new Promise((resolve, reject) => {
1316
+ const timeoutId = setTimeout(() => {
1317
+ const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
1318
+ if (idx !== -1) {
1319
+ waiting.splice(idx, 1);
1320
+ }
1321
+ reject(new Error(`DuckDB connection pool acquire timeout after ${acquireTimeout}ms`));
1322
+ }, acquireTimeout);
1323
+ waiting.push({ resolve, reject, timeoutId });
920
1324
  });
921
1325
  };
922
1326
  const release = async (connection) => {
1327
+ const waiter = waiting.shift();
1328
+ if (waiter) {
1329
+ clearTimeout(waiter.timeoutId);
1330
+ const now2 = Date.now();
1331
+ const meta = metadata.get(connection) ?? { createdAt: now2, lastUsedAt: now2 };
1332
+ const expired = maxLifetimeMs !== undefined && now2 - meta.createdAt >= maxLifetimeMs;
1333
+ if (closed) {
1334
+ await closeClientConnection(connection);
1335
+ total = Math.max(0, total - 1);
1336
+ metadata.delete(connection);
1337
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1338
+ return;
1339
+ }
1340
+ if (expired) {
1341
+ await closeClientConnection(connection);
1342
+ total = Math.max(0, total - 1);
1343
+ metadata.delete(connection);
1344
+ try {
1345
+ const replacement = await acquire();
1346
+ waiter.resolve(replacement);
1347
+ } catch (error) {
1348
+ waiter.reject(error);
1349
+ }
1350
+ return;
1351
+ }
1352
+ meta.lastUsedAt = now2;
1353
+ metadata.set(connection, meta);
1354
+ waiter.resolve(connection);
1355
+ return;
1356
+ }
923
1357
  if (closed) {
924
1358
  await closeClientConnection(connection);
1359
+ metadata.delete(connection);
1360
+ total = Math.max(0, total - 1);
925
1361
  return;
926
1362
  }
927
- const waiter = waiting.shift();
928
- if (waiter) {
929
- waiter(connection);
1363
+ const now = Date.now();
1364
+ const existingMeta = metadata.get(connection) ?? { createdAt: now, lastUsedAt: now };
1365
+ existingMeta.lastUsedAt = now;
1366
+ metadata.set(connection, existingMeta);
1367
+ if (maxLifetimeMs !== undefined && now - existingMeta.createdAt >= maxLifetimeMs) {
1368
+ await closeClientConnection(connection);
1369
+ total -= 1;
1370
+ metadata.delete(connection);
930
1371
  return;
931
1372
  }
932
- idle.push(connection);
1373
+ idle.push({
1374
+ connection,
1375
+ createdAt: existingMeta.createdAt,
1376
+ lastUsedAt: existingMeta.lastUsedAt
1377
+ });
933
1378
  };
934
1379
  const close = async () => {
935
1380
  closed = true;
1381
+ const waiters = waiting.splice(0, waiting.length);
1382
+ for (const waiter of waiters) {
1383
+ clearTimeout(waiter.timeoutId);
1384
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1385
+ }
936
1386
  const toClose = idle.splice(0, idle.length);
937
- await Promise.all(toClose.map((conn) => closeClientConnection(conn)));
1387
+ await Promise.allSettled(toClose.map((item) => closeClientConnection(item.connection)));
1388
+ total = Math.max(0, total - toClose.length);
1389
+ toClose.forEach((item) => metadata.delete(item.connection));
1390
+ const maxWait = 5000;
1391
+ const start = Date.now();
1392
+ while (pendingAcquires > 0 && Date.now() - start < maxWait) {
1393
+ await new Promise((r) => setTimeout(r, 10));
1394
+ }
938
1395
  };
939
1396
  return {
940
1397
  acquire,
@@ -944,6 +1401,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
944
1401
  };
945
1402
  }
946
1403
 
1404
+ // src/options.ts
1405
+ var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
1406
+ function resolveRewriteArraysOption(value) {
1407
+ if (value === undefined)
1408
+ return DEFAULT_REWRITE_ARRAYS_MODE;
1409
+ if (value === true)
1410
+ return "auto";
1411
+ if (value === false)
1412
+ return "never";
1413
+ return value;
1414
+ }
1415
+ var DEFAULT_PREPARED_CACHE_SIZE = 32;
1416
+ function resolvePrepareCacheOption(option) {
1417
+ if (!option)
1418
+ return;
1419
+ if (option === true) {
1420
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
1421
+ }
1422
+ if (typeof option === "number") {
1423
+ const size2 = Math.max(1, Math.floor(option));
1424
+ return { size: size2 };
1425
+ }
1426
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
1427
+ return { size: Math.max(1, Math.floor(size)) };
1428
+ }
1429
+
947
1430
  // src/driver.ts
948
1431
  class DuckDBDriver {
949
1432
  client;
@@ -958,8 +1441,9 @@ class DuckDBDriver {
958
1441
  createSession(schema) {
959
1442
  return new DuckDBSession(this.client, this.dialect, schema, {
960
1443
  logger: this.options.logger,
961
- rewriteArrays: this.options.rewriteArrays,
962
- rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
1444
+ rewriteArrays: this.options.rewriteArrays ?? "auto",
1445
+ rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
1446
+ prepareCache: this.options.prepareCache
963
1447
  });
964
1448
  }
965
1449
  }
@@ -972,6 +1456,8 @@ function isConfigObject(data) {
972
1456
  }
973
1457
  function createFromClient(client, config = {}, instance) {
974
1458
  const dialect = new DuckDBDialect;
1459
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
1460
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
975
1461
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
976
1462
  let schema;
977
1463
  if (config.schema) {
@@ -984,8 +1470,9 @@ function createFromClient(client, config = {}, instance) {
984
1470
  }
985
1471
  const driver = new DuckDBDriver(client, dialect, {
986
1472
  logger,
987
- rewriteArrays: config.rewriteArrays,
988
- rejectStringArrayLiterals: config.rejectStringArrayLiterals
1473
+ rewriteArrays: rewriteArraysMode,
1474
+ rejectStringArrayLiterals: config.rejectStringArrayLiterals,
1475
+ prepareCache
989
1476
  });
990
1477
  const session = driver.createSession(schema);
991
1478
  const db = new DuckDBDatabase(dialect, session, schema, client, instance);
@@ -1042,6 +1529,17 @@ class DuckDBDatabase extends PgDatabase {
1042
1529
  if (isPool(this.$client) && this.$client.close) {
1043
1530
  await this.$client.close();
1044
1531
  }
1532
+ if (!isPool(this.$client)) {
1533
+ await closeClientConnection(this.$client);
1534
+ }
1535
+ if (this.$instance) {
1536
+ const maybeClosable = this.$instance;
1537
+ if (typeof maybeClosable.close === "function") {
1538
+ await maybeClosable.close();
1539
+ } else if (typeof maybeClosable.closeSync === "function") {
1540
+ maybeClosable.closeSync();
1541
+ }
1542
+ }
1045
1543
  }
1046
1544
  select(fields) {
1047
1545
  const selectedFields = fields ? aliasFields(fields) : undefined;
@@ -1054,6 +1552,9 @@ class DuckDBDatabase extends PgDatabase {
1054
1552
  executeBatches(query, options = {}) {
1055
1553
  return this.session.executeBatches(query, options);
1056
1554
  }
1555
+ executeBatchesRaw(query, options = {}) {
1556
+ return this.session.executeBatchesRaw(query, options);
1557
+ }
1057
1558
  executeArrow(query) {
1058
1559
  return this.session.executeArrow(query);
1059
1560
  }
@@ -1445,6 +1946,20 @@ function mapDuckDbType(column, imports, options) {
1445
1946
  imports.pgCore.add("text");
1446
1947
  return { builder: `text(${columnName(column.name)}) /* JSON */` };
1447
1948
  }
1949
+ if (upper.startsWith("ENUM")) {
1950
+ imports.pgCore.add("text");
1951
+ const enumLiteral = raw.replace(/^ENUM\s*/i, "").trim();
1952
+ return {
1953
+ builder: `text(${columnName(column.name)}) /* ENUM ${enumLiteral} */`
1954
+ };
1955
+ }
1956
+ if (upper.startsWith("UNION")) {
1957
+ imports.pgCore.add("text");
1958
+ const unionLiteral = raw.replace(/^UNION\s*/i, "").trim();
1959
+ return {
1960
+ builder: `text(${columnName(column.name)}) /* UNION ${unionLiteral} */`
1961
+ };
1962
+ }
1448
1963
  if (upper === "INET") {
1449
1964
  imports.local.add("duckDbInet");
1450
1965
  return { builder: `duckDbInet(${columnName(column.name)})` };
@@ -1510,7 +2025,7 @@ function mapDuckDbType(column, imports, options) {
1510
2025
  }
1511
2026
  imports.pgCore.add("text");
1512
2027
  return {
1513
- builder: `text(${columnName(column.name)}) /* TODO: verify type ${upper} */`
2028
+ builder: `text(${columnName(column.name)}) /* unsupported DuckDB type: ${upper} */`
1514
2029
  };
1515
2030
  }
1516
2031
  function parseStructFields(inner) {
@@ -1620,6 +2135,7 @@ function renderImports(imports, importBasePath) {
1620
2135
  function parseArgs(argv) {
1621
2136
  const options = {
1622
2137
  outFile: path.resolve(process.cwd(), "drizzle/schema.ts"),
2138
+ outMeta: undefined,
1623
2139
  allDatabases: false,
1624
2140
  includeViews: false,
1625
2141
  useCustomTimeTypes: true
@@ -1645,6 +2161,11 @@ function parseArgs(argv) {
1645
2161
  case "--outFile":
1646
2162
  options.outFile = path.resolve(process.cwd(), argv[++i] ?? "drizzle/schema.ts");
1647
2163
  break;
2164
+ case "--out-json":
2165
+ case "--outJson":
2166
+ case "--json":
2167
+ options.outMeta = path.resolve(process.cwd(), argv[++i] ?? "drizzle/schema.meta.json");
2168
+ break;
1648
2169
  case "--include-views":
1649
2170
  case "--includeViews":
1650
2171
  options.includeViews = true;
@@ -1679,6 +2200,7 @@ Options:
1679
2200
  --all-databases Introspect all attached databases (not just current)
1680
2201
  --schema Comma separated schema list (defaults to all non-system schemas)
1681
2202
  --out Output file (default: ./drizzle/schema.ts)
2203
+ --json Optional JSON metadata output (default: ./drizzle/schema.meta.json)
1682
2204
  --include-views Include views in the generated schema
1683
2205
  --use-pg-time Use pg-core timestamp/date/time instead of DuckDB custom helpers
1684
2206
  --import-base Override import path for duckdb helpers (default: package name)
@@ -1719,11 +2241,23 @@ async function main() {
1719
2241
  });
1720
2242
  await mkdir(path.dirname(options.outFile), { recursive: true });
1721
2243
  await writeFile(options.outFile, result.files.schemaTs, "utf8");
2244
+ if (options.outMeta) {
2245
+ await mkdir(path.dirname(options.outMeta), { recursive: true });
2246
+ await writeFile(options.outMeta, JSON.stringify(result.files.metaJson, null, 2), "utf8");
2247
+ }
1722
2248
  console.log(`Wrote schema to ${options.outFile}`);
2249
+ if (options.outMeta) {
2250
+ console.log(`Wrote metadata to ${options.outMeta}`);
2251
+ }
1723
2252
  } finally {
1724
2253
  if ("closeSync" in connection && typeof connection.closeSync === "function") {
1725
2254
  connection.closeSync();
1726
2255
  }
2256
+ if ("closeSync" in instance && typeof instance.closeSync === "function") {
2257
+ instance.closeSync();
2258
+ } else if ("close" in instance && typeof instance.close === "function") {
2259
+ await instance.close();
2260
+ }
1727
2261
  }
1728
2262
  }
1729
2263
  main().catch((err) => {