@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.
@@ -161,6 +161,9 @@ function walkRight(source, scrubbed, start) {
161
161
  return [scrubbed.length, source.slice(start)];
162
162
  }
163
163
  function adaptArrayOperators(query) {
164
+ if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
165
+ return query;
166
+ }
164
167
  let rewritten = query;
165
168
  let scrubbed = scrubForRewrite(query);
166
169
  let searchStart = 0;
@@ -180,6 +183,328 @@ function adaptArrayOperators(query) {
180
183
  }
181
184
  return rewritten;
182
185
  }
186
+ function extractQuotedIdentifier(original, start) {
187
+ if (original[start] !== '"') {
188
+ return null;
189
+ }
190
+ let pos = start + 1;
191
+ while (pos < original.length && original[pos] !== '"') {
192
+ if (original[pos] === '"' && original[pos + 1] === '"') {
193
+ pos += 2;
194
+ continue;
195
+ }
196
+ pos++;
197
+ }
198
+ if (pos >= original.length) {
199
+ return null;
200
+ }
201
+ return {
202
+ name: original.slice(start + 1, pos),
203
+ end: pos + 1
204
+ };
205
+ }
206
+ function findMainFromClause(scrubbed) {
207
+ const lowerScrubbed = scrubbed.toLowerCase();
208
+ let searchStart = 0;
209
+ const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
210
+ if (withMatch) {
211
+ let depth = 0;
212
+ let pos = withMatch.index + withMatch[0].length;
213
+ while (pos < scrubbed.length) {
214
+ const char = scrubbed[pos];
215
+ if (char === "(") {
216
+ depth++;
217
+ } else if (char === ")") {
218
+ depth--;
219
+ } else if (depth === 0) {
220
+ const remaining = lowerScrubbed.slice(pos);
221
+ if (/^\s*select\s+/i.test(remaining)) {
222
+ searchStart = pos;
223
+ break;
224
+ }
225
+ }
226
+ pos++;
227
+ }
228
+ }
229
+ const fromPattern = /\bfrom\s+/gi;
230
+ fromPattern.lastIndex = searchStart;
231
+ const fromMatch = fromPattern.exec(lowerScrubbed);
232
+ return fromMatch ? fromMatch.index + fromMatch[0].length : -1;
233
+ }
234
+ function parseTableSources(original, scrubbed) {
235
+ const sources = [];
236
+ const lowerScrubbed = scrubbed.toLowerCase();
237
+ const fromPos = findMainFromClause(scrubbed);
238
+ if (fromPos < 0) {
239
+ return sources;
240
+ }
241
+ const fromTable = parseTableRef(original, scrubbed, fromPos);
242
+ if (fromTable) {
243
+ sources.push(fromTable);
244
+ }
245
+ const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+/gi;
246
+ joinPattern.lastIndex = fromPos;
247
+ let joinMatch;
248
+ while ((joinMatch = joinPattern.exec(lowerScrubbed)) !== null) {
249
+ const tableStart = joinMatch.index + joinMatch[0].length;
250
+ const joinTable = parseTableRef(original, scrubbed, tableStart);
251
+ if (joinTable) {
252
+ sources.push(joinTable);
253
+ }
254
+ }
255
+ return sources;
256
+ }
257
+ function parseTableRef(original, scrubbed, start) {
258
+ let pos = start;
259
+ while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
260
+ pos++;
261
+ }
262
+ if (original[pos] !== '"') {
263
+ return null;
264
+ }
265
+ const nameStart = pos;
266
+ const firstIdent = extractQuotedIdentifier(original, pos);
267
+ if (!firstIdent) {
268
+ return null;
269
+ }
270
+ let name = firstIdent.name;
271
+ pos = firstIdent.end;
272
+ let afterName = pos;
273
+ while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
274
+ afterName++;
275
+ }
276
+ if (scrubbed[afterName] === ".") {
277
+ afterName++;
278
+ while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
279
+ afterName++;
280
+ }
281
+ if (original[afterName] === '"') {
282
+ const tableIdent = extractQuotedIdentifier(original, afterName);
283
+ if (tableIdent) {
284
+ name = tableIdent.name;
285
+ pos = tableIdent.end;
286
+ }
287
+ }
288
+ }
289
+ let alias;
290
+ let aliasPos = pos;
291
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
292
+ aliasPos++;
293
+ }
294
+ const afterTable = scrubbed.slice(aliasPos).toLowerCase();
295
+ if (afterTable.startsWith("as ")) {
296
+ aliasPos += 3;
297
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
298
+ aliasPos++;
299
+ }
300
+ }
301
+ 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 ")) {
302
+ const aliasIdent = extractQuotedIdentifier(original, aliasPos);
303
+ if (aliasIdent) {
304
+ alias = aliasIdent.name;
305
+ }
306
+ }
307
+ return {
308
+ name,
309
+ alias,
310
+ position: nameStart
311
+ };
312
+ }
313
+ function findJoinClauses(original, scrubbed, sources) {
314
+ const clauses = [];
315
+ const lowerScrubbed = scrubbed.toLowerCase();
316
+ const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
317
+ let match;
318
+ let sourceIndex = 1;
319
+ while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
320
+ const joinType = (match[1] || "").trim().toLowerCase();
321
+ const joinKeywordEnd = match.index + (match[1] || "").length + "join".length;
322
+ let tableStart = joinKeywordEnd;
323
+ while (tableStart < original.length && isWhitespace(original[tableStart])) {
324
+ tableStart++;
325
+ }
326
+ const tableIdent = extractQuotedIdentifier(original, tableStart);
327
+ if (!tableIdent)
328
+ continue;
329
+ let tableName = tableIdent.name;
330
+ let afterTable = tableIdent.end;
331
+ let checkPos = afterTable;
332
+ while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
333
+ checkPos++;
334
+ }
335
+ if (scrubbed[checkPos] === ".") {
336
+ checkPos++;
337
+ while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
338
+ checkPos++;
339
+ }
340
+ const realTableIdent = extractQuotedIdentifier(original, checkPos);
341
+ if (realTableIdent) {
342
+ tableName = realTableIdent.name;
343
+ afterTable = realTableIdent.end;
344
+ }
345
+ }
346
+ let tableAlias;
347
+ let aliasPos = afterTable;
348
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
349
+ aliasPos++;
350
+ }
351
+ const afterTableStr = scrubbed.slice(aliasPos).toLowerCase();
352
+ if (afterTableStr.startsWith("as ")) {
353
+ aliasPos += 3;
354
+ while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
355
+ aliasPos++;
356
+ }
357
+ }
358
+ if (original[aliasPos] === '"' && !afterTableStr.startsWith("on ")) {
359
+ const aliasIdent = extractQuotedIdentifier(original, aliasPos);
360
+ if (aliasIdent) {
361
+ tableAlias = aliasIdent.name;
362
+ }
363
+ }
364
+ const onStart = match.index + match[0].length;
365
+ 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;
366
+ const remaining = lowerScrubbed.slice(onStart);
367
+ const endMatch = endPattern.exec(remaining);
368
+ const onEnd = endMatch ? onStart + endMatch.index : scrubbed.length;
369
+ let leftSource = "";
370
+ if (sourceIndex > 0 && sourceIndex <= sources.length) {
371
+ const prev = sources[sourceIndex - 1];
372
+ leftSource = prev?.alias || prev?.name || "";
373
+ }
374
+ const rightSource = tableAlias || tableName;
375
+ clauses.push({
376
+ joinType,
377
+ tableName,
378
+ tableAlias,
379
+ onStart,
380
+ onEnd,
381
+ leftSource,
382
+ rightSource
383
+ });
384
+ sourceIndex++;
385
+ }
386
+ return clauses;
387
+ }
388
+ function qualifyJoinColumns(query) {
389
+ const lowerQuery = query.toLowerCase();
390
+ if (!lowerQuery.includes("join")) {
391
+ return query;
392
+ }
393
+ const scrubbed = scrubForRewrite(query);
394
+ const sources = parseTableSources(query, scrubbed);
395
+ if (sources.length < 2) {
396
+ return query;
397
+ }
398
+ const joinClauses = findJoinClauses(query, scrubbed, sources);
399
+ if (joinClauses.length === 0) {
400
+ return query;
401
+ }
402
+ let result = query;
403
+ let offset = 0;
404
+ for (const join of joinClauses) {
405
+ const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
406
+ const originalOnClause = query.slice(join.onStart, join.onEnd);
407
+ let clauseResult = originalOnClause;
408
+ let clauseOffset = 0;
409
+ let eqPos = -1;
410
+ while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
411
+ let lhsEnd = eqPos - 1;
412
+ while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
413
+ lhsEnd--;
414
+ }
415
+ if (scrubbedOnClause[lhsEnd] !== '"')
416
+ continue;
417
+ let lhsStartPos = lhsEnd - 1;
418
+ while (lhsStartPos >= 0 && scrubbedOnClause[lhsStartPos] !== '"') {
419
+ lhsStartPos--;
420
+ }
421
+ if (lhsStartPos < 0)
422
+ continue;
423
+ const lhsIsQualified = lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === ".";
424
+ let rhsStartPos = eqPos + 1;
425
+ while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
426
+ rhsStartPos++;
427
+ }
428
+ const rhsChar = originalOnClause[rhsStartPos];
429
+ const rhsIsParam = rhsChar === "$";
430
+ const rhsIsStringLiteral = rhsChar === "'";
431
+ const rhsIsColumn = rhsChar === '"';
432
+ if (!rhsIsParam && !rhsIsStringLiteral && !rhsIsColumn)
433
+ continue;
434
+ const rhsIsQualified = !rhsIsColumn || rhsStartPos > 0 && scrubbedOnClause[rhsStartPos - 1] === ".";
435
+ if (lhsIsQualified || rhsIsQualified)
436
+ continue;
437
+ const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
438
+ if (!lhsIdent)
439
+ continue;
440
+ let rhsIdent = null;
441
+ let rhsValue = "";
442
+ let rhsEnd = rhsStartPos;
443
+ if (rhsIsParam) {
444
+ let paramEnd = rhsStartPos + 1;
445
+ while (paramEnd < originalOnClause.length && /\d/.test(originalOnClause[paramEnd])) {
446
+ paramEnd++;
447
+ }
448
+ rhsValue = originalOnClause.slice(rhsStartPos, paramEnd);
449
+ rhsEnd = paramEnd;
450
+ } else if (rhsIsStringLiteral) {
451
+ let literalEnd = rhsStartPos + 1;
452
+ while (literalEnd < originalOnClause.length) {
453
+ if (originalOnClause[literalEnd] === "'") {
454
+ if (originalOnClause[literalEnd + 1] === "'") {
455
+ literalEnd += 2;
456
+ continue;
457
+ }
458
+ break;
459
+ }
460
+ literalEnd++;
461
+ }
462
+ rhsValue = originalOnClause.slice(rhsStartPos, literalEnd + 1);
463
+ rhsEnd = literalEnd + 1;
464
+ } else if (rhsIsColumn) {
465
+ rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
466
+ if (rhsIdent) {
467
+ if (rhsIdent.end < scrubbedOnClause.length && scrubbedOnClause[rhsIdent.end] === ".") {
468
+ continue;
469
+ }
470
+ rhsValue = `"${rhsIdent.name}"`;
471
+ rhsEnd = rhsIdent.end;
472
+ }
473
+ }
474
+ if (!rhsValue)
475
+ continue;
476
+ if (!rhsIsColumn || !rhsIdent || lhsIdent.name !== rhsIdent.name) {
477
+ continue;
478
+ }
479
+ const lhsOriginal = `"${lhsIdent.name}"`;
480
+ let newLhs = lhsOriginal;
481
+ let newRhs = rhsValue;
482
+ if (!lhsIsQualified && join.leftSource) {
483
+ newLhs = `"${join.leftSource}"."${lhsIdent.name}"`;
484
+ }
485
+ if (!rhsIsQualified && rhsIsColumn && rhsIdent && join.rightSource) {
486
+ newRhs = `"${join.rightSource}"."${rhsIdent.name}"`;
487
+ }
488
+ if (newLhs !== lhsOriginal || newRhs !== rhsValue) {
489
+ const opStart = lhsIdent.end;
490
+ let opEnd = opStart;
491
+ while (opEnd < rhsEnd && (isWhitespace(originalOnClause[opEnd]) || originalOnClause[opEnd] === "=")) {
492
+ opEnd++;
493
+ }
494
+ const operator = originalOnClause.slice(opStart, opEnd);
495
+ const newExpr = `${newLhs}${operator}${newRhs}`;
496
+ const oldExprLength = rhsEnd - lhsStartPos;
497
+ clauseResult = clauseResult.slice(0, lhsStartPos + clauseOffset) + newExpr + clauseResult.slice(lhsStartPos + oldExprLength + clauseOffset);
498
+ clauseOffset += newExpr.length - oldExprLength;
499
+ }
500
+ }
501
+ if (clauseResult !== originalOnClause) {
502
+ result = result.slice(0, join.onStart + offset) + clauseResult + result.slice(join.onEnd + offset);
503
+ offset += clauseResult.length - originalOnClause.length;
504
+ }
505
+ }
506
+ return result;
507
+ }
183
508
 
184
509
  // src/sql/result-mapper.ts
185
510
  import {
@@ -473,6 +798,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
473
798
  function isPool(client) {
474
799
  return typeof client.acquire === "function";
475
800
  }
801
+ var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
476
802
  function isPgArrayLiteral(value) {
477
803
  return value.startsWith("{") && value.endsWith("}");
478
804
  }
@@ -486,16 +812,20 @@ function parsePgArrayLiteral(value) {
486
812
  }
487
813
  function prepareParams(params, options = {}) {
488
814
  return params.map((param) => {
489
- if (typeof param === "string") {
490
- const trimmed = param.trim();
491
- if (trimmed && isPgArrayLiteral(trimmed)) {
492
- if (options.rejectStringArrayLiterals) {
493
- throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
494
- }
495
- if (options.warnOnStringArrayLiteral) {
496
- options.warnOnStringArrayLiteral();
815
+ if (typeof param === "string" && param.length > 0) {
816
+ const firstChar = param[0];
817
+ const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
818
+ if (maybeArrayLiteral) {
819
+ const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
820
+ if (trimmed && isPgArrayLiteral(trimmed)) {
821
+ if (options.rejectStringArrayLiterals) {
822
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
823
+ }
824
+ if (options.warnOnStringArrayLiteral) {
825
+ options.warnOnStringArrayLiteral();
826
+ }
827
+ return parsePgArrayLiteral(trimmed);
497
828
  }
498
- return parsePgArrayLiteral(trimmed);
499
829
  }
500
830
  }
501
831
  return param;
@@ -520,13 +850,121 @@ function toNodeApiValue(value) {
520
850
  return value;
521
851
  }
522
852
  function deduplicateColumns(columns) {
523
- const seen = {};
524
- return columns.map((col) => {
525
- const count = seen[col] ?? 0;
526
- seen[col] = count + 1;
527
- return count === 0 ? col : `${col}_${count}`;
853
+ const counts = new Map;
854
+ let hasDuplicates = false;
855
+ for (const column of columns) {
856
+ const next = (counts.get(column) ?? 0) + 1;
857
+ counts.set(column, next);
858
+ if (next > 1) {
859
+ hasDuplicates = true;
860
+ break;
861
+ }
862
+ }
863
+ if (!hasDuplicates) {
864
+ return columns;
865
+ }
866
+ counts.clear();
867
+ return columns.map((column) => {
868
+ const count = counts.get(column) ?? 0;
869
+ counts.set(column, count + 1);
870
+ return count === 0 ? column : `${column}_${count}`;
528
871
  });
529
872
  }
873
+ function destroyPreparedStatement(entry) {
874
+ if (!entry)
875
+ return;
876
+ try {
877
+ entry.statement.destroySync();
878
+ } catch {}
879
+ }
880
+ function getPreparedCache(connection, size) {
881
+ const store = connection;
882
+ const existing = store[PREPARED_CACHE];
883
+ if (existing) {
884
+ existing.size = size;
885
+ return existing;
886
+ }
887
+ const cache = { size, entries: new Map };
888
+ store[PREPARED_CACHE] = cache;
889
+ return cache;
890
+ }
891
+ function evictOldest(cache) {
892
+ const oldest = cache.entries.keys().next();
893
+ if (!oldest.done) {
894
+ const key = oldest.value;
895
+ const entry = cache.entries.get(key);
896
+ cache.entries.delete(key);
897
+ destroyPreparedStatement(entry);
898
+ }
899
+ }
900
+ function evictCacheEntry(cache, key) {
901
+ const entry = cache.entries.get(key);
902
+ cache.entries.delete(key);
903
+ destroyPreparedStatement(entry);
904
+ }
905
+ async function getOrPrepareStatement(connection, query, cacheConfig) {
906
+ const cache = getPreparedCache(connection, cacheConfig.size);
907
+ const cached = cache.entries.get(query);
908
+ if (cached) {
909
+ cache.entries.delete(query);
910
+ cache.entries.set(query, cached);
911
+ return cached.statement;
912
+ }
913
+ const statement = await connection.prepare(query);
914
+ cache.entries.set(query, { statement });
915
+ while (cache.entries.size > cache.size) {
916
+ evictOldest(cache);
917
+ }
918
+ return statement;
919
+ }
920
+ async function materializeResultRows(result) {
921
+ const rows = await result.getRowsJS() ?? [];
922
+ const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
923
+ const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
924
+ return { columns, rows };
925
+ }
926
+ async function materializeRows(client, query, params, options = {}) {
927
+ if (isPool(client)) {
928
+ const connection2 = await client.acquire();
929
+ try {
930
+ return await materializeRows(connection2, query, params, options);
931
+ } finally {
932
+ await client.release(connection2);
933
+ }
934
+ }
935
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
936
+ const connection = client;
937
+ if (options.prepareCache && typeof connection.prepare === "function") {
938
+ const cache = getPreparedCache(connection, options.prepareCache.size);
939
+ try {
940
+ const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
941
+ if (values) {
942
+ statement.bind(values);
943
+ } else {
944
+ statement.clearBindings?.();
945
+ }
946
+ const result2 = await statement.run();
947
+ cache.entries.delete(query);
948
+ cache.entries.set(query, { statement });
949
+ return await materializeResultRows(result2);
950
+ } catch (error) {
951
+ evictCacheEntry(cache, query);
952
+ throw error;
953
+ }
954
+ }
955
+ const result = await connection.run(query, values);
956
+ return await materializeResultRows(result);
957
+ }
958
+ function clearPreparedCache(connection) {
959
+ const store = connection;
960
+ const cache = store[PREPARED_CACHE];
961
+ if (!cache)
962
+ return;
963
+ for (const entry of cache.entries.values()) {
964
+ destroyPreparedStatement(entry);
965
+ }
966
+ cache.entries.clear();
967
+ }
530
968
  function mapRowsToObjects(columns, rows) {
531
969
  return rows.map((vals) => {
532
970
  const obj = {};
@@ -537,6 +975,7 @@ function mapRowsToObjects(columns, rows) {
537
975
  });
538
976
  }
539
977
  async function closeClientConnection(connection) {
978
+ clearPreparedCache(connection);
540
979
  if ("close" in connection && typeof connection.close === "function") {
541
980
  await connection.close();
542
981
  return;
@@ -549,27 +988,51 @@ async function closeClientConnection(connection) {
549
988
  connection.disconnectSync();
550
989
  }
551
990
  }
552
- async function executeOnClient(client, query, params) {
991
+ async function executeOnClient(client, query, params, options = {}) {
992
+ const { columns, rows } = await materializeRows(client, query, params, options);
993
+ if (!rows || rows.length === 0) {
994
+ return [];
995
+ }
996
+ return mapRowsToObjects(columns, rows);
997
+ }
998
+ async function executeArraysOnClient(client, query, params, options = {}) {
999
+ return await materializeRows(client, query, params, options);
1000
+ }
1001
+ async function* executeInBatches(client, query, params, options = {}) {
553
1002
  if (isPool(client)) {
554
1003
  const connection = await client.acquire();
555
1004
  try {
556
- return await executeOnClient(connection, query, params);
1005
+ yield* executeInBatches(connection, query, params, options);
1006
+ return;
557
1007
  } finally {
558
1008
  await client.release(connection);
559
1009
  }
560
1010
  }
1011
+ const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
561
1012
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
562
- const result = await client.run(query, values);
563
- const rows = await result.getRowsJS();
564
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
565
- const uniqueColumns = deduplicateColumns(columns);
566
- return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
1013
+ const result = await client.stream(query, values);
1014
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
1015
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
1016
+ let buffer = [];
1017
+ for await (const chunk of result.yieldRowsJs()) {
1018
+ const objects = mapRowsToObjects(columns, chunk);
1019
+ for (const row of objects) {
1020
+ buffer.push(row);
1021
+ if (buffer.length >= rowsPerChunk) {
1022
+ yield buffer;
1023
+ buffer = [];
1024
+ }
1025
+ }
1026
+ }
1027
+ if (buffer.length > 0) {
1028
+ yield buffer;
1029
+ }
567
1030
  }
568
- async function* executeInBatches(client, query, params, options = {}) {
1031
+ async function* executeInBatchesRaw(client, query, params, options = {}) {
569
1032
  if (isPool(client)) {
570
1033
  const connection = await client.acquire();
571
1034
  try {
572
- yield* executeInBatches(connection, query, params, options);
1035
+ yield* executeInBatchesRaw(connection, query, params, options);
573
1036
  return;
574
1037
  } finally {
575
1038
  await client.release(connection);
@@ -578,21 +1041,20 @@ async function* executeInBatches(client, query, params, options = {}) {
578
1041
  const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
579
1042
  const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
580
1043
  const result = await client.stream(query, values);
581
- const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
582
- const uniqueColumns = deduplicateColumns(columns);
1044
+ const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
1045
+ const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
583
1046
  let buffer = [];
584
1047
  for await (const chunk of result.yieldRowsJs()) {
585
- const objects = mapRowsToObjects(uniqueColumns, chunk);
586
- for (const row of objects) {
1048
+ for (const row of chunk) {
587
1049
  buffer.push(row);
588
1050
  if (buffer.length >= rowsPerChunk) {
589
- yield buffer;
1051
+ yield { columns, rows: buffer };
590
1052
  buffer = [];
591
1053
  }
592
1054
  }
593
1055
  }
594
1056
  if (buffer.length > 0) {
595
- yield buffer;
1057
+ yield { columns, rows: buffer };
596
1058
  }
597
1059
  }
598
1060
  async function executeArrowOnClient(client, query, params) {
@@ -620,6 +1082,24 @@ function isSavepointSyntaxError(error) {
620
1082
  }
621
1083
  return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
622
1084
  }
1085
+ function rewriteQuery(mode, query) {
1086
+ if (mode === "never") {
1087
+ return { sql: query, rewritten: false };
1088
+ }
1089
+ let result = query;
1090
+ let wasRewritten = false;
1091
+ const arrayRewritten = adaptArrayOperators(result);
1092
+ if (arrayRewritten !== result) {
1093
+ result = arrayRewritten;
1094
+ wasRewritten = true;
1095
+ }
1096
+ const joinQualified = qualifyJoinColumns(result);
1097
+ if (joinQualified !== result) {
1098
+ result = joinQualified;
1099
+ wasRewritten = true;
1100
+ }
1101
+ return { sql: result, rewritten: wasRewritten };
1102
+ }
623
1103
 
624
1104
  class DuckDBPreparedQuery extends PgPreparedQuery {
625
1105
  client;
@@ -630,11 +1110,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
630
1110
  fields;
631
1111
  _isResponseInArrayMode;
632
1112
  customResultMapper;
633
- rewriteArrays;
1113
+ rewriteArraysMode;
634
1114
  rejectStringArrayLiterals;
1115
+ prepareCache;
635
1116
  warnOnStringArrayLiteral;
636
1117
  static [entityKind] = "DuckDBPreparedQuery";
637
- constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArrays, rejectStringArrayLiterals, warnOnStringArrayLiteral) {
1118
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
638
1119
  super({ sql: queryString, params });
639
1120
  this.client = client;
640
1121
  this.dialect = dialect;
@@ -644,8 +1125,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
644
1125
  this.fields = fields;
645
1126
  this._isResponseInArrayMode = _isResponseInArrayMode;
646
1127
  this.customResultMapper = customResultMapper;
647
- this.rewriteArrays = rewriteArrays;
1128
+ this.rewriteArraysMode = rewriteArraysMode;
648
1129
  this.rejectStringArrayLiterals = rejectStringArrayLiterals;
1130
+ this.prepareCache = prepareCache;
649
1131
  this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
650
1132
  }
651
1133
  async execute(placeholderValues = {}) {
@@ -654,18 +1136,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
654
1136
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
655
1137
  warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
656
1138
  });
657
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(this.queryString) : this.queryString;
658
- if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
1139
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
1140
+ if (didRewrite) {
659
1141
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
660
1142
  }
661
1143
  this.logger.logQuery(rewrittenQuery, params);
662
1144
  const { fields, joinsNotNullableMap, customResultMapper } = this;
663
- const rows = await executeOnClient(this.client, rewrittenQuery, params);
664
- if (rows.length === 0 || !fields) {
665
- return rows;
1145
+ if (fields) {
1146
+ const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
1147
+ if (rows2.length === 0) {
1148
+ return [];
1149
+ }
1150
+ return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
666
1151
  }
667
- const rowValues = rows.map((row) => Object.values(row));
668
- return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
1152
+ const rows = await executeOnClient(this.client, rewrittenQuery, params, {
1153
+ prepareCache: this.prepareCache
1154
+ });
1155
+ return rows;
669
1156
  }
670
1157
  all(placeholderValues = {}) {
671
1158
  return this.execute(placeholderValues);
@@ -682,8 +1169,9 @@ class DuckDBSession extends PgSession {
682
1169
  static [entityKind] = "DuckDBSession";
683
1170
  dialect;
684
1171
  logger;
685
- rewriteArrays;
1172
+ rewriteArraysMode;
686
1173
  rejectStringArrayLiterals;
1174
+ prepareCache;
687
1175
  hasWarnedArrayLiteral = false;
688
1176
  rollbackOnly = false;
689
1177
  constructor(client, dialect, schema, options = {}) {
@@ -693,11 +1181,17 @@ class DuckDBSession extends PgSession {
693
1181
  this.options = options;
694
1182
  this.dialect = dialect;
695
1183
  this.logger = options.logger ?? new NoopLogger;
696
- this.rewriteArrays = options.rewriteArrays ?? true;
1184
+ this.rewriteArraysMode = options.rewriteArrays ?? "auto";
697
1185
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
1186
+ this.prepareCache = options.prepareCache;
1187
+ this.options = {
1188
+ ...options,
1189
+ rewriteArrays: this.rewriteArraysMode,
1190
+ prepareCache: this.prepareCache
1191
+ };
698
1192
  }
699
1193
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
700
- 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);
1194
+ 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);
701
1195
  }
702
1196
  execute(query) {
703
1197
  this.dialect.resetPgJsonFlag();
@@ -757,13 +1251,28 @@ query: ${query}`, []);
757
1251
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
758
1252
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
759
1253
  });
760
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
761
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
1254
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1255
+ if (didRewrite) {
762
1256
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
763
1257
  }
764
1258
  this.logger.logQuery(rewrittenQuery, params);
765
1259
  return executeInBatches(this.client, rewrittenQuery, params, options);
766
1260
  }
1261
+ executeBatchesRaw(query, options = {}) {
1262
+ this.dialect.resetPgJsonFlag();
1263
+ const builtQuery = this.dialect.sqlToQuery(query);
1264
+ this.dialect.assertNoPgJsonColumns();
1265
+ const params = prepareParams(builtQuery.params, {
1266
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1267
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
1268
+ });
1269
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1270
+ if (didRewrite) {
1271
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
1272
+ }
1273
+ this.logger.logQuery(rewrittenQuery, params);
1274
+ return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
1275
+ }
767
1276
  async executeArrow(query) {
768
1277
  this.dialect.resetPgJsonFlag();
769
1278
  const builtQuery = this.dialect.sqlToQuery(query);
@@ -772,8 +1281,8 @@ query: ${query}`, []);
772
1281
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
773
1282
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
774
1283
  });
775
- const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
776
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
1284
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1285
+ if (didRewrite) {
777
1286
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
778
1287
  }
779
1288
  this.logger.logQuery(rewrittenQuery, params);
@@ -811,6 +1320,9 @@ class DuckDBTransaction extends PgTransaction {
811
1320
  executeBatches(query, options = {}) {
812
1321
  return this.session.executeBatches(query, options);
813
1322
  }
1323
+ executeBatchesRaw(query, options = {}) {
1324
+ return this.session.executeBatchesRaw(query, options);
1325
+ }
814
1326
  executeArrow(query) {
815
1327
  return this.session.executeArrow(query);
816
1328
  }
@@ -1222,6 +1734,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
1222
1734
  };
1223
1735
  }
1224
1736
 
1737
+ // src/options.ts
1738
+ var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
1739
+ function resolveRewriteArraysOption(value) {
1740
+ if (value === undefined)
1741
+ return DEFAULT_REWRITE_ARRAYS_MODE;
1742
+ if (value === true)
1743
+ return "auto";
1744
+ if (value === false)
1745
+ return "never";
1746
+ return value;
1747
+ }
1748
+ var DEFAULT_PREPARED_CACHE_SIZE = 32;
1749
+ function resolvePrepareCacheOption(option) {
1750
+ if (!option)
1751
+ return;
1752
+ if (option === true) {
1753
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
1754
+ }
1755
+ if (typeof option === "number") {
1756
+ const size2 = Math.max(1, Math.floor(option));
1757
+ return { size: size2 };
1758
+ }
1759
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
1760
+ return { size: Math.max(1, Math.floor(size)) };
1761
+ }
1762
+
1225
1763
  // src/driver.ts
1226
1764
  class DuckDBDriver {
1227
1765
  client;
@@ -1236,8 +1774,9 @@ class DuckDBDriver {
1236
1774
  createSession(schema) {
1237
1775
  return new DuckDBSession(this.client, this.dialect, schema, {
1238
1776
  logger: this.options.logger,
1239
- rewriteArrays: this.options.rewriteArrays,
1240
- rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
1777
+ rewriteArrays: this.options.rewriteArrays ?? "auto",
1778
+ rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
1779
+ prepareCache: this.options.prepareCache
1241
1780
  });
1242
1781
  }
1243
1782
  }
@@ -1250,6 +1789,8 @@ function isConfigObject(data) {
1250
1789
  }
1251
1790
  function createFromClient(client, config = {}, instance) {
1252
1791
  const dialect = new DuckDBDialect;
1792
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
1793
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
1253
1794
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
1254
1795
  let schema;
1255
1796
  if (config.schema) {
@@ -1262,8 +1803,9 @@ function createFromClient(client, config = {}, instance) {
1262
1803
  }
1263
1804
  const driver = new DuckDBDriver(client, dialect, {
1264
1805
  logger,
1265
- rewriteArrays: config.rewriteArrays,
1266
- rejectStringArrayLiterals: config.rejectStringArrayLiterals
1806
+ rewriteArrays: rewriteArraysMode,
1807
+ rejectStringArrayLiterals: config.rejectStringArrayLiterals,
1808
+ prepareCache
1267
1809
  });
1268
1810
  const session = driver.createSession(schema);
1269
1811
  const db = new DuckDBDatabase(dialect, session, schema, client, instance);
@@ -1343,6 +1885,9 @@ class DuckDBDatabase extends PgDatabase {
1343
1885
  executeBatches(query, options = {}) {
1344
1886
  return this.session.executeBatches(query, options);
1345
1887
  }
1888
+ executeBatchesRaw(query, options = {}) {
1889
+ return this.session.executeBatchesRaw(query, options);
1890
+ }
1346
1891
  executeArrow(query) {
1347
1892
  return this.session.executeArrow(query);
1348
1893
  }