@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.
package/dist/index.mjs CHANGED
@@ -16,83 +16,162 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
16
16
  import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
17
17
 
18
18
  // src/sql/query-rewriters.ts
19
- function adaptArrayOperators(query) {
20
- const operators = [
21
- { token: "@>", fn: "array_has_all" },
22
- { token: "<@", fn: "array_has_all", swap: true },
23
- { token: "&&", fn: "array_has_any" }
24
- ];
25
- const isWhitespace = (char) => char !== undefined && /\s/.test(char);
26
- const walkLeft = (source, start) => {
27
- let idx = start;
28
- while (idx >= 0 && isWhitespace(source[idx])) {
29
- idx--;
30
- }
31
- let depth = 0;
32
- let inString = false;
33
- for (;idx >= 0; idx--) {
34
- const ch = source[idx];
35
- if (ch === undefined)
36
- break;
37
- if (ch === "'" && source[idx - 1] !== "\\") {
38
- inString = !inString;
19
+ var OPERATORS = [
20
+ { token: "@>", fn: "array_has_all" },
21
+ { token: "<@", fn: "array_has_all", swap: true },
22
+ { token: "&&", fn: "array_has_any" }
23
+ ];
24
+ var isWhitespace = (char) => char !== undefined && /\s/.test(char);
25
+ function scrubForRewrite(query) {
26
+ let scrubbed = "";
27
+ let state = "code";
28
+ for (let i = 0;i < query.length; i += 1) {
29
+ const char = query[i];
30
+ const next = query[i + 1];
31
+ if (state === "code") {
32
+ if (char === "'") {
33
+ scrubbed += "'";
34
+ state = "single";
35
+ continue;
39
36
  }
40
- if (inString)
37
+ if (char === '"') {
38
+ scrubbed += '"';
39
+ state = "double";
41
40
  continue;
42
- if (ch === ")" || ch === "]") {
43
- depth++;
44
- } else if (ch === "(" || ch === "[") {
45
- depth--;
46
- if (depth < 0) {
47
- return [idx + 1, source.slice(idx + 1, start + 1)];
48
- }
49
- } else if (depth === 0 && isWhitespace(ch)) {
50
- return [idx + 1, source.slice(idx + 1, start + 1)];
51
41
  }
42
+ if (char === "-" && next === "-") {
43
+ scrubbed += " ";
44
+ i += 1;
45
+ state = "lineComment";
46
+ continue;
47
+ }
48
+ if (char === "/" && next === "*") {
49
+ scrubbed += " ";
50
+ i += 1;
51
+ state = "blockComment";
52
+ continue;
53
+ }
54
+ scrubbed += char;
55
+ continue;
52
56
  }
53
- return [0, source.slice(0, start + 1)];
54
- };
55
- const walkRight = (source, start) => {
56
- let idx = start;
57
- while (idx < source.length && isWhitespace(source[idx])) {
58
- idx++;
59
- }
60
- let depth = 0;
61
- let inString = false;
62
- for (;idx < source.length; idx++) {
63
- const ch = source[idx];
64
- if (ch === undefined)
65
- break;
66
- if (ch === "'" && source[idx - 1] !== "\\") {
67
- inString = !inString;
57
+ if (state === "single") {
58
+ if (char === "'" && next === "'") {
59
+ scrubbed += "''";
60
+ i += 1;
61
+ continue;
62
+ }
63
+ scrubbed += char === "'" ? "'" : ".";
64
+ if (char === "'") {
65
+ state = "code";
68
66
  }
69
- if (inString)
67
+ continue;
68
+ }
69
+ if (state === "double") {
70
+ if (char === '"' && next === '"') {
71
+ scrubbed += '""';
72
+ i += 1;
70
73
  continue;
71
- if (ch === "(" || ch === "[") {
72
- depth++;
73
- } else if (ch === ")" || ch === "]") {
74
- depth--;
75
- if (depth < 0) {
76
- return [idx, source.slice(start, idx)];
77
- }
78
- } else if (depth === 0 && isWhitespace(ch)) {
74
+ }
75
+ scrubbed += char === '"' ? '"' : ".";
76
+ if (char === '"') {
77
+ state = "code";
78
+ }
79
+ continue;
80
+ }
81
+ if (state === "lineComment") {
82
+ scrubbed += char === `
83
+ ` ? `
84
+ ` : " ";
85
+ if (char === `
86
+ `) {
87
+ state = "code";
88
+ }
89
+ continue;
90
+ }
91
+ if (state === "blockComment") {
92
+ if (char === "*" && next === "/") {
93
+ scrubbed += " ";
94
+ i += 1;
95
+ state = "code";
96
+ } else {
97
+ scrubbed += " ";
98
+ }
99
+ }
100
+ }
101
+ return scrubbed;
102
+ }
103
+ function findNextOperator(scrubbed, start) {
104
+ for (let idx = start;idx < scrubbed.length; idx += 1) {
105
+ for (const operator of OPERATORS) {
106
+ if (scrubbed.startsWith(operator.token, idx)) {
107
+ return { index: idx, operator };
108
+ }
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function walkLeft(source, scrubbed, start) {
114
+ let idx = start;
115
+ while (idx >= 0 && isWhitespace(scrubbed[idx])) {
116
+ idx -= 1;
117
+ }
118
+ let depth = 0;
119
+ for (;idx >= 0; idx -= 1) {
120
+ const ch = scrubbed[idx];
121
+ if (ch === ")" || ch === "]") {
122
+ depth += 1;
123
+ } else if (ch === "(" || ch === "[") {
124
+ if (depth === 0) {
125
+ return [idx + 1, source.slice(idx + 1, start + 1)];
126
+ }
127
+ depth = Math.max(0, depth - 1);
128
+ } else if (depth === 0 && isWhitespace(ch)) {
129
+ return [idx + 1, source.slice(idx + 1, start + 1)];
130
+ }
131
+ }
132
+ return [0, source.slice(0, start + 1)];
133
+ }
134
+ function walkRight(source, scrubbed, start) {
135
+ let idx = start;
136
+ while (idx < scrubbed.length && isWhitespace(scrubbed[idx])) {
137
+ idx += 1;
138
+ }
139
+ let depth = 0;
140
+ for (;idx < scrubbed.length; idx += 1) {
141
+ const ch = scrubbed[idx];
142
+ if (ch === "(" || ch === "[") {
143
+ depth += 1;
144
+ } else if (ch === ")" || ch === "]") {
145
+ if (depth === 0) {
79
146
  return [idx, source.slice(start, idx)];
80
147
  }
148
+ depth = Math.max(0, depth - 1);
149
+ } else if (depth === 0 && isWhitespace(ch)) {
150
+ return [idx, source.slice(start, idx)];
81
151
  }
82
- return [source.length, source.slice(start)];
83
- };
152
+ }
153
+ return [scrubbed.length, source.slice(start)];
154
+ }
155
+ function adaptArrayOperators(query) {
156
+ if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
157
+ return query;
158
+ }
84
159
  let rewritten = query;
85
- for (const { token, fn, swap } of operators) {
86
- let idx = rewritten.indexOf(token);
87
- while (idx !== -1) {
88
- const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
89
- const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
90
- const left = leftExpr.trim();
91
- const right = rightExpr.trim();
92
- const replacement = `${fn}(${swap ? right : left}, ${swap ? left : right})`;
93
- rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
94
- idx = rewritten.indexOf(token, leftStart + replacement.length);
95
- }
160
+ let scrubbed = scrubForRewrite(query);
161
+ let searchStart = 0;
162
+ while (true) {
163
+ const next = findNextOperator(scrubbed, searchStart);
164
+ if (!next)
165
+ break;
166
+ const { index, operator } = next;
167
+ const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
168
+ const [rightEnd, rightExpr] = walkRight(rewritten, scrubbed, index + operator.token.length);
169
+ const left = leftExpr.trim();
170
+ const right = rightExpr.trim();
171
+ const replacement = `${operator.fn}(${operator.swap ? right : left}, ${operator.swap ? left : right})`;
172
+ rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
173
+ scrubbed = scrubForRewrite(rewritten);
174
+ searchStart = leftStart + replacement.length;
96
175
  }
97
176
  return rewritten;
98
177
  }
@@ -386,6 +465,12 @@ function dateToMicros(value) {
386
465
  if (value instanceof Date) {
387
466
  return BigInt(value.getTime()) * 1000n;
388
467
  }
468
+ if (typeof value === "bigint") {
469
+ return value;
470
+ }
471
+ if (typeof value === "number") {
472
+ return BigInt(Math.trunc(value)) * 1000n;
473
+ }
389
474
  let normalized = value;
390
475
  if (!value.includes("T") && value.includes(" ")) {
391
476
  normalized = value.replace(" ", "T");
@@ -442,6 +527,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
442
527
  function isPool(client) {
443
528
  return typeof client.acquire === "function";
444
529
  }
530
+ var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
445
531
  function isPgArrayLiteral(value) {
446
532
  return value.startsWith("{") && value.endsWith("}");
447
533
  }
@@ -455,14 +541,21 @@ function parsePgArrayLiteral(value) {
455
541
  }
456
542
  function prepareParams(params, options = {}) {
457
543
  return params.map((param) => {
458
- if (typeof param === "string" && isPgArrayLiteral(param)) {
459
- if (options.rejectStringArrayLiterals) {
460
- throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
461
- }
462
- if (options.warnOnStringArrayLiteral) {
463
- options.warnOnStringArrayLiteral();
544
+ if (typeof param === "string" && param.length > 0) {
545
+ const firstChar = param[0];
546
+ const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
547
+ if (maybeArrayLiteral) {
548
+ const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
549
+ if (trimmed && isPgArrayLiteral(trimmed)) {
550
+ if (options.rejectStringArrayLiterals) {
551
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
552
+ }
553
+ if (options.warnOnStringArrayLiteral) {
554
+ options.warnOnStringArrayLiteral();
555
+ }
556
+ return parsePgArrayLiteral(trimmed);
557
+ }
464
558
  }
465
- return parsePgArrayLiteral(param);
466
559
  }
467
560
  return param;
468
561
  });
@@ -486,13 +579,121 @@ function toNodeApiValue(value) {
486
579
  return value;
487
580
  }
488
581
  function deduplicateColumns(columns) {
489
- const seen = {};
490
- return columns.map((col) => {
491
- const count = seen[col] ?? 0;
492
- seen[col] = count + 1;
493
- return count === 0 ? col : `${col}_${count}`;
582
+ const counts = new Map;
583
+ let hasDuplicates = false;
584
+ for (const column of columns) {
585
+ const next = (counts.get(column) ?? 0) + 1;
586
+ counts.set(column, next);
587
+ if (next > 1) {
588
+ hasDuplicates = true;
589
+ break;
590
+ }
591
+ }
592
+ if (!hasDuplicates) {
593
+ return columns;
594
+ }
595
+ counts.clear();
596
+ return columns.map((column) => {
597
+ const count = counts.get(column) ?? 0;
598
+ counts.set(column, count + 1);
599
+ return count === 0 ? column : `${column}_${count}`;
494
600
  });
495
601
  }
602
+ function destroyPreparedStatement(entry) {
603
+ if (!entry)
604
+ return;
605
+ try {
606
+ entry.statement.destroySync();
607
+ } catch {}
608
+ }
609
+ function getPreparedCache(connection, size) {
610
+ const store = connection;
611
+ const existing = store[PREPARED_CACHE];
612
+ if (existing) {
613
+ existing.size = size;
614
+ return existing;
615
+ }
616
+ const cache = { size, entries: new Map };
617
+ store[PREPARED_CACHE] = cache;
618
+ return cache;
619
+ }
620
+ function evictOldest(cache) {
621
+ const oldest = cache.entries.keys().next();
622
+ if (!oldest.done) {
623
+ const key = oldest.value;
624
+ const entry = cache.entries.get(key);
625
+ cache.entries.delete(key);
626
+ destroyPreparedStatement(entry);
627
+ }
628
+ }
629
+ function evictCacheEntry(cache, key) {
630
+ const entry = cache.entries.get(key);
631
+ cache.entries.delete(key);
632
+ destroyPreparedStatement(entry);
633
+ }
634
+ async function getOrPrepareStatement(connection, query, cacheConfig) {
635
+ const cache = getPreparedCache(connection, cacheConfig.size);
636
+ const cached = cache.entries.get(query);
637
+ if (cached) {
638
+ cache.entries.delete(query);
639
+ cache.entries.set(query, cached);
640
+ return cached.statement;
641
+ }
642
+ const statement = await connection.prepare(query);
643
+ cache.entries.set(query, { statement });
644
+ while (cache.entries.size > cache.size) {
645
+ evictOldest(cache);
646
+ }
647
+ return statement;
648
+ }
649
+ async function materializeResultRows(result) {
650
+ const rows = await result.getRowsJS() ?? [];
651
+ const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
652
+ const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
653
+ return { columns, rows };
654
+ }
655
+ async function materializeRows(client, query, params, options = {}) {
656
+ if (isPool(client)) {
657
+ const connection2 = await client.acquire();
658
+ try {
659
+ return await materializeRows(connection2, query, params, options);
660
+ } finally {
661
+ await client.release(connection2);
662
+ }
663
+ }
664
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
665
+ const connection = client;
666
+ if (options.prepareCache && typeof connection.prepare === "function") {
667
+ const cache = getPreparedCache(connection, options.prepareCache.size);
668
+ try {
669
+ const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
670
+ if (values) {
671
+ statement.bind(values);
672
+ } else {
673
+ statement.clearBindings?.();
674
+ }
675
+ const result2 = await statement.run();
676
+ cache.entries.delete(query);
677
+ cache.entries.set(query, { statement });
678
+ return await materializeResultRows(result2);
679
+ } catch (error) {
680
+ evictCacheEntry(cache, query);
681
+ throw error;
682
+ }
683
+ }
684
+ const result = await connection.run(query, values);
685
+ return await materializeResultRows(result);
686
+ }
687
+ function clearPreparedCache(connection) {
688
+ const store = connection;
689
+ const cache = store[PREPARED_CACHE];
690
+ if (!cache)
691
+ return;
692
+ for (const entry of cache.entries.values()) {
693
+ destroyPreparedStatement(entry);
694
+ }
695
+ cache.entries.clear();
696
+ }
496
697
  function mapRowsToObjects(columns, rows) {
497
698
  return rows.map((vals) => {
498
699
  const obj = {};
@@ -503,6 +704,7 @@ function mapRowsToObjects(columns, rows) {
503
704
  });
504
705
  }
505
706
  async function closeClientConnection(connection) {
707
+ clearPreparedCache(connection);
506
708
  if ("close" in connection && typeof connection.close === "function") {
507
709
  await connection.close();
508
710
  return;
@@ -515,27 +717,51 @@ async function closeClientConnection(connection) {
515
717
  connection.disconnectSync();
516
718
  }
517
719
  }
518
- async function executeOnClient(client, query, params) {
720
+ async function executeOnClient(client, query, params, options = {}) {
721
+ const { columns, rows } = await materializeRows(client, query, params, options);
722
+ if (!rows || rows.length === 0) {
723
+ return [];
724
+ }
725
+ return mapRowsToObjects(columns, rows);
726
+ }
727
+ async function executeArraysOnClient(client, query, params, options = {}) {
728
+ return await materializeRows(client, query, params, options);
729
+ }
730
+ async function* executeInBatches(client, query, params, options = {}) {
519
731
  if (isPool(client)) {
520
732
  const connection = await client.acquire();
521
733
  try {
522
- return await executeOnClient(connection, query, params);
734
+ yield* executeInBatches(connection, query, params, options);
735
+ return;
523
736
  } finally {
524
737
  await client.release(connection);
525
738
  }
526
739
  }
740
+ const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
527
741
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
528
- const result = await client.run(query, values);
529
- const rows = await result.getRowsJS();
530
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
531
- const uniqueColumns = deduplicateColumns(columns);
532
- return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
742
+ const result = await client.stream(query, values);
743
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
744
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
745
+ let buffer = [];
746
+ for await (const chunk of result.yieldRowsJs()) {
747
+ const objects = mapRowsToObjects(columns, chunk);
748
+ for (const row of objects) {
749
+ buffer.push(row);
750
+ if (buffer.length >= rowsPerChunk) {
751
+ yield buffer;
752
+ buffer = [];
753
+ }
754
+ }
755
+ }
756
+ if (buffer.length > 0) {
757
+ yield buffer;
758
+ }
533
759
  }
534
- async function* executeInBatches(client, query, params, options = {}) {
760
+ async function* executeInBatchesRaw(client, query, params, options = {}) {
535
761
  if (isPool(client)) {
536
762
  const connection = await client.acquire();
537
763
  try {
538
- yield* executeInBatches(connection, query, params, options);
764
+ yield* executeInBatchesRaw(connection, query, params, options);
539
765
  return;
540
766
  } finally {
541
767
  await client.release(connection);
@@ -544,21 +770,20 @@ async function* executeInBatches(client, query, params, options = {}) {
544
770
  const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
545
771
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
546
772
  const result = await client.stream(query, values);
547
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
548
- const uniqueColumns = deduplicateColumns(columns);
773
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
774
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
549
775
  let buffer = [];
550
776
  for await (const chunk of result.yieldRowsJs()) {
551
- const objects = mapRowsToObjects(uniqueColumns, chunk);
552
- for (const row of objects) {
777
+ for (const row of chunk) {
553
778
  buffer.push(row);
554
779
  if (buffer.length >= rowsPerChunk) {
555
- yield buffer;
780
+ yield { columns, rows: buffer };
556
781
  buffer = [];
557
782
  }
558
783
  }
559
784
  }
560
785
  if (buffer.length > 0) {
561
- yield buffer;
786
+ yield { columns, rows: buffer };
562
787
  }
563
788
  }
564
789
  async function executeArrowOnClient(client, query, params) {
@@ -580,6 +805,20 @@ async function executeArrowOnClient(client, query, params) {
580
805
  }
581
806
 
582
807
  // src/session.ts
808
+ function isSavepointSyntaxError(error) {
809
+ if (!(error instanceof Error) || !error.message) {
810
+ return false;
811
+ }
812
+ return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
813
+ }
814
+ function rewriteQuery(mode, query) {
815
+ if (mode === "never") {
816
+ return { sql: query, rewritten: false };
817
+ }
818
+ const rewritten = adaptArrayOperators(query);
819
+ return { sql: rewritten, rewritten: rewritten !== query };
820
+ }
821
+
583
822
  class DuckDBPreparedQuery extends PgPreparedQuery {
584
823
  client;
585
824
  dialect;
@@ -589,11 +828,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
589
828
  fields;
590
829
  _isResponseInArrayMode;
591
830
  customResultMapper;
592
- rewriteArrays;
831
+ rewriteArraysMode;
593
832
  rejectStringArrayLiterals;
833
+ prepareCache;
594
834
  warnOnStringArrayLiteral;
595
835
  static [entityKind] = "DuckDBPreparedQuery";
596
- constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArrays, rejectStringArrayLiterals, warnOnStringArrayLiteral) {
836
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
597
837
  super({ sql: queryString, params });
598
838
  this.client = client;
599
839
  this.dialect = dialect;
@@ -603,8 +843,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
603
843
  this.fields = fields;
604
844
  this._isResponseInArrayMode = _isResponseInArrayMode;
605
845
  this.customResultMapper = customResultMapper;
606
- this.rewriteArrays = rewriteArrays;
846
+ this.rewriteArraysMode = rewriteArraysMode;
607
847
  this.rejectStringArrayLiterals = rejectStringArrayLiterals;
848
+ this.prepareCache = prepareCache;
608
849
  this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
609
850
  }
610
851
  async execute(placeholderValues = {}) {
@@ -613,18 +854,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
613
854
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
614
855
  warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
615
856
  });
616
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(this.queryString) : this.queryString;
617
- if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
857
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
858
+ if (didRewrite) {
618
859
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
619
860
  }
620
861
  this.logger.logQuery(rewrittenQuery, params);
621
862
  const { fields, joinsNotNullableMap, customResultMapper } = this;
622
- const rows = await executeOnClient(this.client, rewrittenQuery, params);
623
- if (rows.length === 0 || !fields) {
624
- return rows;
863
+ if (fields) {
864
+ const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
865
+ if (rows2.length === 0) {
866
+ return [];
867
+ }
868
+ return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
625
869
  }
626
- const rowValues = rows.map((row) => Object.values(row));
627
- return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
870
+ const rows = await executeOnClient(this.client, rewrittenQuery, params, {
871
+ prepareCache: this.prepareCache
872
+ });
873
+ return rows;
628
874
  }
629
875
  all(placeholderValues = {}) {
630
876
  return this.execute(placeholderValues);
@@ -641,9 +887,11 @@ class DuckDBSession extends PgSession {
641
887
  static [entityKind] = "DuckDBSession";
642
888
  dialect;
643
889
  logger;
644
- rewriteArrays;
890
+ rewriteArraysMode;
645
891
  rejectStringArrayLiterals;
892
+ prepareCache;
646
893
  hasWarnedArrayLiteral = false;
894
+ rollbackOnly = false;
647
895
  constructor(client, dialect, schema, options = {}) {
648
896
  super(dialect);
649
897
  this.client = client;
@@ -651,13 +899,27 @@ class DuckDBSession extends PgSession {
651
899
  this.options = options;
652
900
  this.dialect = dialect;
653
901
  this.logger = options.logger ?? new NoopLogger;
654
- this.rewriteArrays = options.rewriteArrays ?? true;
902
+ this.rewriteArraysMode = options.rewriteArrays ?? "auto";
655
903
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
904
+ this.prepareCache = options.prepareCache;
905
+ this.options = {
906
+ ...options,
907
+ rewriteArrays: this.rewriteArraysMode,
908
+ prepareCache: this.prepareCache
909
+ };
656
910
  }
657
911
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
658
- 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);
912
+ 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);
659
913
  }
660
- async transaction(transaction) {
914
+ execute(query) {
915
+ this.dialect.resetPgJsonFlag();
916
+ return super.execute(query);
917
+ }
918
+ all(query) {
919
+ this.dialect.resetPgJsonFlag();
920
+ return super.all(query);
921
+ }
922
+ async transaction(transaction, config) {
661
923
  let pinnedConnection;
662
924
  let pool;
663
925
  let clientForTx = this.client;
@@ -670,8 +932,15 @@ class DuckDBSession extends PgSession {
670
932
  const tx = new DuckDBTransaction(this.dialect, session, this.schema);
671
933
  try {
672
934
  await tx.execute(sql`BEGIN TRANSACTION;`);
935
+ if (config) {
936
+ await tx.setTransaction(config);
937
+ }
673
938
  try {
674
939
  const result = await transaction(tx);
940
+ if (session.isRollbackOnly()) {
941
+ await tx.execute(sql`rollback`);
942
+ throw new TransactionRollbackError;
943
+ }
675
944
  await tx.execute(sql`commit`);
676
945
  return result;
677
946
  } catch (error) {
@@ -693,31 +962,56 @@ class DuckDBSession extends PgSession {
693
962
  query: ${query}`, []);
694
963
  };
695
964
  executeBatches(query, options = {}) {
965
+ this.dialect.resetPgJsonFlag();
696
966
  const builtQuery = this.dialect.sqlToQuery(query);
967
+ this.dialect.assertNoPgJsonColumns();
697
968
  const params = prepareParams(builtQuery.params, {
698
969
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
699
970
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
700
971
  });
701
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
702
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
972
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
973
+ if (didRewrite) {
703
974
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
704
975
  }
705
976
  this.logger.logQuery(rewrittenQuery, params);
706
977
  return executeInBatches(this.client, rewrittenQuery, params, options);
707
978
  }
979
+ executeBatchesRaw(query, options = {}) {
980
+ this.dialect.resetPgJsonFlag();
981
+ const builtQuery = this.dialect.sqlToQuery(query);
982
+ this.dialect.assertNoPgJsonColumns();
983
+ const params = prepareParams(builtQuery.params, {
984
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
985
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
986
+ });
987
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
988
+ if (didRewrite) {
989
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
990
+ }
991
+ this.logger.logQuery(rewrittenQuery, params);
992
+ return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
993
+ }
708
994
  async executeArrow(query) {
995
+ this.dialect.resetPgJsonFlag();
709
996
  const builtQuery = this.dialect.sqlToQuery(query);
997
+ this.dialect.assertNoPgJsonColumns();
710
998
  const params = prepareParams(builtQuery.params, {
711
999
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
712
1000
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
713
1001
  });
714
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
715
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
1002
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1003
+ if (didRewrite) {
716
1004
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
717
1005
  }
718
1006
  this.logger.logQuery(rewrittenQuery, params);
719
1007
  return executeArrowOnClient(this.client, rewrittenQuery, params);
720
1008
  }
1009
+ markRollbackOnly() {
1010
+ this.rollbackOnly = true;
1011
+ }
1012
+ isRollbackOnly() {
1013
+ return this.rollbackOnly;
1014
+ }
721
1015
  }
722
1016
 
723
1017
  class DuckDBTransaction extends PgTransaction {
@@ -744,12 +1038,53 @@ class DuckDBTransaction extends PgTransaction {
744
1038
  executeBatches(query, options = {}) {
745
1039
  return this.session.executeBatches(query, options);
746
1040
  }
1041
+ executeBatchesRaw(query, options = {}) {
1042
+ return this.session.executeBatchesRaw(query, options);
1043
+ }
747
1044
  executeArrow(query) {
748
1045
  return this.session.executeArrow(query);
749
1046
  }
750
1047
  async transaction(transaction) {
751
- const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
752
- return transaction(nestedTx);
1048
+ const internals = this;
1049
+ const savepoint = `drizzle_savepoint_${this.nestedIndex + 1}`;
1050
+ const savepointSql = sql.raw(`savepoint ${savepoint}`);
1051
+ const releaseSql = sql.raw(`release savepoint ${savepoint}`);
1052
+ const rollbackSql = sql.raw(`rollback to savepoint ${savepoint}`);
1053
+ const nestedTx = new DuckDBTransaction(internals.dialect, internals.session, this.schema, this.nestedIndex + 1);
1054
+ if (internals.dialect.areSavepointsUnsupported()) {
1055
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
1056
+ }
1057
+ let createdSavepoint = false;
1058
+ try {
1059
+ await internals.session.execute(savepointSql);
1060
+ internals.dialect.markSavepointsSupported();
1061
+ createdSavepoint = true;
1062
+ } catch (error) {
1063
+ if (!isSavepointSyntaxError(error)) {
1064
+ throw error;
1065
+ }
1066
+ internals.dialect.markSavepointsUnsupported();
1067
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
1068
+ }
1069
+ try {
1070
+ const result = await transaction(nestedTx);
1071
+ if (createdSavepoint) {
1072
+ await internals.session.execute(releaseSql);
1073
+ }
1074
+ return result;
1075
+ } catch (error) {
1076
+ if (createdSavepoint) {
1077
+ await internals.session.execute(rollbackSql);
1078
+ }
1079
+ internals.session.markRollbackOnly();
1080
+ throw error;
1081
+ }
1082
+ }
1083
+ runNestedWithoutSavepoint(transaction, nestedTx, internals) {
1084
+ return transaction(nestedTx).catch((error) => {
1085
+ internals.session.markRollbackOnly();
1086
+ throw error;
1087
+ });
753
1088
  }
754
1089
  }
755
1090
  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.";
@@ -771,15 +1106,30 @@ import {
771
1106
  import {
772
1107
  sql as sql2
773
1108
  } from "drizzle-orm";
774
-
775
1109
  class DuckDBDialect extends PgDialect {
776
1110
  static [entityKind2] = "DuckDBPgDialect";
777
1111
  hasPgJsonColumn = false;
1112
+ savepointsSupported = 0 /* Unknown */;
1113
+ resetPgJsonFlag() {
1114
+ this.hasPgJsonColumn = false;
1115
+ }
1116
+ markPgJsonDetected() {
1117
+ this.hasPgJsonColumn = true;
1118
+ }
778
1119
  assertNoPgJsonColumns() {
779
1120
  if (this.hasPgJsonColumn) {
780
- throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDBs native JSON type.");
1121
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
781
1122
  }
782
1123
  }
1124
+ areSavepointsUnsupported() {
1125
+ return this.savepointsSupported === 2 /* No */;
1126
+ }
1127
+ markSavepointsSupported() {
1128
+ this.savepointsSupported = 1 /* Yes */;
1129
+ }
1130
+ markSavepointsUnsupported() {
1131
+ this.savepointsSupported = 2 /* No */;
1132
+ }
783
1133
  async migrate(migrations, session, config) {
784
1134
  const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
785
1135
  const migrationsSchema = migrationConfig.migrationsSchema ?? "drizzle";
@@ -816,8 +1166,8 @@ class DuckDBDialect extends PgDialect {
816
1166
  }
817
1167
  prepareTyping(encoder) {
818
1168
  if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
819
- this.hasPgJsonColumn = true;
820
- return "none";
1169
+ this.markPgJsonDetected();
1170
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
821
1171
  } else if (is2(encoder, PgNumeric)) {
822
1172
  return "decimal";
823
1173
  } else if (is2(encoder, PgTime2)) {
@@ -951,41 +1301,148 @@ function resolvePoolSize(pool) {
951
1301
  }
952
1302
  function createDuckDBConnectionPool(instance, options = {}) {
953
1303
  const size = options.size && options.size > 0 ? options.size : 4;
1304
+ const acquireTimeout = options.acquireTimeout ?? 30000;
1305
+ const maxWaitingRequests = options.maxWaitingRequests ?? 100;
1306
+ const maxLifetimeMs = options.maxLifetimeMs;
1307
+ const idleTimeoutMs = options.idleTimeoutMs;
1308
+ const metadata = new WeakMap;
954
1309
  const idle = [];
955
1310
  const waiting = [];
956
1311
  let total = 0;
957
1312
  let closed = false;
1313
+ let pendingAcquires = 0;
1314
+ const shouldRecycle = (conn, now) => {
1315
+ if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
1316
+ return true;
1317
+ }
1318
+ if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
1319
+ return true;
1320
+ }
1321
+ return false;
1322
+ };
958
1323
  const acquire = async () => {
959
1324
  if (closed) {
960
1325
  throw new Error("DuckDB connection pool is closed");
961
1326
  }
962
- if (idle.length > 0) {
963
- return idle.pop();
1327
+ while (idle.length > 0) {
1328
+ const pooled = idle.pop();
1329
+ const now = Date.now();
1330
+ if (shouldRecycle(pooled, now)) {
1331
+ await closeClientConnection(pooled.connection);
1332
+ total = Math.max(0, total - 1);
1333
+ metadata.delete(pooled.connection);
1334
+ continue;
1335
+ }
1336
+ pooled.lastUsedAt = now;
1337
+ metadata.set(pooled.connection, {
1338
+ createdAt: pooled.createdAt,
1339
+ lastUsedAt: pooled.lastUsedAt
1340
+ });
1341
+ return pooled.connection;
964
1342
  }
965
1343
  if (total < size) {
1344
+ pendingAcquires += 1;
966
1345
  total += 1;
967
- return await DuckDBConnection.create(instance);
1346
+ try {
1347
+ const connection = await DuckDBConnection.create(instance);
1348
+ if (closed) {
1349
+ await closeClientConnection(connection);
1350
+ total -= 1;
1351
+ throw new Error("DuckDB connection pool is closed");
1352
+ }
1353
+ const now = Date.now();
1354
+ metadata.set(connection, { createdAt: now, lastUsedAt: now });
1355
+ return connection;
1356
+ } catch (error) {
1357
+ total -= 1;
1358
+ throw error;
1359
+ } finally {
1360
+ pendingAcquires -= 1;
1361
+ }
1362
+ }
1363
+ if (waiting.length >= maxWaitingRequests) {
1364
+ throw new Error(`DuckDB connection pool queue is full (max ${maxWaitingRequests} waiting requests)`);
968
1365
  }
969
- return await new Promise((resolve) => {
970
- waiting.push(resolve);
1366
+ return await new Promise((resolve, reject) => {
1367
+ const timeoutId = setTimeout(() => {
1368
+ const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
1369
+ if (idx !== -1) {
1370
+ waiting.splice(idx, 1);
1371
+ }
1372
+ reject(new Error(`DuckDB connection pool acquire timeout after ${acquireTimeout}ms`));
1373
+ }, acquireTimeout);
1374
+ waiting.push({ resolve, reject, timeoutId });
971
1375
  });
972
1376
  };
973
1377
  const release = async (connection) => {
1378
+ const waiter = waiting.shift();
1379
+ if (waiter) {
1380
+ clearTimeout(waiter.timeoutId);
1381
+ const now2 = Date.now();
1382
+ const meta = metadata.get(connection) ?? { createdAt: now2, lastUsedAt: now2 };
1383
+ const expired = maxLifetimeMs !== undefined && now2 - meta.createdAt >= maxLifetimeMs;
1384
+ if (closed) {
1385
+ await closeClientConnection(connection);
1386
+ total = Math.max(0, total - 1);
1387
+ metadata.delete(connection);
1388
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1389
+ return;
1390
+ }
1391
+ if (expired) {
1392
+ await closeClientConnection(connection);
1393
+ total = Math.max(0, total - 1);
1394
+ metadata.delete(connection);
1395
+ try {
1396
+ const replacement = await acquire();
1397
+ waiter.resolve(replacement);
1398
+ } catch (error) {
1399
+ waiter.reject(error);
1400
+ }
1401
+ return;
1402
+ }
1403
+ meta.lastUsedAt = now2;
1404
+ metadata.set(connection, meta);
1405
+ waiter.resolve(connection);
1406
+ return;
1407
+ }
974
1408
  if (closed) {
975
1409
  await closeClientConnection(connection);
1410
+ metadata.delete(connection);
1411
+ total = Math.max(0, total - 1);
976
1412
  return;
977
1413
  }
978
- const waiter = waiting.shift();
979
- if (waiter) {
980
- waiter(connection);
1414
+ const now = Date.now();
1415
+ const existingMeta = metadata.get(connection) ?? { createdAt: now, lastUsedAt: now };
1416
+ existingMeta.lastUsedAt = now;
1417
+ metadata.set(connection, existingMeta);
1418
+ if (maxLifetimeMs !== undefined && now - existingMeta.createdAt >= maxLifetimeMs) {
1419
+ await closeClientConnection(connection);
1420
+ total -= 1;
1421
+ metadata.delete(connection);
981
1422
  return;
982
1423
  }
983
- idle.push(connection);
1424
+ idle.push({
1425
+ connection,
1426
+ createdAt: existingMeta.createdAt,
1427
+ lastUsedAt: existingMeta.lastUsedAt
1428
+ });
984
1429
  };
985
1430
  const close = async () => {
986
1431
  closed = true;
1432
+ const waiters = waiting.splice(0, waiting.length);
1433
+ for (const waiter of waiters) {
1434
+ clearTimeout(waiter.timeoutId);
1435
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1436
+ }
987
1437
  const toClose = idle.splice(0, idle.length);
988
- await Promise.all(toClose.map((conn) => closeClientConnection(conn)));
1438
+ await Promise.allSettled(toClose.map((item) => closeClientConnection(item.connection)));
1439
+ total = Math.max(0, total - toClose.length);
1440
+ toClose.forEach((item) => metadata.delete(item.connection));
1441
+ const maxWait = 5000;
1442
+ const start = Date.now();
1443
+ while (pendingAcquires > 0 && Date.now() - start < maxWait) {
1444
+ await new Promise((r) => setTimeout(r, 10));
1445
+ }
989
1446
  };
990
1447
  return {
991
1448
  acquire,
@@ -995,6 +1452,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
995
1452
  };
996
1453
  }
997
1454
 
1455
+ // src/options.ts
1456
+ var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
1457
+ function resolveRewriteArraysOption(value) {
1458
+ if (value === undefined)
1459
+ return DEFAULT_REWRITE_ARRAYS_MODE;
1460
+ if (value === true)
1461
+ return "auto";
1462
+ if (value === false)
1463
+ return "never";
1464
+ return value;
1465
+ }
1466
+ var DEFAULT_PREPARED_CACHE_SIZE = 32;
1467
+ function resolvePrepareCacheOption(option) {
1468
+ if (!option)
1469
+ return;
1470
+ if (option === true) {
1471
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
1472
+ }
1473
+ if (typeof option === "number") {
1474
+ const size2 = Math.max(1, Math.floor(option));
1475
+ return { size: size2 };
1476
+ }
1477
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
1478
+ return { size: Math.max(1, Math.floor(size)) };
1479
+ }
1480
+
998
1481
  // src/driver.ts
999
1482
  class DuckDBDriver {
1000
1483
  client;
@@ -1009,8 +1492,9 @@ class DuckDBDriver {
1009
1492
  createSession(schema) {
1010
1493
  return new DuckDBSession(this.client, this.dialect, schema, {
1011
1494
  logger: this.options.logger,
1012
- rewriteArrays: this.options.rewriteArrays,
1013
- rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
1495
+ rewriteArrays: this.options.rewriteArrays ?? "auto",
1496
+ rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
1497
+ prepareCache: this.options.prepareCache
1014
1498
  });
1015
1499
  }
1016
1500
  }
@@ -1023,6 +1507,8 @@ function isConfigObject(data) {
1023
1507
  }
1024
1508
  function createFromClient(client, config = {}, instance) {
1025
1509
  const dialect = new DuckDBDialect;
1510
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
1511
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
1026
1512
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
1027
1513
  let schema;
1028
1514
  if (config.schema) {
@@ -1035,8 +1521,9 @@ function createFromClient(client, config = {}, instance) {
1035
1521
  }
1036
1522
  const driver = new DuckDBDriver(client, dialect, {
1037
1523
  logger,
1038
- rewriteArrays: config.rewriteArrays,
1039
- rejectStringArrayLiterals: config.rejectStringArrayLiterals
1524
+ rewriteArrays: rewriteArraysMode,
1525
+ rejectStringArrayLiterals: config.rejectStringArrayLiterals,
1526
+ prepareCache
1040
1527
  });
1041
1528
  const session = driver.createSession(schema);
1042
1529
  const db = new DuckDBDatabase(dialect, session, schema, client, instance);
@@ -1093,6 +1580,17 @@ class DuckDBDatabase extends PgDatabase {
1093
1580
  if (isPool(this.$client) && this.$client.close) {
1094
1581
  await this.$client.close();
1095
1582
  }
1583
+ if (!isPool(this.$client)) {
1584
+ await closeClientConnection(this.$client);
1585
+ }
1586
+ if (this.$instance) {
1587
+ const maybeClosable = this.$instance;
1588
+ if (typeof maybeClosable.close === "function") {
1589
+ await maybeClosable.close();
1590
+ } else if (typeof maybeClosable.closeSync === "function") {
1591
+ maybeClosable.closeSync();
1592
+ }
1593
+ }
1096
1594
  }
1097
1595
  select(fields) {
1098
1596
  const selectedFields = fields ? aliasFields(fields) : undefined;
@@ -1105,6 +1603,9 @@ class DuckDBDatabase extends PgDatabase {
1105
1603
  executeBatches(query, options = {}) {
1106
1604
  return this.session.executeBatches(query, options);
1107
1605
  }
1606
+ executeBatchesRaw(query, options = {}) {
1607
+ return this.session.executeBatchesRaw(query, options);
1608
+ }
1108
1609
  executeArrow(query) {
1109
1610
  return this.session.executeArrow(query);
1110
1611
  }
@@ -1176,6 +1677,13 @@ function buildStructLiteral(value, schema) {
1176
1677
  });
1177
1678
  return sql4`struct_pack(${sql4.join(parts, sql4.raw(", "))})`;
1178
1679
  }
1680
+ function buildMapLiteral(value, valueType) {
1681
+ const keys = Object.keys(value);
1682
+ const vals = Object.values(value);
1683
+ const keyList = buildListLiteral(keys, "TEXT");
1684
+ const valList = buildListLiteral(vals, valueType?.endsWith("[]") ? valueType.slice(0, -2) : valueType);
1685
+ return sql4`map(${keyList}, ${valList})`;
1686
+ }
1179
1687
  var duckDbList = (name, elementType) => customType({
1180
1688
  dataType() {
1181
1689
  return `${elementType}[]`;
@@ -1189,11 +1697,11 @@ var duckDbList = (name, elementType) => customType({
1189
1697
  }
1190
1698
  if (typeof value === "string") {
1191
1699
  const parsed = coerceArrayString(value);
1192
- if (parsed) {
1700
+ if (parsed !== undefined) {
1193
1701
  return parsed;
1194
1702
  }
1195
1703
  }
1196
- return [];
1704
+ return value;
1197
1705
  }
1198
1706
  })(name);
1199
1707
  var duckDbArray = (name, elementType, fixedLength) => customType({
@@ -1209,11 +1717,11 @@ var duckDbArray = (name, elementType, fixedLength) => customType({
1209
1717
  }
1210
1718
  if (typeof value === "string") {
1211
1719
  const parsed = coerceArrayString(value);
1212
- if (parsed) {
1720
+ if (parsed !== undefined) {
1213
1721
  return parsed;
1214
1722
  }
1215
1723
  }
1216
- return [];
1724
+ return value;
1217
1725
  }
1218
1726
  })(name);
1219
1727
  var duckDbMap = (name, valueType) => customType({
@@ -1298,6 +1806,21 @@ var duckDbInterval = (name) => customType({
1298
1806
  return value;
1299
1807
  }
1300
1808
  })(name);
1809
+ function shouldBindTimestamp(options) {
1810
+ const bindMode = options.bindMode ?? "auto";
1811
+ if (bindMode === "bind")
1812
+ return true;
1813
+ if (bindMode === "literal")
1814
+ return false;
1815
+ const isBun = typeof process !== "undefined" && typeof process.versions?.bun !== "undefined";
1816
+ if (isBun)
1817
+ return false;
1818
+ const forceLiteral = typeof process !== "undefined" ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS : undefined;
1819
+ if (forceLiteral && forceLiteral !== "0") {
1820
+ return false;
1821
+ }
1822
+ return true;
1823
+ }
1301
1824
  var duckDbTimestamp = (name, options = {}) => customType({
1302
1825
  dataType() {
1303
1826
  if (options.withTimezone) {
@@ -1307,12 +1830,19 @@ var duckDbTimestamp = (name, options = {}) => customType({
1307
1830
  return `TIMESTAMP${precision}`;
1308
1831
  },
1309
1832
  toDriver(value) {
1833
+ if (shouldBindTimestamp(options)) {
1834
+ return wrapTimestamp(value, options.withTimezone ?? false, options.precision);
1835
+ }
1310
1836
  const iso = value instanceof Date ? value.toISOString() : value;
1311
1837
  const normalized = iso.replace("T", " ").replace("Z", "+00");
1312
1838
  const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
1313
1839
  return sql4.raw(`${typeKeyword} '${normalized}'`);
1314
1840
  },
1315
1841
  fromDriver(value) {
1842
+ if (value && typeof value === "object" && "kind" in value && value.kind === "timestamp") {
1843
+ const wrapped = value;
1844
+ return wrapped.data instanceof Date ? wrapped.data : typeof wrapped.data === "number" || typeof wrapped.data === "bigint" ? new Date(Number(wrapped.data) / 1000) : wrapped.data;
1845
+ }
1316
1846
  if (options.mode === "string") {
1317
1847
  if (value instanceof Date) {
1318
1848
  return value.toISOString().replace("T", " ").replace("Z", "+00");
@@ -1761,6 +2291,20 @@ function mapDuckDbType(column, imports, options) {
1761
2291
  imports.pgCore.add("text");
1762
2292
  return { builder: `text(${columnName(column.name)}) /* JSON */` };
1763
2293
  }
2294
+ if (upper.startsWith("ENUM")) {
2295
+ imports.pgCore.add("text");
2296
+ const enumLiteral = raw.replace(/^ENUM\s*/i, "").trim();
2297
+ return {
2298
+ builder: `text(${columnName(column.name)}) /* ENUM ${enumLiteral} */`
2299
+ };
2300
+ }
2301
+ if (upper.startsWith("UNION")) {
2302
+ imports.pgCore.add("text");
2303
+ const unionLiteral = raw.replace(/^UNION\s*/i, "").trim();
2304
+ return {
2305
+ builder: `text(${columnName(column.name)}) /* UNION ${unionLiteral} */`
2306
+ };
2307
+ }
1764
2308
  if (upper === "INET") {
1765
2309
  imports.local.add("duckDbInet");
1766
2310
  return { builder: `duckDbInet(${columnName(column.name)})` };
@@ -1826,7 +2370,7 @@ function mapDuckDbType(column, imports, options) {
1826
2370
  }
1827
2371
  imports.pgCore.add("text");
1828
2372
  return {
1829
- builder: `text(${columnName(column.name)}) /* TODO: verify type ${upper} */`
2373
+ builder: `text(${columnName(column.name)}) /* unsupported DuckDB type: ${upper} */`
1830
2374
  };
1831
2375
  }
1832
2376
  function parseStructFields(inner) {
@@ -2049,13 +2593,19 @@ export {
2049
2593
  wrapJson,
2050
2594
  wrapBlob,
2051
2595
  wrapArray,
2596
+ toIdentifier,
2052
2597
  sumN,
2053
2598
  sumDistinctN,
2599
+ splitTopLevel,
2054
2600
  rowNumber,
2601
+ resolveRewriteArraysOption,
2602
+ resolvePrepareCacheOption,
2055
2603
  resolvePoolSize,
2056
2604
  rank,
2057
2605
  prepareParams,
2058
2606
  percentileCont,
2607
+ parseStructFields,
2608
+ parseMapValue,
2059
2609
  olap,
2060
2610
  migrate,
2061
2611
  median,
@@ -2064,9 +2614,12 @@ export {
2064
2614
  isPool,
2065
2615
  isDuckDBWrapper,
2066
2616
  introspect,
2617
+ formatLiteral,
2067
2618
  executeOnClient,
2619
+ executeInBatchesRaw,
2068
2620
  executeInBatches,
2069
2621
  executeArrowOnClient,
2622
+ executeArraysOnClient,
2070
2623
  duckDbTimestamp,
2071
2624
  duckDbTime,
2072
2625
  duckDbStruct,
@@ -2085,7 +2638,12 @@ export {
2085
2638
  denseRank,
2086
2639
  createDuckDBConnectionPool,
2087
2640
  countN,
2641
+ coerceArrayString,
2088
2642
  closeClientConnection,
2643
+ buildStructLiteral,
2644
+ buildMapLiteral,
2645
+ buildListLiteral,
2646
+ buildDefault,
2089
2647
  avgN,
2090
2648
  anyValue,
2091
2649
  POOL_PRESETS,