@leonardovida-md/drizzle-neo-duckdb 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/driver.ts
2
+ import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
2
3
  import { entityKind as entityKind3 } from "drizzle-orm/entity";
3
4
  import { DefaultLogger } from "drizzle-orm/logger";
4
5
  import { PgDatabase } from "drizzle-orm/pg-core/db";
@@ -15,83 +16,159 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
15
16
  import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
16
17
 
17
18
  // src/sql/query-rewriters.ts
18
- var tableIdPropSelectionRegex = new RegExp([
19
- `("(.+)"\\."(.+)")`,
20
- `(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
21
- ].join(""), "i");
22
- function adaptArrayOperators(query) {
23
- const operators = [
24
- { token: "@>", fn: "array_has_all" },
25
- { token: "<@", fn: "array_has_all", swap: true },
26
- { token: "&&", fn: "array_has_any" }
27
- ];
28
- const isWhitespace = (char) => char !== undefined && /\s/.test(char);
29
- const walkLeft = (source, start) => {
30
- let idx = start;
31
- while (idx >= 0 && isWhitespace(source[idx])) {
32
- idx--;
33
- }
34
- let depth = 0;
35
- let inString = false;
36
- for (;idx >= 0; idx--) {
37
- const ch = source[idx];
38
- if (ch === "'" && source[idx - 1] !== "\\") {
39
- 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;
40
36
  }
41
- if (inString)
37
+ if (char === '"') {
38
+ scrubbed += '"';
39
+ state = "double";
40
+ continue;
41
+ }
42
+ if (char === "-" && next === "-") {
43
+ scrubbed += " ";
44
+ i += 1;
45
+ state = "lineComment";
42
46
  continue;
43
- if (ch === ")" || ch === "]") {
44
- depth++;
45
- } else if (ch === "(" || ch === "[") {
46
- depth--;
47
- if (depth < 0) {
48
- return [idx + 1, source.slice(idx + 1, start + 1)];
49
- }
50
- } else if (depth === 0 && isWhitespace(ch)) {
51
- return [idx + 1, source.slice(idx + 1, start + 1)];
52
47
  }
48
+ if (char === "/" && next === "*") {
49
+ scrubbed += " ";
50
+ i += 1;
51
+ state = "blockComment";
52
+ continue;
53
+ }
54
+ scrubbed += char;
55
+ continue;
53
56
  }
54
- return [0, source.slice(0, start + 1)];
55
- };
56
- const walkRight = (source, start) => {
57
- let idx = start;
58
- while (idx < source.length && isWhitespace(source[idx])) {
59
- idx++;
60
- }
61
- let depth = 0;
62
- let inString = false;
63
- for (;idx < source.length; idx++) {
64
- const ch = source[idx];
65
- if (ch === "'" && source[idx - 1] !== "\\") {
66
- 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";
67
66
  }
68
- if (inString)
67
+ continue;
68
+ }
69
+ if (state === "double") {
70
+ if (char === '"' && next === '"') {
71
+ scrubbed += '""';
72
+ i += 1;
69
73
  continue;
70
- if (ch === "(" || ch === "[") {
71
- depth++;
72
- } else if (ch === ")" || ch === "]") {
73
- depth--;
74
- if (depth < 0) {
75
- return [idx, source.slice(start, idx)];
76
- }
77
- } 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) {
78
146
  return [idx, source.slice(start, idx)];
79
147
  }
148
+ depth = Math.max(0, depth - 1);
149
+ } else if (depth === 0 && isWhitespace(ch)) {
150
+ return [idx, source.slice(start, idx)];
80
151
  }
81
- return [source.length, source.slice(start)];
82
- };
152
+ }
153
+ return [scrubbed.length, source.slice(start)];
154
+ }
155
+ function adaptArrayOperators(query) {
83
156
  let rewritten = query;
84
- for (const { token, fn, swap } of operators) {
85
- let idx = rewritten.indexOf(token);
86
- while (idx !== -1) {
87
- const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
88
- const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
89
- const left = leftExpr.trim();
90
- const right = rightExpr.trim();
91
- const replacement = `${fn}(${swap ? right : left}, ${swap ? left : right})`;
92
- rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
93
- idx = rewritten.indexOf(token, leftStart + replacement.length);
94
- }
157
+ let scrubbed = scrubForRewrite(query);
158
+ let searchStart = 0;
159
+ while (true) {
160
+ const next = findNextOperator(scrubbed, searchStart);
161
+ if (!next)
162
+ break;
163
+ const { index, operator } = next;
164
+ const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
165
+ const [rightEnd, rightExpr] = walkRight(rewritten, scrubbed, index + operator.token.length);
166
+ const left = leftExpr.trim();
167
+ const right = rightExpr.trim();
168
+ const replacement = `${operator.fn}(${operator.swap ? right : left}, ${operator.swap ? left : right})`;
169
+ rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
170
+ scrubbed = scrubForRewrite(rewritten);
171
+ searchStart = leftStart + replacement.length;
95
172
  }
96
173
  return rewritten;
97
174
  }
@@ -317,6 +394,8 @@ import {
317
394
  timestampValue,
318
395
  timestampTZValue
319
396
  } from "@duckdb/node-api";
397
+
398
+ // src/value-wrappers-core.ts
320
399
  var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
321
400
  function isDuckDBWrapper(value) {
322
401
  return value !== null && typeof value === "object" && DUCKDB_VALUE_MARKER in value && value[DUCKDB_VALUE_MARKER] === true;
@@ -377,10 +456,18 @@ function wrapJson(data) {
377
456
  data
378
457
  };
379
458
  }
459
+
460
+ // src/value-wrappers.ts
380
461
  function dateToMicros(value) {
381
462
  if (value instanceof Date) {
382
463
  return BigInt(value.getTime()) * 1000n;
383
464
  }
465
+ if (typeof value === "bigint") {
466
+ return value;
467
+ }
468
+ if (typeof value === "number") {
469
+ return BigInt(Math.trunc(value)) * 1000n;
470
+ }
384
471
  let normalized = value;
385
472
  if (!value.includes("T") && value.includes(" ")) {
386
473
  normalized = value.replace(" ", "T");
@@ -434,6 +521,9 @@ function wrapperToNodeApiValue(wrapper, toValue) {
434
521
  }
435
522
 
436
523
  // src/client.ts
524
+ function isPool(client) {
525
+ return typeof client.acquire === "function";
526
+ }
437
527
  function isPgArrayLiteral(value) {
438
528
  return value.startsWith("{") && value.endsWith("}");
439
529
  }
@@ -445,18 +535,19 @@ function parsePgArrayLiteral(value) {
445
535
  return value;
446
536
  }
447
537
  }
448
- var warnedArrayLiteral = false;
449
538
  function prepareParams(params, options = {}) {
450
539
  return params.map((param) => {
451
- if (typeof param === "string" && isPgArrayLiteral(param)) {
452
- if (options.rejectStringArrayLiterals) {
453
- throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
454
- }
455
- if (!warnedArrayLiteral && options.warnOnStringArrayLiteral) {
456
- warnedArrayLiteral = true;
457
- options.warnOnStringArrayLiteral();
540
+ if (typeof param === "string") {
541
+ const trimmed = param.trim();
542
+ if (trimmed && isPgArrayLiteral(trimmed)) {
543
+ if (options.rejectStringArrayLiterals) {
544
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
545
+ }
546
+ if (options.warnOnStringArrayLiteral) {
547
+ options.warnOnStringArrayLiteral();
548
+ }
549
+ return parsePgArrayLiteral(trimmed);
458
550
  }
459
- return parsePgArrayLiteral(param);
460
551
  }
461
552
  return param;
462
553
  });
@@ -510,6 +601,14 @@ async function closeClientConnection(connection) {
510
601
  }
511
602
  }
512
603
  async function executeOnClient(client, query, params) {
604
+ if (isPool(client)) {
605
+ const connection = await client.acquire();
606
+ try {
607
+ return await executeOnClient(connection, query, params);
608
+ } finally {
609
+ await client.release(connection);
610
+ }
611
+ }
513
612
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
514
613
  const result = await client.run(query, values);
515
614
  const rows = await result.getRowsJS();
@@ -518,6 +617,15 @@ async function executeOnClient(client, query, params) {
518
617
  return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
519
618
  }
520
619
  async function* executeInBatches(client, query, params, options = {}) {
620
+ if (isPool(client)) {
621
+ const connection = await client.acquire();
622
+ try {
623
+ yield* executeInBatches(connection, query, params, options);
624
+ return;
625
+ } finally {
626
+ await client.release(connection);
627
+ }
628
+ }
521
629
  const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
522
630
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
523
631
  const result = await client.stream(query, values);
@@ -539,6 +647,14 @@ async function* executeInBatches(client, query, params, options = {}) {
539
647
  }
540
648
  }
541
649
  async function executeArrowOnClient(client, query, params) {
650
+ if (isPool(client)) {
651
+ const connection = await client.acquire();
652
+ try {
653
+ return await executeArrowOnClient(connection, query, params);
654
+ } finally {
655
+ await client.release(connection);
656
+ }
657
+ }
542
658
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
543
659
  const result = await client.run(query, values);
544
660
  const maybeArrow = result.toArrow ?? result.getArrowTable;
@@ -549,6 +665,13 @@ async function executeArrowOnClient(client, query, params) {
549
665
  }
550
666
 
551
667
  // src/session.ts
668
+ function isSavepointSyntaxError(error) {
669
+ if (!(error instanceof Error) || !error.message) {
670
+ return false;
671
+ }
672
+ return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
673
+ }
674
+
552
675
  class DuckDBPreparedQuery extends PgPreparedQuery {
553
676
  client;
554
677
  dialect;
@@ -613,6 +736,7 @@ class DuckDBSession extends PgSession {
613
736
  rewriteArrays;
614
737
  rejectStringArrayLiterals;
615
738
  hasWarnedArrayLiteral = false;
739
+ rollbackOnly = false;
616
740
  constructor(client, dialect, schema, options = {}) {
617
741
  super(dialect);
618
742
  this.client = client;
@@ -626,17 +750,46 @@ class DuckDBSession extends PgSession {
626
750
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
627
751
  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);
628
752
  }
629
- async transaction(transaction) {
630
- const session = new DuckDBSession(this.client, this.dialect, this.schema, this.options);
753
+ execute(query) {
754
+ this.dialect.resetPgJsonFlag();
755
+ return super.execute(query);
756
+ }
757
+ all(query) {
758
+ this.dialect.resetPgJsonFlag();
759
+ return super.all(query);
760
+ }
761
+ async transaction(transaction, config) {
762
+ let pinnedConnection;
763
+ let pool;
764
+ let clientForTx = this.client;
765
+ if (isPool(this.client)) {
766
+ pool = this.client;
767
+ pinnedConnection = await pool.acquire();
768
+ clientForTx = pinnedConnection;
769
+ }
770
+ const session = new DuckDBSession(clientForTx, this.dialect, this.schema, this.options);
631
771
  const tx = new DuckDBTransaction(this.dialect, session, this.schema);
632
- await tx.execute(sql`BEGIN TRANSACTION;`);
633
772
  try {
634
- const result = await transaction(tx);
635
- await tx.execute(sql`commit`);
636
- return result;
637
- } catch (error) {
638
- await tx.execute(sql`rollback`);
639
- throw error;
773
+ await tx.execute(sql`BEGIN TRANSACTION;`);
774
+ if (config) {
775
+ await tx.setTransaction(config);
776
+ }
777
+ try {
778
+ const result = await transaction(tx);
779
+ if (session.isRollbackOnly()) {
780
+ await tx.execute(sql`rollback`);
781
+ throw new TransactionRollbackError;
782
+ }
783
+ await tx.execute(sql`commit`);
784
+ return result;
785
+ } catch (error) {
786
+ await tx.execute(sql`rollback`);
787
+ throw error;
788
+ }
789
+ } finally {
790
+ if (pinnedConnection && pool) {
791
+ await pool.release(pinnedConnection);
792
+ }
640
793
  }
641
794
  }
642
795
  warnOnStringArrayLiteral = (query) => {
@@ -648,7 +801,9 @@ class DuckDBSession extends PgSession {
648
801
  query: ${query}`, []);
649
802
  };
650
803
  executeBatches(query, options = {}) {
804
+ this.dialect.resetPgJsonFlag();
651
805
  const builtQuery = this.dialect.sqlToQuery(query);
806
+ this.dialect.assertNoPgJsonColumns();
652
807
  const params = prepareParams(builtQuery.params, {
653
808
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
654
809
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
@@ -661,7 +816,9 @@ query: ${query}`, []);
661
816
  return executeInBatches(this.client, rewrittenQuery, params, options);
662
817
  }
663
818
  async executeArrow(query) {
819
+ this.dialect.resetPgJsonFlag();
664
820
  const builtQuery = this.dialect.sqlToQuery(query);
821
+ this.dialect.assertNoPgJsonColumns();
665
822
  const params = prepareParams(builtQuery.params, {
666
823
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
667
824
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
@@ -673,6 +830,12 @@ query: ${query}`, []);
673
830
  this.logger.logQuery(rewrittenQuery, params);
674
831
  return executeArrowOnClient(this.client, rewrittenQuery, params);
675
832
  }
833
+ markRollbackOnly() {
834
+ this.rollbackOnly = true;
835
+ }
836
+ isRollbackOnly() {
837
+ return this.rollbackOnly;
838
+ }
676
839
  }
677
840
 
678
841
  class DuckDBTransaction extends PgTransaction {
@@ -703,8 +866,46 @@ class DuckDBTransaction extends PgTransaction {
703
866
  return this.session.executeArrow(query);
704
867
  }
705
868
  async transaction(transaction) {
706
- const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
707
- return transaction(nestedTx);
869
+ const internals = this;
870
+ const savepoint = `drizzle_savepoint_${this.nestedIndex + 1}`;
871
+ const savepointSql = sql.raw(`savepoint ${savepoint}`);
872
+ const releaseSql = sql.raw(`release savepoint ${savepoint}`);
873
+ const rollbackSql = sql.raw(`rollback to savepoint ${savepoint}`);
874
+ const nestedTx = new DuckDBTransaction(internals.dialect, internals.session, this.schema, this.nestedIndex + 1);
875
+ if (internals.dialect.areSavepointsUnsupported()) {
876
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
877
+ }
878
+ let createdSavepoint = false;
879
+ try {
880
+ await internals.session.execute(savepointSql);
881
+ internals.dialect.markSavepointsSupported();
882
+ createdSavepoint = true;
883
+ } catch (error) {
884
+ if (!isSavepointSyntaxError(error)) {
885
+ throw error;
886
+ }
887
+ internals.dialect.markSavepointsUnsupported();
888
+ return this.runNestedWithoutSavepoint(transaction, nestedTx, internals);
889
+ }
890
+ try {
891
+ const result = await transaction(nestedTx);
892
+ if (createdSavepoint) {
893
+ await internals.session.execute(releaseSql);
894
+ }
895
+ return result;
896
+ } catch (error) {
897
+ if (createdSavepoint) {
898
+ await internals.session.execute(rollbackSql);
899
+ }
900
+ internals.session.markRollbackOnly();
901
+ throw error;
902
+ }
903
+ }
904
+ runNestedWithoutSavepoint(transaction, nestedTx, internals) {
905
+ return transaction(nestedTx).catch((error) => {
906
+ internals.session.markRollbackOnly();
907
+ throw error;
908
+ });
708
909
  }
709
910
  }
710
911
  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.";
@@ -726,15 +927,30 @@ import {
726
927
  import {
727
928
  sql as sql2
728
929
  } from "drizzle-orm";
729
-
730
930
  class DuckDBDialect extends PgDialect {
731
931
  static [entityKind2] = "DuckDBPgDialect";
732
932
  hasPgJsonColumn = false;
933
+ savepointsSupported = 0 /* Unknown */;
934
+ resetPgJsonFlag() {
935
+ this.hasPgJsonColumn = false;
936
+ }
937
+ markPgJsonDetected() {
938
+ this.hasPgJsonColumn = true;
939
+ }
733
940
  assertNoPgJsonColumns() {
734
941
  if (this.hasPgJsonColumn) {
735
- throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDBs native JSON type.");
942
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
736
943
  }
737
944
  }
945
+ areSavepointsUnsupported() {
946
+ return this.savepointsSupported === 2 /* No */;
947
+ }
948
+ markSavepointsSupported() {
949
+ this.savepointsSupported = 1 /* Yes */;
950
+ }
951
+ markSavepointsUnsupported() {
952
+ this.savepointsSupported = 2 /* No */;
953
+ }
738
954
  async migrate(migrations, session, config) {
739
955
  const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
740
956
  const migrationsSchema = migrationConfig.migrationsSchema ?? "drizzle";
@@ -771,8 +987,8 @@ class DuckDBDialect extends PgDialect {
771
987
  }
772
988
  prepareTyping(encoder) {
773
989
  if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
774
- this.hasPgJsonColumn = true;
775
- return "none";
990
+ this.markPgJsonDetected();
991
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type.");
776
992
  } else if (is2(encoder, PgNumeric)) {
777
993
  return "decimal";
778
994
  } else if (is2(encoder, PgTime2)) {
@@ -884,6 +1100,179 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
884
1100
  }
885
1101
  }
886
1102
 
1103
+ // src/pool.ts
1104
+ import { DuckDBConnection } from "@duckdb/node-api";
1105
+ var POOL_PRESETS = {
1106
+ pulse: 4,
1107
+ standard: 6,
1108
+ jumbo: 8,
1109
+ mega: 12,
1110
+ giga: 16,
1111
+ local: 8,
1112
+ memory: 4
1113
+ };
1114
+ function resolvePoolSize(pool) {
1115
+ if (pool === false)
1116
+ return false;
1117
+ if (pool === undefined)
1118
+ return 4;
1119
+ if (typeof pool === "string")
1120
+ return POOL_PRESETS[pool];
1121
+ return pool.size ?? 4;
1122
+ }
1123
+ function createDuckDBConnectionPool(instance, options = {}) {
1124
+ const size = options.size && options.size > 0 ? options.size : 4;
1125
+ const acquireTimeout = options.acquireTimeout ?? 30000;
1126
+ const maxWaitingRequests = options.maxWaitingRequests ?? 100;
1127
+ const maxLifetimeMs = options.maxLifetimeMs;
1128
+ const idleTimeoutMs = options.idleTimeoutMs;
1129
+ const metadata = new WeakMap;
1130
+ const idle = [];
1131
+ const waiting = [];
1132
+ let total = 0;
1133
+ let closed = false;
1134
+ let pendingAcquires = 0;
1135
+ const shouldRecycle = (conn, now) => {
1136
+ if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
1137
+ return true;
1138
+ }
1139
+ if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
1140
+ return true;
1141
+ }
1142
+ return false;
1143
+ };
1144
+ const acquire = async () => {
1145
+ if (closed) {
1146
+ throw new Error("DuckDB connection pool is closed");
1147
+ }
1148
+ while (idle.length > 0) {
1149
+ const pooled = idle.pop();
1150
+ const now = Date.now();
1151
+ if (shouldRecycle(pooled, now)) {
1152
+ await closeClientConnection(pooled.connection);
1153
+ total = Math.max(0, total - 1);
1154
+ metadata.delete(pooled.connection);
1155
+ continue;
1156
+ }
1157
+ pooled.lastUsedAt = now;
1158
+ metadata.set(pooled.connection, {
1159
+ createdAt: pooled.createdAt,
1160
+ lastUsedAt: pooled.lastUsedAt
1161
+ });
1162
+ return pooled.connection;
1163
+ }
1164
+ if (total < size) {
1165
+ pendingAcquires += 1;
1166
+ total += 1;
1167
+ try {
1168
+ const connection = await DuckDBConnection.create(instance);
1169
+ if (closed) {
1170
+ await closeClientConnection(connection);
1171
+ total -= 1;
1172
+ throw new Error("DuckDB connection pool is closed");
1173
+ }
1174
+ const now = Date.now();
1175
+ metadata.set(connection, { createdAt: now, lastUsedAt: now });
1176
+ return connection;
1177
+ } catch (error) {
1178
+ total -= 1;
1179
+ throw error;
1180
+ } finally {
1181
+ pendingAcquires -= 1;
1182
+ }
1183
+ }
1184
+ if (waiting.length >= maxWaitingRequests) {
1185
+ throw new Error(`DuckDB connection pool queue is full (max ${maxWaitingRequests} waiting requests)`);
1186
+ }
1187
+ return await new Promise((resolve, reject) => {
1188
+ const timeoutId = setTimeout(() => {
1189
+ const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
1190
+ if (idx !== -1) {
1191
+ waiting.splice(idx, 1);
1192
+ }
1193
+ reject(new Error(`DuckDB connection pool acquire timeout after ${acquireTimeout}ms`));
1194
+ }, acquireTimeout);
1195
+ waiting.push({ resolve, reject, timeoutId });
1196
+ });
1197
+ };
1198
+ const release = async (connection) => {
1199
+ const waiter = waiting.shift();
1200
+ if (waiter) {
1201
+ clearTimeout(waiter.timeoutId);
1202
+ const now2 = Date.now();
1203
+ const meta = metadata.get(connection) ?? { createdAt: now2, lastUsedAt: now2 };
1204
+ const expired = maxLifetimeMs !== undefined && now2 - meta.createdAt >= maxLifetimeMs;
1205
+ if (closed) {
1206
+ await closeClientConnection(connection);
1207
+ total = Math.max(0, total - 1);
1208
+ metadata.delete(connection);
1209
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1210
+ return;
1211
+ }
1212
+ if (expired) {
1213
+ await closeClientConnection(connection);
1214
+ total = Math.max(0, total - 1);
1215
+ metadata.delete(connection);
1216
+ try {
1217
+ const replacement = await acquire();
1218
+ waiter.resolve(replacement);
1219
+ } catch (error) {
1220
+ waiter.reject(error);
1221
+ }
1222
+ return;
1223
+ }
1224
+ meta.lastUsedAt = now2;
1225
+ metadata.set(connection, meta);
1226
+ waiter.resolve(connection);
1227
+ return;
1228
+ }
1229
+ if (closed) {
1230
+ await closeClientConnection(connection);
1231
+ metadata.delete(connection);
1232
+ total = Math.max(0, total - 1);
1233
+ return;
1234
+ }
1235
+ const now = Date.now();
1236
+ const existingMeta = metadata.get(connection) ?? { createdAt: now, lastUsedAt: now };
1237
+ existingMeta.lastUsedAt = now;
1238
+ metadata.set(connection, existingMeta);
1239
+ if (maxLifetimeMs !== undefined && now - existingMeta.createdAt >= maxLifetimeMs) {
1240
+ await closeClientConnection(connection);
1241
+ total -= 1;
1242
+ metadata.delete(connection);
1243
+ return;
1244
+ }
1245
+ idle.push({
1246
+ connection,
1247
+ createdAt: existingMeta.createdAt,
1248
+ lastUsedAt: existingMeta.lastUsedAt
1249
+ });
1250
+ };
1251
+ const close = async () => {
1252
+ closed = true;
1253
+ const waiters = waiting.splice(0, waiting.length);
1254
+ for (const waiter of waiters) {
1255
+ clearTimeout(waiter.timeoutId);
1256
+ waiter.reject(new Error("DuckDB connection pool is closed"));
1257
+ }
1258
+ const toClose = idle.splice(0, idle.length);
1259
+ await Promise.allSettled(toClose.map((item) => closeClientConnection(item.connection)));
1260
+ total = Math.max(0, total - toClose.length);
1261
+ toClose.forEach((item) => metadata.delete(item.connection));
1262
+ const maxWait = 5000;
1263
+ const start = Date.now();
1264
+ while (pendingAcquires > 0 && Date.now() - start < maxWait) {
1265
+ await new Promise((r) => setTimeout(r, 10));
1266
+ }
1267
+ };
1268
+ return {
1269
+ acquire,
1270
+ release,
1271
+ close,
1272
+ size
1273
+ };
1274
+ }
1275
+
887
1276
  // src/driver.ts
888
1277
  class DuckDBDriver {
889
1278
  client;
@@ -903,7 +1292,14 @@ class DuckDBDriver {
903
1292
  });
904
1293
  }
905
1294
  }
906
- function drizzle(client, config = {}) {
1295
+ function isConfigObject(data) {
1296
+ if (typeof data !== "object" || data === null)
1297
+ return false;
1298
+ if (data.constructor?.name !== "Object")
1299
+ return false;
1300
+ return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
1301
+ }
1302
+ function createFromClient(client, config = {}, instance) {
907
1303
  const dialect = new DuckDBDialect;
908
1304
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
909
1305
  let schema;
@@ -921,17 +1317,71 @@ function drizzle(client, config = {}) {
921
1317
  rejectStringArrayLiterals: config.rejectStringArrayLiterals
922
1318
  });
923
1319
  const session = driver.createSession(schema);
924
- return new DuckDBDatabase(dialect, session, schema);
1320
+ const db = new DuckDBDatabase(dialect, session, schema, client, instance);
1321
+ return db;
1322
+ }
1323
+ async function createFromConnectionString(path, instanceOptions, config = {}) {
1324
+ const instance = await DuckDBInstance2.create(path, instanceOptions);
1325
+ const poolSize = resolvePoolSize(config.pool);
1326
+ if (poolSize === false) {
1327
+ const connection = await instance.connect();
1328
+ return createFromClient(connection, config, instance);
1329
+ }
1330
+ const pool = createDuckDBConnectionPool(instance, { size: poolSize });
1331
+ return createFromClient(pool, config, instance);
1332
+ }
1333
+ function drizzle(clientOrConfigOrPath, config) {
1334
+ if (typeof clientOrConfigOrPath === "string") {
1335
+ return createFromConnectionString(clientOrConfigOrPath, undefined, config);
1336
+ }
1337
+ if (isConfigObject(clientOrConfigOrPath)) {
1338
+ const configObj = clientOrConfigOrPath;
1339
+ if ("connection" in configObj) {
1340
+ const connConfig = configObj;
1341
+ const { connection, ...restConfig } = connConfig;
1342
+ if (typeof connection === "string") {
1343
+ return createFromConnectionString(connection, undefined, restConfig);
1344
+ }
1345
+ return createFromConnectionString(connection.path, connection.options, restConfig);
1346
+ }
1347
+ if ("client" in configObj) {
1348
+ const clientConfig = configObj;
1349
+ const { client: clientValue, ...restConfig } = clientConfig;
1350
+ return createFromClient(clientValue, restConfig);
1351
+ }
1352
+ throw new Error("Invalid drizzle config: either connection or client must be provided");
1353
+ }
1354
+ return createFromClient(clientOrConfigOrPath, config);
925
1355
  }
926
1356
 
927
1357
  class DuckDBDatabase extends PgDatabase {
928
1358
  dialect;
929
1359
  session;
930
1360
  static [entityKind3] = "DuckDBDatabase";
931
- constructor(dialect, session, schema) {
1361
+ $client;
1362
+ $instance;
1363
+ constructor(dialect, session, schema, client, instance) {
932
1364
  super(dialect, session, schema);
933
1365
  this.dialect = dialect;
934
1366
  this.session = session;
1367
+ this.$client = client;
1368
+ this.$instance = instance;
1369
+ }
1370
+ async close() {
1371
+ if (isPool(this.$client) && this.$client.close) {
1372
+ await this.$client.close();
1373
+ }
1374
+ if (!isPool(this.$client)) {
1375
+ await closeClientConnection(this.$client);
1376
+ }
1377
+ if (this.$instance) {
1378
+ const maybeClosable = this.$instance;
1379
+ if (typeof maybeClosable.close === "function") {
1380
+ await maybeClosable.close();
1381
+ } else if (typeof maybeClosable.closeSync === "function") {
1382
+ maybeClosable.closeSync();
1383
+ }
1384
+ }
935
1385
  }
936
1386
  select(fields) {
937
1387
  const selectedFields = fields ? aliasFields(fields) : undefined;
@@ -1015,6 +1465,13 @@ function buildStructLiteral(value, schema) {
1015
1465
  });
1016
1466
  return sql4`struct_pack(${sql4.join(parts, sql4.raw(", "))})`;
1017
1467
  }
1468
+ function buildMapLiteral(value, valueType) {
1469
+ const keys = Object.keys(value);
1470
+ const vals = Object.values(value);
1471
+ const keyList = buildListLiteral(keys, "TEXT");
1472
+ const valList = buildListLiteral(vals, valueType?.endsWith("[]") ? valueType.slice(0, -2) : valueType);
1473
+ return sql4`map(${keyList}, ${valList})`;
1474
+ }
1018
1475
  var duckDbList = (name, elementType) => customType({
1019
1476
  dataType() {
1020
1477
  return `${elementType}[]`;
@@ -1028,11 +1485,11 @@ var duckDbList = (name, elementType) => customType({
1028
1485
  }
1029
1486
  if (typeof value === "string") {
1030
1487
  const parsed = coerceArrayString(value);
1031
- if (parsed) {
1488
+ if (parsed !== undefined) {
1032
1489
  return parsed;
1033
1490
  }
1034
1491
  }
1035
- return [];
1492
+ return value;
1036
1493
  }
1037
1494
  })(name);
1038
1495
  var duckDbArray = (name, elementType, fixedLength) => customType({
@@ -1048,11 +1505,11 @@ var duckDbArray = (name, elementType, fixedLength) => customType({
1048
1505
  }
1049
1506
  if (typeof value === "string") {
1050
1507
  const parsed = coerceArrayString(value);
1051
- if (parsed) {
1508
+ if (parsed !== undefined) {
1052
1509
  return parsed;
1053
1510
  }
1054
1511
  }
1055
- return [];
1512
+ return value;
1056
1513
  }
1057
1514
  })(name);
1058
1515
  var duckDbMap = (name, valueType) => customType({
@@ -1220,7 +1677,7 @@ async function migrate(db, config) {
1220
1677
  // src/introspect.ts
1221
1678
  import { sql as sql5 } from "drizzle-orm";
1222
1679
  var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
1223
- var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
1680
+ var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
1224
1681
  async function introspect(db, opts = {}) {
1225
1682
  const database = await resolveDatabase(db, opts.database, opts.allDatabases);
1226
1683
  const schemas = await resolveSchemas(db, database, opts.schemas);
@@ -1556,6 +2013,22 @@ function mapDuckDbType(column, imports, options) {
1556
2013
  imports.pgCore.add("doublePrecision");
1557
2014
  return { builder: `doublePrecision(${columnName(column.name)})` };
1558
2015
  }
2016
+ const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
2017
+ if (arrayMatch) {
2018
+ imports.local.add("duckDbArray");
2019
+ const [, base, length] = arrayMatch;
2020
+ return {
2021
+ builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
2022
+ };
2023
+ }
2024
+ const listMatch = /^(.*)\[\]$/.exec(upper);
2025
+ if (listMatch) {
2026
+ imports.local.add("duckDbList");
2027
+ const [, base] = listMatch;
2028
+ return {
2029
+ builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
2030
+ };
2031
+ }
1559
2032
  if (upper.startsWith("CHAR(") || upper === "CHAR") {
1560
2033
  imports.pgCore.add("char");
1561
2034
  const length = column.characterLength;
@@ -1584,6 +2057,20 @@ function mapDuckDbType(column, imports, options) {
1584
2057
  imports.pgCore.add("text");
1585
2058
  return { builder: `text(${columnName(column.name)}) /* JSON */` };
1586
2059
  }
2060
+ if (upper.startsWith("ENUM")) {
2061
+ imports.pgCore.add("text");
2062
+ const enumLiteral = raw.replace(/^ENUM\s*/i, "").trim();
2063
+ return {
2064
+ builder: `text(${columnName(column.name)}) /* ENUM ${enumLiteral} */`
2065
+ };
2066
+ }
2067
+ if (upper.startsWith("UNION")) {
2068
+ imports.pgCore.add("text");
2069
+ const unionLiteral = raw.replace(/^UNION\s*/i, "").trim();
2070
+ return {
2071
+ builder: `text(${columnName(column.name)}) /* UNION ${unionLiteral} */`
2072
+ };
2073
+ }
1587
2074
  if (upper === "INET") {
1588
2075
  imports.local.add("duckDbInet");
1589
2076
  return { builder: `duckDbInet(${columnName(column.name)})` };
@@ -1596,22 +2083,6 @@ function mapDuckDbType(column, imports, options) {
1596
2083
  imports.local.add("duckDbBlob");
1597
2084
  return { builder: `duckDbBlob(${columnName(column.name)})` };
1598
2085
  }
1599
- const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
1600
- if (arrayMatch) {
1601
- imports.local.add("duckDbArray");
1602
- const [, base, length] = arrayMatch;
1603
- return {
1604
- builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
1605
- };
1606
- }
1607
- const listMatch = /^(.*)\[\]$/.exec(upper);
1608
- if (listMatch) {
1609
- imports.local.add("duckDbList");
1610
- const [, base] = listMatch;
1611
- return {
1612
- builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
1613
- };
1614
- }
1615
2086
  if (upper.startsWith("STRUCT")) {
1616
2087
  imports.local.add("duckDbStruct");
1617
2088
  const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
@@ -1665,7 +2136,7 @@ function mapDuckDbType(column, imports, options) {
1665
2136
  }
1666
2137
  imports.pgCore.add("text");
1667
2138
  return {
1668
- builder: `text(${columnName(column.name)}) /* TODO: verify type ${upper} */`
2139
+ builder: `text(${columnName(column.name)}) /* unsupported DuckDB type: ${upper} */`
1669
2140
  };
1670
2141
  }
1671
2142
  function parseStructFields(inner) {
@@ -1888,19 +2359,26 @@ export {
1888
2359
  wrapJson,
1889
2360
  wrapBlob,
1890
2361
  wrapArray,
2362
+ toIdentifier,
1891
2363
  sumN,
1892
2364
  sumDistinctN,
2365
+ splitTopLevel,
1893
2366
  rowNumber,
2367
+ resolvePoolSize,
1894
2368
  rank,
1895
2369
  prepareParams,
1896
2370
  percentileCont,
2371
+ parseStructFields,
2372
+ parseMapValue,
1897
2373
  olap,
1898
2374
  migrate,
1899
2375
  median,
1900
2376
  lead,
1901
2377
  lag,
2378
+ isPool,
1902
2379
  isDuckDBWrapper,
1903
2380
  introspect,
2381
+ formatLiteral,
1904
2382
  executeOnClient,
1905
2383
  executeInBatches,
1906
2384
  executeArrowOnClient,
@@ -1920,15 +2398,23 @@ export {
1920
2398
  duckDbArray,
1921
2399
  drizzle,
1922
2400
  denseRank,
2401
+ createDuckDBConnectionPool,
1923
2402
  countN,
2403
+ coerceArrayString,
1924
2404
  closeClientConnection,
2405
+ buildStructLiteral,
2406
+ buildMapLiteral,
2407
+ buildListLiteral,
2408
+ buildDefault,
1925
2409
  avgN,
1926
2410
  anyValue,
2411
+ POOL_PRESETS,
1927
2412
  OlapBuilder,
1928
2413
  DuckDBTransaction,
1929
2414
  DuckDBSession,
1930
2415
  DuckDBPreparedQuery,
1931
2416
  DuckDBDriver,
1932
2417
  DuckDBDatabase,
1933
- DUCKDB_VALUE_MARKER
2418
+ DUCKDB_VALUE_MARKER,
2419
+ DEFAULT_IMPORT_BASE
1934
2420
  };