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

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
@@ -153,6 +153,9 @@ function walkRight(source, scrubbed, start) {
153
153
  return [scrubbed.length, source.slice(start)];
154
154
  }
155
155
  function adaptArrayOperators(query) {
156
+ if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
157
+ return query;
158
+ }
156
159
  let rewritten = query;
157
160
  let scrubbed = scrubForRewrite(query);
158
161
  let searchStart = 0;
@@ -172,6 +175,328 @@ function adaptArrayOperators(query) {
172
175
  }
173
176
  return rewritten;
174
177
  }
178
+ function extractQuotedIdentifier(original, start) {
179
+ if (original[start] !== '"') {
180
+ return null;
181
+ }
182
+ let pos = start + 1;
183
+ while (pos < original.length && original[pos] !== '"') {
184
+ if (original[pos] === '"' && original[pos + 1] === '"') {
185
+ pos += 2;
186
+ continue;
187
+ }
188
+ pos++;
189
+ }
190
+ if (pos >= original.length) {
191
+ return null;
192
+ }
193
+ return {
194
+ name: original.slice(start + 1, pos),
195
+ end: pos + 1
196
+ };
197
+ }
198
+ function findMainFromClause(scrubbed) {
199
+ const lowerScrubbed = scrubbed.toLowerCase();
200
+ let searchStart = 0;
201
+ const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
202
+ if (withMatch) {
203
+ let depth = 0;
204
+ let pos = withMatch.index + withMatch[0].length;
205
+ while (pos < scrubbed.length) {
206
+ const char = scrubbed[pos];
207
+ if (char === "(") {
208
+ depth++;
209
+ } else if (char === ")") {
210
+ depth--;
211
+ } else if (depth === 0) {
212
+ const remaining = lowerScrubbed.slice(pos);
213
+ if (/^\s*select\s+/i.test(remaining)) {
214
+ searchStart = pos;
215
+ break;
216
+ }
217
+ }
218
+ pos++;
219
+ }
220
+ }
221
+ const fromPattern = /\bfrom\s+/gi;
222
+ fromPattern.lastIndex = searchStart;
223
+ const fromMatch = fromPattern.exec(lowerScrubbed);
224
+ return fromMatch ? fromMatch.index + fromMatch[0].length : -1;
225
+ }
226
+ function parseTableSources(original, scrubbed) {
227
+ const sources = [];
228
+ const lowerScrubbed = scrubbed.toLowerCase();
229
+ const fromPos = findMainFromClause(scrubbed);
230
+ if (fromPos < 0) {
231
+ return sources;
232
+ }
233
+ const fromTable = parseTableRef(original, scrubbed, fromPos);
234
+ if (fromTable) {
235
+ sources.push(fromTable);
236
+ }
237
+ const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+/gi;
238
+ joinPattern.lastIndex = fromPos;
239
+ let joinMatch;
240
+ while ((joinMatch = joinPattern.exec(lowerScrubbed)) !== null) {
241
+ const tableStart = joinMatch.index + joinMatch[0].length;
242
+ const joinTable = parseTableRef(original, scrubbed, tableStart);
243
+ if (joinTable) {
244
+ sources.push(joinTable);
245
+ }
246
+ }
247
+ return sources;
248
+ }
249
+ function parseTableRef(original, scrubbed, start) {
250
+ let pos = start;
251
+ while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
252
+ pos++;
253
+ }
254
+ if (original[pos] !== '"') {
255
+ return null;
256
+ }
257
+ const nameStart = pos;
258
+ const firstIdent = extractQuotedIdentifier(original, pos);
259
+ if (!firstIdent) {
260
+ return null;
261
+ }
262
+ let name = firstIdent.name;
263
+ pos = firstIdent.end;
264
+ let afterName = pos;
265
+ while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
266
+ afterName++;
267
+ }
268
+ if (scrubbed[afterName] === ".") {
269
+ afterName++;
270
+ while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
271
+ afterName++;
272
+ }
273
+ if (original[afterName] === '"') {
274
+ const tableIdent = extractQuotedIdentifier(original, afterName);
275
+ if (tableIdent) {
276
+ name = tableIdent.name;
277
+ pos = tableIdent.end;
278
+ }
279
+ }
280
+ }
281
+ let alias;
282
+ let aliasPos = pos;
283
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
284
+ aliasPos++;
285
+ }
286
+ const afterTable = scrubbed.slice(aliasPos).toLowerCase();
287
+ if (afterTable.startsWith("as ")) {
288
+ aliasPos += 3;
289
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
290
+ aliasPos++;
291
+ }
292
+ }
293
+ if (original[aliasPos] === '"' && !afterTable.startsWith("on ") && !afterTable.startsWith("left ") && !afterTable.startsWith("right ") && !afterTable.startsWith("inner ") && !afterTable.startsWith("full ") && !afterTable.startsWith("cross ") && !afterTable.startsWith("join ") && !afterTable.startsWith("where ") && !afterTable.startsWith("group ") && !afterTable.startsWith("order ") && !afterTable.startsWith("limit ") && !afterTable.startsWith("as ")) {
294
+ const aliasIdent = extractQuotedIdentifier(original, aliasPos);
295
+ if (aliasIdent) {
296
+ alias = aliasIdent.name;
297
+ }
298
+ }
299
+ return {
300
+ name,
301
+ alias,
302
+ position: nameStart
303
+ };
304
+ }
305
+ function findJoinClauses(original, scrubbed, sources) {
306
+ const clauses = [];
307
+ const lowerScrubbed = scrubbed.toLowerCase();
308
+ const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
309
+ let match;
310
+ let sourceIndex = 1;
311
+ while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
312
+ const joinType = (match[1] || "").trim().toLowerCase();
313
+ const joinKeywordEnd = match.index + (match[1] || "").length + "join".length;
314
+ let tableStart = joinKeywordEnd;
315
+ while (tableStart < original.length && isWhitespace(original[tableStart])) {
316
+ tableStart++;
317
+ }
318
+ const tableIdent = extractQuotedIdentifier(original, tableStart);
319
+ if (!tableIdent)
320
+ continue;
321
+ let tableName = tableIdent.name;
322
+ let afterTable = tableIdent.end;
323
+ let checkPos = afterTable;
324
+ while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
325
+ checkPos++;
326
+ }
327
+ if (scrubbed[checkPos] === ".") {
328
+ checkPos++;
329
+ while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
330
+ checkPos++;
331
+ }
332
+ const realTableIdent = extractQuotedIdentifier(original, checkPos);
333
+ if (realTableIdent) {
334
+ tableName = realTableIdent.name;
335
+ afterTable = realTableIdent.end;
336
+ }
337
+ }
338
+ let tableAlias;
339
+ let aliasPos = afterTable;
340
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
341
+ aliasPos++;
342
+ }
343
+ const afterTableStr = scrubbed.slice(aliasPos).toLowerCase();
344
+ if (afterTableStr.startsWith("as ")) {
345
+ aliasPos += 3;
346
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
347
+ aliasPos++;
348
+ }
349
+ }
350
+ if (original[aliasPos] === '"' && !afterTableStr.startsWith("on ")) {
351
+ const aliasIdent = extractQuotedIdentifier(original, aliasPos);
352
+ if (aliasIdent) {
353
+ tableAlias = aliasIdent.name;
354
+ }
355
+ }
356
+ const onStart = match.index + match[0].length;
357
+ const endPattern = /\b(left\s+join|right\s+join|inner\s+join|full\s+join|cross\s+join|join|where|group\s+by|order\s+by|limit|$)/i;
358
+ const remaining = lowerScrubbed.slice(onStart);
359
+ const endMatch = endPattern.exec(remaining);
360
+ const onEnd = endMatch ? onStart + endMatch.index : scrubbed.length;
361
+ let leftSource = "";
362
+ if (sourceIndex > 0 && sourceIndex <= sources.length) {
363
+ const prev = sources[sourceIndex - 1];
364
+ leftSource = prev?.alias || prev?.name || "";
365
+ }
366
+ const rightSource = tableAlias || tableName;
367
+ clauses.push({
368
+ joinType,
369
+ tableName,
370
+ tableAlias,
371
+ onStart,
372
+ onEnd,
373
+ leftSource,
374
+ rightSource
375
+ });
376
+ sourceIndex++;
377
+ }
378
+ return clauses;
379
+ }
380
+ function qualifyJoinColumns(query) {
381
+ const lowerQuery = query.toLowerCase();
382
+ if (!lowerQuery.includes("join")) {
383
+ return query;
384
+ }
385
+ const scrubbed = scrubForRewrite(query);
386
+ const sources = parseTableSources(query, scrubbed);
387
+ if (sources.length < 2) {
388
+ return query;
389
+ }
390
+ const joinClauses = findJoinClauses(query, scrubbed, sources);
391
+ if (joinClauses.length === 0) {
392
+ return query;
393
+ }
394
+ let result = query;
395
+ let offset = 0;
396
+ for (const join of joinClauses) {
397
+ const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
398
+ const originalOnClause = query.slice(join.onStart, join.onEnd);
399
+ let clauseResult = originalOnClause;
400
+ let clauseOffset = 0;
401
+ let eqPos = -1;
402
+ while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
403
+ let lhsEnd = eqPos - 1;
404
+ while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
405
+ lhsEnd--;
406
+ }
407
+ if (scrubbedOnClause[lhsEnd] !== '"')
408
+ continue;
409
+ let lhsStartPos = lhsEnd - 1;
410
+ while (lhsStartPos >= 0 && scrubbedOnClause[lhsStartPos] !== '"') {
411
+ lhsStartPos--;
412
+ }
413
+ if (lhsStartPos < 0)
414
+ continue;
415
+ const lhsIsQualified = lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === ".";
416
+ let rhsStartPos = eqPos + 1;
417
+ while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
418
+ rhsStartPos++;
419
+ }
420
+ const rhsChar = originalOnClause[rhsStartPos];
421
+ const rhsIsParam = rhsChar === "$";
422
+ const rhsIsStringLiteral = rhsChar === "'";
423
+ const rhsIsColumn = rhsChar === '"';
424
+ if (!rhsIsParam && !rhsIsStringLiteral && !rhsIsColumn)
425
+ continue;
426
+ const rhsIsQualified = !rhsIsColumn || rhsStartPos > 0 && scrubbedOnClause[rhsStartPos - 1] === ".";
427
+ if (lhsIsQualified || rhsIsQualified)
428
+ continue;
429
+ const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
430
+ if (!lhsIdent)
431
+ continue;
432
+ let rhsIdent = null;
433
+ let rhsValue = "";
434
+ let rhsEnd = rhsStartPos;
435
+ if (rhsIsParam) {
436
+ let paramEnd = rhsStartPos + 1;
437
+ while (paramEnd < originalOnClause.length && /\d/.test(originalOnClause[paramEnd])) {
438
+ paramEnd++;
439
+ }
440
+ rhsValue = originalOnClause.slice(rhsStartPos, paramEnd);
441
+ rhsEnd = paramEnd;
442
+ } else if (rhsIsStringLiteral) {
443
+ let literalEnd = rhsStartPos + 1;
444
+ while (literalEnd < originalOnClause.length) {
445
+ if (originalOnClause[literalEnd] === "'") {
446
+ if (originalOnClause[literalEnd + 1] === "'") {
447
+ literalEnd += 2;
448
+ continue;
449
+ }
450
+ break;
451
+ }
452
+ literalEnd++;
453
+ }
454
+ rhsValue = originalOnClause.slice(rhsStartPos, literalEnd + 1);
455
+ rhsEnd = literalEnd + 1;
456
+ } else if (rhsIsColumn) {
457
+ rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
458
+ if (rhsIdent) {
459
+ if (rhsIdent.end < scrubbedOnClause.length && scrubbedOnClause[rhsIdent.end] === ".") {
460
+ continue;
461
+ }
462
+ rhsValue = `"${rhsIdent.name}"`;
463
+ rhsEnd = rhsIdent.end;
464
+ }
465
+ }
466
+ if (!rhsValue)
467
+ continue;
468
+ if (!rhsIsColumn || !rhsIdent || lhsIdent.name !== rhsIdent.name) {
469
+ continue;
470
+ }
471
+ const lhsOriginal = `"${lhsIdent.name}"`;
472
+ let newLhs = lhsOriginal;
473
+ let newRhs = rhsValue;
474
+ if (!lhsIsQualified && join.leftSource) {
475
+ newLhs = `"${join.leftSource}"."${lhsIdent.name}"`;
476
+ }
477
+ if (!rhsIsQualified && rhsIsColumn && rhsIdent && join.rightSource) {
478
+ newRhs = `"${join.rightSource}"."${rhsIdent.name}"`;
479
+ }
480
+ if (newLhs !== lhsOriginal || newRhs !== rhsValue) {
481
+ const opStart = lhsIdent.end;
482
+ let opEnd = opStart;
483
+ while (opEnd < rhsEnd && (isWhitespace(originalOnClause[opEnd]) || originalOnClause[opEnd] === "=")) {
484
+ opEnd++;
485
+ }
486
+ const operator = originalOnClause.slice(opStart, opEnd);
487
+ const newExpr = `${newLhs}${operator}${newRhs}`;
488
+ const oldExprLength = rhsEnd - lhsStartPos;
489
+ clauseResult = clauseResult.slice(0, lhsStartPos + clauseOffset) + newExpr + clauseResult.slice(lhsStartPos + oldExprLength + clauseOffset);
490
+ clauseOffset += newExpr.length - oldExprLength;
491
+ }
492
+ }
493
+ if (clauseResult !== originalOnClause) {
494
+ result = result.slice(0, join.onStart + offset) + clauseResult + result.slice(join.onEnd + offset);
495
+ offset += clauseResult.length - originalOnClause.length;
496
+ }
497
+ }
498
+ return result;
499
+ }
175
500
 
176
501
  // src/sql/result-mapper.ts
177
502
  import {
@@ -524,6 +849,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
524
849
  function isPool(client) {
525
850
  return typeof client.acquire === "function";
526
851
  }
852
+ var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
527
853
  function isPgArrayLiteral(value) {
528
854
  return value.startsWith("{") && value.endsWith("}");
529
855
  }
@@ -537,16 +863,20 @@ function parsePgArrayLiteral(value) {
537
863
  }
538
864
  function prepareParams(params, options = {}) {
539
865
  return params.map((param) => {
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();
866
+ if (typeof param === "string" && param.length > 0) {
867
+ const firstChar = param[0];
868
+ const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
869
+ if (maybeArrayLiteral) {
870
+ const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
871
+ if (trimmed && isPgArrayLiteral(trimmed)) {
872
+ if (options.rejectStringArrayLiterals) {
873
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
874
+ }
875
+ if (options.warnOnStringArrayLiteral) {
876
+ options.warnOnStringArrayLiteral();
877
+ }
878
+ return parsePgArrayLiteral(trimmed);
548
879
  }
549
- return parsePgArrayLiteral(trimmed);
550
880
  }
551
881
  }
552
882
  return param;
@@ -571,13 +901,121 @@ function toNodeApiValue(value) {
571
901
  return value;
572
902
  }
573
903
  function deduplicateColumns(columns) {
574
- const seen = {};
575
- return columns.map((col) => {
576
- const count = seen[col] ?? 0;
577
- seen[col] = count + 1;
578
- return count === 0 ? col : `${col}_${count}`;
904
+ const counts = new Map;
905
+ let hasDuplicates = false;
906
+ for (const column of columns) {
907
+ const next = (counts.get(column) ?? 0) + 1;
908
+ counts.set(column, next);
909
+ if (next > 1) {
910
+ hasDuplicates = true;
911
+ break;
912
+ }
913
+ }
914
+ if (!hasDuplicates) {
915
+ return columns;
916
+ }
917
+ counts.clear();
918
+ return columns.map((column) => {
919
+ const count = counts.get(column) ?? 0;
920
+ counts.set(column, count + 1);
921
+ return count === 0 ? column : `${column}_${count}`;
579
922
  });
580
923
  }
924
+ function destroyPreparedStatement(entry) {
925
+ if (!entry)
926
+ return;
927
+ try {
928
+ entry.statement.destroySync();
929
+ } catch {}
930
+ }
931
+ function getPreparedCache(connection, size) {
932
+ const store = connection;
933
+ const existing = store[PREPARED_CACHE];
934
+ if (existing) {
935
+ existing.size = size;
936
+ return existing;
937
+ }
938
+ const cache = { size, entries: new Map };
939
+ store[PREPARED_CACHE] = cache;
940
+ return cache;
941
+ }
942
+ function evictOldest(cache) {
943
+ const oldest = cache.entries.keys().next();
944
+ if (!oldest.done) {
945
+ const key = oldest.value;
946
+ const entry = cache.entries.get(key);
947
+ cache.entries.delete(key);
948
+ destroyPreparedStatement(entry);
949
+ }
950
+ }
951
+ function evictCacheEntry(cache, key) {
952
+ const entry = cache.entries.get(key);
953
+ cache.entries.delete(key);
954
+ destroyPreparedStatement(entry);
955
+ }
956
+ async function getOrPrepareStatement(connection, query, cacheConfig) {
957
+ const cache = getPreparedCache(connection, cacheConfig.size);
958
+ const cached = cache.entries.get(query);
959
+ if (cached) {
960
+ cache.entries.delete(query);
961
+ cache.entries.set(query, cached);
962
+ return cached.statement;
963
+ }
964
+ const statement = await connection.prepare(query);
965
+ cache.entries.set(query, { statement });
966
+ while (cache.entries.size > cache.size) {
967
+ evictOldest(cache);
968
+ }
969
+ return statement;
970
+ }
971
+ async function materializeResultRows(result) {
972
+ const rows = await result.getRowsJS() ?? [];
973
+ const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
974
+ const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
975
+ return { columns, rows };
976
+ }
977
+ async function materializeRows(client, query, params, options = {}) {
978
+ if (isPool(client)) {
979
+ const connection2 = await client.acquire();
980
+ try {
981
+ return await materializeRows(connection2, query, params, options);
982
+ } finally {
983
+ await client.release(connection2);
984
+ }
985
+ }
986
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
987
+ const connection = client;
988
+ if (options.prepareCache && typeof connection.prepare === "function") {
989
+ const cache = getPreparedCache(connection, options.prepareCache.size);
990
+ try {
991
+ const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
992
+ if (values) {
993
+ statement.bind(values);
994
+ } else {
995
+ statement.clearBindings?.();
996
+ }
997
+ const result2 = await statement.run();
998
+ cache.entries.delete(query);
999
+ cache.entries.set(query, { statement });
1000
+ return await materializeResultRows(result2);
1001
+ } catch (error) {
1002
+ evictCacheEntry(cache, query);
1003
+ throw error;
1004
+ }
1005
+ }
1006
+ const result = await connection.run(query, values);
1007
+ return await materializeResultRows(result);
1008
+ }
1009
+ function clearPreparedCache(connection) {
1010
+ const store = connection;
1011
+ const cache = store[PREPARED_CACHE];
1012
+ if (!cache)
1013
+ return;
1014
+ for (const entry of cache.entries.values()) {
1015
+ destroyPreparedStatement(entry);
1016
+ }
1017
+ cache.entries.clear();
1018
+ }
581
1019
  function mapRowsToObjects(columns, rows) {
582
1020
  return rows.map((vals) => {
583
1021
  const obj = {};
@@ -588,6 +1026,7 @@ function mapRowsToObjects(columns, rows) {
588
1026
  });
589
1027
  }
590
1028
  async function closeClientConnection(connection) {
1029
+ clearPreparedCache(connection);
591
1030
  if ("close" in connection && typeof connection.close === "function") {
592
1031
  await connection.close();
593
1032
  return;
@@ -600,27 +1039,51 @@ async function closeClientConnection(connection) {
600
1039
  connection.disconnectSync();
601
1040
  }
602
1041
  }
603
- async function executeOnClient(client, query, params) {
1042
+ async function executeOnClient(client, query, params, options = {}) {
1043
+ const { columns, rows } = await materializeRows(client, query, params, options);
1044
+ if (!rows || rows.length === 0) {
1045
+ return [];
1046
+ }
1047
+ return mapRowsToObjects(columns, rows);
1048
+ }
1049
+ async function executeArraysOnClient(client, query, params, options = {}) {
1050
+ return await materializeRows(client, query, params, options);
1051
+ }
1052
+ async function* executeInBatches(client, query, params, options = {}) {
604
1053
  if (isPool(client)) {
605
1054
  const connection = await client.acquire();
606
1055
  try {
607
- return await executeOnClient(connection, query, params);
1056
+ yield* executeInBatches(connection, query, params, options);
1057
+ return;
608
1058
  } finally {
609
1059
  await client.release(connection);
610
1060
  }
611
1061
  }
1062
+ const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
612
1063
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
613
- const result = await client.run(query, values);
614
- const rows = await result.getRowsJS();
615
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
616
- const uniqueColumns = deduplicateColumns(columns);
617
- return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
1064
+ const result = await client.stream(query, values);
1065
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
1066
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
1067
+ let buffer = [];
1068
+ for await (const chunk of result.yieldRowsJs()) {
1069
+ const objects = mapRowsToObjects(columns, chunk);
1070
+ for (const row of objects) {
1071
+ buffer.push(row);
1072
+ if (buffer.length >= rowsPerChunk) {
1073
+ yield buffer;
1074
+ buffer = [];
1075
+ }
1076
+ }
1077
+ }
1078
+ if (buffer.length > 0) {
1079
+ yield buffer;
1080
+ }
618
1081
  }
619
- async function* executeInBatches(client, query, params, options = {}) {
1082
+ async function* executeInBatchesRaw(client, query, params, options = {}) {
620
1083
  if (isPool(client)) {
621
1084
  const connection = await client.acquire();
622
1085
  try {
623
- yield* executeInBatches(connection, query, params, options);
1086
+ yield* executeInBatchesRaw(connection, query, params, options);
624
1087
  return;
625
1088
  } finally {
626
1089
  await client.release(connection);
@@ -629,21 +1092,20 @@ async function* executeInBatches(client, query, params, options = {}) {
629
1092
  const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
630
1093
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
631
1094
  const result = await client.stream(query, values);
632
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
633
- const uniqueColumns = deduplicateColumns(columns);
1095
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
1096
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
634
1097
  let buffer = [];
635
1098
  for await (const chunk of result.yieldRowsJs()) {
636
- const objects = mapRowsToObjects(uniqueColumns, chunk);
637
- for (const row of objects) {
1099
+ for (const row of chunk) {
638
1100
  buffer.push(row);
639
1101
  if (buffer.length >= rowsPerChunk) {
640
- yield buffer;
1102
+ yield { columns, rows: buffer };
641
1103
  buffer = [];
642
1104
  }
643
1105
  }
644
1106
  }
645
1107
  if (buffer.length > 0) {
646
- yield buffer;
1108
+ yield { columns, rows: buffer };
647
1109
  }
648
1110
  }
649
1111
  async function executeArrowOnClient(client, query, params) {
@@ -671,6 +1133,24 @@ function isSavepointSyntaxError(error) {
671
1133
  }
672
1134
  return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
673
1135
  }
1136
+ function rewriteQuery(mode, query) {
1137
+ if (mode === "never") {
1138
+ return { sql: query, rewritten: false };
1139
+ }
1140
+ let result = query;
1141
+ let wasRewritten = false;
1142
+ const arrayRewritten = adaptArrayOperators(result);
1143
+ if (arrayRewritten !== result) {
1144
+ result = arrayRewritten;
1145
+ wasRewritten = true;
1146
+ }
1147
+ const joinQualified = qualifyJoinColumns(result);
1148
+ if (joinQualified !== result) {
1149
+ result = joinQualified;
1150
+ wasRewritten = true;
1151
+ }
1152
+ return { sql: result, rewritten: wasRewritten };
1153
+ }
674
1154
 
675
1155
  class DuckDBPreparedQuery extends PgPreparedQuery {
676
1156
  client;
@@ -681,11 +1161,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
681
1161
  fields;
682
1162
  _isResponseInArrayMode;
683
1163
  customResultMapper;
684
- rewriteArrays;
1164
+ rewriteArraysMode;
685
1165
  rejectStringArrayLiterals;
1166
+ prepareCache;
686
1167
  warnOnStringArrayLiteral;
687
1168
  static [entityKind] = "DuckDBPreparedQuery";
688
- constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArrays, rejectStringArrayLiterals, warnOnStringArrayLiteral) {
1169
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
689
1170
  super({ sql: queryString, params });
690
1171
  this.client = client;
691
1172
  this.dialect = dialect;
@@ -695,8 +1176,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
695
1176
  this.fields = fields;
696
1177
  this._isResponseInArrayMode = _isResponseInArrayMode;
697
1178
  this.customResultMapper = customResultMapper;
698
- this.rewriteArrays = rewriteArrays;
1179
+ this.rewriteArraysMode = rewriteArraysMode;
699
1180
  this.rejectStringArrayLiterals = rejectStringArrayLiterals;
1181
+ this.prepareCache = prepareCache;
700
1182
  this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
701
1183
  }
702
1184
  async execute(placeholderValues = {}) {
@@ -705,18 +1187,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
705
1187
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
706
1188
  warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
707
1189
  });
708
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(this.queryString) : this.queryString;
709
- if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
1190
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
1191
+ if (didRewrite) {
710
1192
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
711
1193
  }
712
1194
  this.logger.logQuery(rewrittenQuery, params);
713
1195
  const { fields, joinsNotNullableMap, customResultMapper } = this;
714
- const rows = await executeOnClient(this.client, rewrittenQuery, params);
715
- if (rows.length === 0 || !fields) {
716
- return rows;
1196
+ if (fields) {
1197
+ const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
1198
+ if (rows2.length === 0) {
1199
+ return [];
1200
+ }
1201
+ return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
717
1202
  }
718
- const rowValues = rows.map((row) => Object.values(row));
719
- return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
1203
+ const rows = await executeOnClient(this.client, rewrittenQuery, params, {
1204
+ prepareCache: this.prepareCache
1205
+ });
1206
+ return rows;
720
1207
  }
721
1208
  all(placeholderValues = {}) {
722
1209
  return this.execute(placeholderValues);
@@ -733,8 +1220,9 @@ class DuckDBSession extends PgSession {
733
1220
  static [entityKind] = "DuckDBSession";
734
1221
  dialect;
735
1222
  logger;
736
- rewriteArrays;
1223
+ rewriteArraysMode;
737
1224
  rejectStringArrayLiterals;
1225
+ prepareCache;
738
1226
  hasWarnedArrayLiteral = false;
739
1227
  rollbackOnly = false;
740
1228
  constructor(client, dialect, schema, options = {}) {
@@ -744,11 +1232,17 @@ class DuckDBSession extends PgSession {
744
1232
  this.options = options;
745
1233
  this.dialect = dialect;
746
1234
  this.logger = options.logger ?? new NoopLogger;
747
- this.rewriteArrays = options.rewriteArrays ?? true;
1235
+ this.rewriteArraysMode = options.rewriteArrays ?? "auto";
748
1236
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
1237
+ this.prepareCache = options.prepareCache;
1238
+ this.options = {
1239
+ ...options,
1240
+ rewriteArrays: this.rewriteArraysMode,
1241
+ prepareCache: this.prepareCache
1242
+ };
749
1243
  }
750
1244
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
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);
1245
+ 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);
752
1246
  }
753
1247
  execute(query) {
754
1248
  this.dialect.resetPgJsonFlag();
@@ -808,13 +1302,28 @@ query: ${query}`, []);
808
1302
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
809
1303
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
810
1304
  });
811
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
812
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
1305
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1306
+ if (didRewrite) {
813
1307
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
814
1308
  }
815
1309
  this.logger.logQuery(rewrittenQuery, params);
816
1310
  return executeInBatches(this.client, rewrittenQuery, params, options);
817
1311
  }
1312
+ executeBatchesRaw(query, options = {}) {
1313
+ this.dialect.resetPgJsonFlag();
1314
+ const builtQuery = this.dialect.sqlToQuery(query);
1315
+ this.dialect.assertNoPgJsonColumns();
1316
+ const params = prepareParams(builtQuery.params, {
1317
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1318
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
1319
+ });
1320
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1321
+ if (didRewrite) {
1322
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
1323
+ }
1324
+ this.logger.logQuery(rewrittenQuery, params);
1325
+ return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
1326
+ }
818
1327
  async executeArrow(query) {
819
1328
  this.dialect.resetPgJsonFlag();
820
1329
  const builtQuery = this.dialect.sqlToQuery(query);
@@ -823,8 +1332,8 @@ query: ${query}`, []);
823
1332
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
824
1333
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
825
1334
  });
826
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
827
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
1335
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1336
+ if (didRewrite) {
828
1337
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
829
1338
  }
830
1339
  this.logger.logQuery(rewrittenQuery, params);
@@ -862,6 +1371,9 @@ class DuckDBTransaction extends PgTransaction {
862
1371
  executeBatches(query, options = {}) {
863
1372
  return this.session.executeBatches(query, options);
864
1373
  }
1374
+ executeBatchesRaw(query, options = {}) {
1375
+ return this.session.executeBatchesRaw(query, options);
1376
+ }
865
1377
  executeArrow(query) {
866
1378
  return this.session.executeArrow(query);
867
1379
  }
@@ -1273,6 +1785,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
1273
1785
  };
1274
1786
  }
1275
1787
 
1788
+ // src/options.ts
1789
+ var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
1790
+ function resolveRewriteArraysOption(value) {
1791
+ if (value === undefined)
1792
+ return DEFAULT_REWRITE_ARRAYS_MODE;
1793
+ if (value === true)
1794
+ return "auto";
1795
+ if (value === false)
1796
+ return "never";
1797
+ return value;
1798
+ }
1799
+ var DEFAULT_PREPARED_CACHE_SIZE = 32;
1800
+ function resolvePrepareCacheOption(option) {
1801
+ if (!option)
1802
+ return;
1803
+ if (option === true) {
1804
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
1805
+ }
1806
+ if (typeof option === "number") {
1807
+ const size2 = Math.max(1, Math.floor(option));
1808
+ return { size: size2 };
1809
+ }
1810
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
1811
+ return { size: Math.max(1, Math.floor(size)) };
1812
+ }
1813
+
1276
1814
  // src/driver.ts
1277
1815
  class DuckDBDriver {
1278
1816
  client;
@@ -1287,8 +1825,9 @@ class DuckDBDriver {
1287
1825
  createSession(schema) {
1288
1826
  return new DuckDBSession(this.client, this.dialect, schema, {
1289
1827
  logger: this.options.logger,
1290
- rewriteArrays: this.options.rewriteArrays,
1291
- rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
1828
+ rewriteArrays: this.options.rewriteArrays ?? "auto",
1829
+ rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
1830
+ prepareCache: this.options.prepareCache
1292
1831
  });
1293
1832
  }
1294
1833
  }
@@ -1301,6 +1840,8 @@ function isConfigObject(data) {
1301
1840
  }
1302
1841
  function createFromClient(client, config = {}, instance) {
1303
1842
  const dialect = new DuckDBDialect;
1843
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
1844
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
1304
1845
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
1305
1846
  let schema;
1306
1847
  if (config.schema) {
@@ -1313,8 +1854,9 @@ function createFromClient(client, config = {}, instance) {
1313
1854
  }
1314
1855
  const driver = new DuckDBDriver(client, dialect, {
1315
1856
  logger,
1316
- rewriteArrays: config.rewriteArrays,
1317
- rejectStringArrayLiterals: config.rejectStringArrayLiterals
1857
+ rewriteArrays: rewriteArraysMode,
1858
+ rejectStringArrayLiterals: config.rejectStringArrayLiterals,
1859
+ prepareCache
1318
1860
  });
1319
1861
  const session = driver.createSession(schema);
1320
1862
  const db = new DuckDBDatabase(dialect, session, schema, client, instance);
@@ -1394,6 +1936,9 @@ class DuckDBDatabase extends PgDatabase {
1394
1936
  executeBatches(query, options = {}) {
1395
1937
  return this.session.executeBatches(query, options);
1396
1938
  }
1939
+ executeBatchesRaw(query, options = {}) {
1940
+ return this.session.executeBatchesRaw(query, options);
1941
+ }
1397
1942
  executeArrow(query) {
1398
1943
  return this.session.executeArrow(query);
1399
1944
  }
@@ -1594,6 +2139,21 @@ var duckDbInterval = (name) => customType({
1594
2139
  return value;
1595
2140
  }
1596
2141
  })(name);
2142
+ function shouldBindTimestamp(options) {
2143
+ const bindMode = options.bindMode ?? "auto";
2144
+ if (bindMode === "bind")
2145
+ return true;
2146
+ if (bindMode === "literal")
2147
+ return false;
2148
+ const isBun = typeof process !== "undefined" && typeof process.versions?.bun !== "undefined";
2149
+ if (isBun)
2150
+ return false;
2151
+ const forceLiteral = typeof process !== "undefined" ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS : undefined;
2152
+ if (forceLiteral && forceLiteral !== "0") {
2153
+ return false;
2154
+ }
2155
+ return true;
2156
+ }
1597
2157
  var duckDbTimestamp = (name, options = {}) => customType({
1598
2158
  dataType() {
1599
2159
  if (options.withTimezone) {
@@ -1603,12 +2163,19 @@ var duckDbTimestamp = (name, options = {}) => customType({
1603
2163
  return `TIMESTAMP${precision}`;
1604
2164
  },
1605
2165
  toDriver(value) {
2166
+ if (shouldBindTimestamp(options)) {
2167
+ return wrapTimestamp(value, options.withTimezone ?? false, options.precision);
2168
+ }
1606
2169
  const iso = value instanceof Date ? value.toISOString() : value;
1607
2170
  const normalized = iso.replace("T", " ").replace("Z", "+00");
1608
2171
  const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
1609
2172
  return sql4.raw(`${typeKeyword} '${normalized}'`);
1610
2173
  },
1611
2174
  fromDriver(value) {
2175
+ if (value && typeof value === "object" && "kind" in value && value.kind === "timestamp") {
2176
+ const wrapped = value;
2177
+ return wrapped.data instanceof Date ? wrapped.data : typeof wrapped.data === "number" || typeof wrapped.data === "bigint" ? new Date(Number(wrapped.data) / 1000) : wrapped.data;
2178
+ }
1612
2179
  if (options.mode === "string") {
1613
2180
  if (value instanceof Date) {
1614
2181
  return value.toISOString().replace("T", " ").replace("Z", "+00");
@@ -2364,6 +2931,8 @@ export {
2364
2931
  sumDistinctN,
2365
2932
  splitTopLevel,
2366
2933
  rowNumber,
2934
+ resolveRewriteArraysOption,
2935
+ resolvePrepareCacheOption,
2367
2936
  resolvePoolSize,
2368
2937
  rank,
2369
2938
  prepareParams,
@@ -2380,8 +2949,10 @@ export {
2380
2949
  introspect,
2381
2950
  formatLiteral,
2382
2951
  executeOnClient,
2952
+ executeInBatchesRaw,
2383
2953
  executeInBatches,
2384
2954
  executeArrowOnClient,
2955
+ executeArraysOnClient,
2385
2956
  duckDbTimestamp,
2386
2957
  duckDbTime,
2387
2958
  duckDbStruct,