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

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.
@@ -23,489 +23,6 @@ import { PgTransaction } from "drizzle-orm/pg-core";
23
23
  import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
24
24
  import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
25
25
 
26
- // src/sql/query-rewriters.ts
27
- var OPERATORS = [
28
- { token: "@>", fn: "array_has_all" },
29
- { token: "<@", fn: "array_has_all", swap: true },
30
- { token: "&&", fn: "array_has_any" }
31
- ];
32
- var isWhitespace = (char) => char !== undefined && /\s/.test(char);
33
- function scrubForRewrite(query) {
34
- let scrubbed = "";
35
- let state = "code";
36
- for (let i = 0;i < query.length; i += 1) {
37
- const char = query[i];
38
- const next = query[i + 1];
39
- if (state === "code") {
40
- if (char === "'") {
41
- scrubbed += "'";
42
- state = "single";
43
- continue;
44
- }
45
- if (char === '"') {
46
- scrubbed += '"';
47
- state = "double";
48
- continue;
49
- }
50
- if (char === "-" && next === "-") {
51
- scrubbed += " ";
52
- i += 1;
53
- state = "lineComment";
54
- continue;
55
- }
56
- if (char === "/" && next === "*") {
57
- scrubbed += " ";
58
- i += 1;
59
- state = "blockComment";
60
- continue;
61
- }
62
- scrubbed += char;
63
- continue;
64
- }
65
- if (state === "single") {
66
- if (char === "'" && next === "'") {
67
- scrubbed += "''";
68
- i += 1;
69
- continue;
70
- }
71
- scrubbed += char === "'" ? "'" : ".";
72
- if (char === "'") {
73
- state = "code";
74
- }
75
- continue;
76
- }
77
- if (state === "double") {
78
- if (char === '"' && next === '"') {
79
- scrubbed += '""';
80
- i += 1;
81
- continue;
82
- }
83
- scrubbed += char === '"' ? '"' : ".";
84
- if (char === '"') {
85
- state = "code";
86
- }
87
- continue;
88
- }
89
- if (state === "lineComment") {
90
- scrubbed += char === `
91
- ` ? `
92
- ` : " ";
93
- if (char === `
94
- `) {
95
- state = "code";
96
- }
97
- continue;
98
- }
99
- if (state === "blockComment") {
100
- if (char === "*" && next === "/") {
101
- scrubbed += " ";
102
- i += 1;
103
- state = "code";
104
- } else {
105
- scrubbed += " ";
106
- }
107
- }
108
- }
109
- return scrubbed;
110
- }
111
- function findNextOperator(scrubbed, start) {
112
- for (let idx = start;idx < scrubbed.length; idx += 1) {
113
- for (const operator of OPERATORS) {
114
- if (scrubbed.startsWith(operator.token, idx)) {
115
- return { index: idx, operator };
116
- }
117
- }
118
- }
119
- return null;
120
- }
121
- function walkLeft(source, scrubbed, start) {
122
- let idx = start;
123
- while (idx >= 0 && isWhitespace(scrubbed[idx])) {
124
- idx -= 1;
125
- }
126
- let depth = 0;
127
- for (;idx >= 0; idx -= 1) {
128
- const ch = scrubbed[idx];
129
- if (ch === ")" || ch === "]") {
130
- depth += 1;
131
- } else if (ch === "(" || ch === "[") {
132
- if (depth === 0) {
133
- return [idx + 1, source.slice(idx + 1, start + 1)];
134
- }
135
- depth = Math.max(0, depth - 1);
136
- } else if (depth === 0 && isWhitespace(ch)) {
137
- return [idx + 1, source.slice(idx + 1, start + 1)];
138
- }
139
- }
140
- return [0, source.slice(0, start + 1)];
141
- }
142
- function walkRight(source, scrubbed, start) {
143
- let idx = start;
144
- while (idx < scrubbed.length && isWhitespace(scrubbed[idx])) {
145
- idx += 1;
146
- }
147
- let depth = 0;
148
- for (;idx < scrubbed.length; idx += 1) {
149
- const ch = scrubbed[idx];
150
- if (ch === "(" || ch === "[") {
151
- depth += 1;
152
- } else if (ch === ")" || ch === "]") {
153
- if (depth === 0) {
154
- return [idx, source.slice(start, idx)];
155
- }
156
- depth = Math.max(0, depth - 1);
157
- } else if (depth === 0 && isWhitespace(ch)) {
158
- return [idx, source.slice(start, idx)];
159
- }
160
- }
161
- return [scrubbed.length, source.slice(start)];
162
- }
163
- function adaptArrayOperators(query) {
164
- if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
165
- return query;
166
- }
167
- let rewritten = query;
168
- let scrubbed = scrubForRewrite(query);
169
- let searchStart = 0;
170
- while (true) {
171
- const next = findNextOperator(scrubbed, searchStart);
172
- if (!next)
173
- break;
174
- const { index, operator } = next;
175
- const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
176
- const [rightEnd, rightExpr] = walkRight(rewritten, scrubbed, index + operator.token.length);
177
- const left = leftExpr.trim();
178
- const right = rightExpr.trim();
179
- const replacement = `${operator.fn}(${operator.swap ? right : left}, ${operator.swap ? left : right})`;
180
- rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
181
- scrubbed = scrubForRewrite(rewritten);
182
- searchStart = leftStart + replacement.length;
183
- }
184
- return rewritten;
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
- }
508
-
509
26
  // src/sql/result-mapper.ts
510
27
  import {
511
28
  Column,
@@ -1082,24 +599,6 @@ function isSavepointSyntaxError(error) {
1082
599
  }
1083
600
  return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
1084
601
  }
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
- }
1103
602
 
1104
603
  class DuckDBPreparedQuery extends PgPreparedQuery {
1105
604
  client;
@@ -1110,12 +609,11 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
1110
609
  fields;
1111
610
  _isResponseInArrayMode;
1112
611
  customResultMapper;
1113
- rewriteArraysMode;
1114
612
  rejectStringArrayLiterals;
1115
613
  prepareCache;
1116
614
  warnOnStringArrayLiteral;
1117
615
  static [entityKind] = "DuckDBPreparedQuery";
1118
- constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
616
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
1119
617
  super({ sql: queryString, params });
1120
618
  this.client = client;
1121
619
  this.dialect = dialect;
@@ -1125,7 +623,6 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
1125
623
  this.fields = fields;
1126
624
  this._isResponseInArrayMode = _isResponseInArrayMode;
1127
625
  this.customResultMapper = customResultMapper;
1128
- this.rewriteArraysMode = rewriteArraysMode;
1129
626
  this.rejectStringArrayLiterals = rejectStringArrayLiterals;
1130
627
  this.prepareCache = prepareCache;
1131
628
  this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
@@ -1136,20 +633,16 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
1136
633
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1137
634
  warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
1138
635
  });
1139
- const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
1140
- if (didRewrite) {
1141
- this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
1142
- }
1143
- this.logger.logQuery(rewrittenQuery, params);
636
+ this.logger.logQuery(this.queryString, params);
1144
637
  const { fields, joinsNotNullableMap, customResultMapper } = this;
1145
638
  if (fields) {
1146
- const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
639
+ const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params, { prepareCache: this.prepareCache });
1147
640
  if (rows2.length === 0) {
1148
641
  return [];
1149
642
  }
1150
643
  return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
1151
644
  }
1152
- const rows = await executeOnClient(this.client, rewrittenQuery, params, {
645
+ const rows = await executeOnClient(this.client, this.queryString, params, {
1153
646
  prepareCache: this.prepareCache
1154
647
  });
1155
648
  return rows;
@@ -1169,7 +662,6 @@ class DuckDBSession extends PgSession {
1169
662
  static [entityKind] = "DuckDBSession";
1170
663
  dialect;
1171
664
  logger;
1172
- rewriteArraysMode;
1173
665
  rejectStringArrayLiterals;
1174
666
  prepareCache;
1175
667
  hasWarnedArrayLiteral = false;
@@ -1181,17 +673,15 @@ class DuckDBSession extends PgSession {
1181
673
  this.options = options;
1182
674
  this.dialect = dialect;
1183
675
  this.logger = options.logger ?? new NoopLogger;
1184
- this.rewriteArraysMode = options.rewriteArrays ?? "auto";
1185
676
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
1186
677
  this.prepareCache = options.prepareCache;
1187
678
  this.options = {
1188
679
  ...options,
1189
- rewriteArrays: this.rewriteArraysMode,
1190
680
  prepareCache: this.prepareCache
1191
681
  };
1192
682
  }
1193
683
  prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
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);
684
+ return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rejectStringArrayLiterals, this.prepareCache, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
1195
685
  }
1196
686
  execute(query) {
1197
687
  this.dialect.resetPgJsonFlag();
@@ -1251,12 +741,8 @@ query: ${query}`, []);
1251
741
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1252
742
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
1253
743
  });
1254
- const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1255
- if (didRewrite) {
1256
- this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
1257
- }
1258
- this.logger.logQuery(rewrittenQuery, params);
1259
- return executeInBatches(this.client, rewrittenQuery, params, options);
744
+ this.logger.logQuery(builtQuery.sql, params);
745
+ return executeInBatches(this.client, builtQuery.sql, params, options);
1260
746
  }
1261
747
  executeBatchesRaw(query, options = {}) {
1262
748
  this.dialect.resetPgJsonFlag();
@@ -1266,12 +752,8 @@ query: ${query}`, []);
1266
752
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1267
753
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
1268
754
  });
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);
755
+ this.logger.logQuery(builtQuery.sql, params);
756
+ return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
1275
757
  }
1276
758
  async executeArrow(query) {
1277
759
  this.dialect.resetPgJsonFlag();
@@ -1281,12 +763,8 @@ query: ${query}`, []);
1281
763
  rejectStringArrayLiterals: this.rejectStringArrayLiterals,
1282
764
  warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
1283
765
  });
1284
- const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
1285
- if (didRewrite) {
1286
- this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
1287
- }
1288
- this.logger.logQuery(rewrittenQuery, params);
1289
- return executeArrowOnClient(this.client, rewrittenQuery, params);
766
+ this.logger.logQuery(builtQuery.sql, params);
767
+ return executeArrowOnClient(this.client, builtQuery.sql, params);
1290
768
  }
1291
769
  markRollbackOnly() {
1292
770
  this.rollbackOnly = true;
@@ -1388,6 +866,335 @@ import {
1388
866
  import {
1389
867
  sql as sql2
1390
868
  } from "drizzle-orm";
869
+
870
+ // src/sql/ast-transformer.ts
871
+ import nodeSqlParser from "node-sql-parser";
872
+
873
+ // src/sql/visitors/array-operators.ts
874
+ var OPERATOR_MAP = {
875
+ "@>": { fn: "array_has_all" },
876
+ "<@": { fn: "array_has_all", swap: true },
877
+ "&&": { fn: "array_has_any" }
878
+ };
879
+ function walkExpression(expr, parent, key) {
880
+ if (!expr || typeof expr !== "object")
881
+ return false;
882
+ let transformed = false;
883
+ const exprObj = expr;
884
+ if ("type" in expr && exprObj.type === "binary_expr") {
885
+ const binary = expr;
886
+ const mapping = OPERATOR_MAP[binary.operator];
887
+ if (mapping) {
888
+ const fnExpr = {
889
+ type: "function",
890
+ name: { name: [{ type: "default", value: mapping.fn }] },
891
+ args: {
892
+ type: "expr_list",
893
+ value: mapping.swap ? [binary.right, binary.left] : [binary.left, binary.right]
894
+ }
895
+ };
896
+ if (parent && key) {
897
+ parent[key] = fnExpr;
898
+ }
899
+ transformed = true;
900
+ } else {
901
+ transformed = walkExpression(binary.left, binary, "left") || transformed;
902
+ transformed = walkExpression(binary.right, binary, "right") || transformed;
903
+ }
904
+ }
905
+ if ("type" in expr && exprObj.type === "unary_expr") {
906
+ if ("expr" in exprObj) {
907
+ transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
908
+ }
909
+ }
910
+ if ("type" in expr && exprObj.type === "case") {
911
+ if ("expr" in exprObj && exprObj.expr) {
912
+ transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
913
+ }
914
+ if ("args" in exprObj && Array.isArray(exprObj.args)) {
915
+ for (let i = 0;i < exprObj.args.length; i++) {
916
+ const whenClause = exprObj.args[i];
917
+ if (whenClause.cond) {
918
+ transformed = walkExpression(whenClause.cond, whenClause, "cond") || transformed;
919
+ }
920
+ if (whenClause.result) {
921
+ transformed = walkExpression(whenClause.result, whenClause, "result") || transformed;
922
+ }
923
+ }
924
+ }
925
+ }
926
+ if ("args" in expr && exprObj.args) {
927
+ const args = exprObj.args;
928
+ if ("value" in args && Array.isArray(args.value)) {
929
+ for (let i = 0;i < args.value.length; i++) {
930
+ transformed = walkExpression(args.value[i], args.value, String(i)) || transformed;
931
+ }
932
+ } else if ("expr" in args) {
933
+ transformed = walkExpression(args.expr, args, "expr") || transformed;
934
+ }
935
+ }
936
+ if ("ast" in exprObj && exprObj.ast) {
937
+ const subAst = exprObj.ast;
938
+ if (subAst.type === "select") {
939
+ transformed = walkSelectImpl(subAst) || transformed;
940
+ }
941
+ }
942
+ if ("type" in expr && exprObj.type === "expr_list") {
943
+ if ("value" in exprObj && Array.isArray(exprObj.value)) {
944
+ for (let i = 0;i < exprObj.value.length; i++) {
945
+ transformed = walkExpression(exprObj.value[i], exprObj.value, String(i)) || transformed;
946
+ }
947
+ }
948
+ }
949
+ return transformed;
950
+ }
951
+ function walkFrom(from) {
952
+ if (!from || !Array.isArray(from))
953
+ return false;
954
+ let transformed = false;
955
+ for (const f of from) {
956
+ if ("join" in f) {
957
+ const join = f;
958
+ transformed = walkExpression(join.on, join, "on") || transformed;
959
+ }
960
+ if ("expr" in f && f.expr && "ast" in f.expr) {
961
+ transformed = walkSelectImpl(f.expr.ast) || transformed;
962
+ }
963
+ }
964
+ return transformed;
965
+ }
966
+ function walkSelectImpl(select) {
967
+ let transformed = false;
968
+ if (select.with) {
969
+ for (const cte of select.with) {
970
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
971
+ if (cteSelect && cteSelect.type === "select") {
972
+ transformed = walkSelectImpl(cteSelect) || transformed;
973
+ }
974
+ }
975
+ }
976
+ if (Array.isArray(select.from)) {
977
+ transformed = walkFrom(select.from) || transformed;
978
+ }
979
+ transformed = walkExpression(select.where, select, "where") || transformed;
980
+ if (select.having) {
981
+ if (Array.isArray(select.having)) {
982
+ for (let i = 0;i < select.having.length; i++) {
983
+ transformed = walkExpression(select.having[i], select.having, String(i)) || transformed;
984
+ }
985
+ } else {
986
+ transformed = walkExpression(select.having, select, "having") || transformed;
987
+ }
988
+ }
989
+ if (Array.isArray(select.columns)) {
990
+ for (const col of select.columns) {
991
+ if ("expr" in col) {
992
+ transformed = walkExpression(col.expr, col, "expr") || transformed;
993
+ }
994
+ }
995
+ }
996
+ if (select._next) {
997
+ transformed = walkSelectImpl(select._next) || transformed;
998
+ }
999
+ return transformed;
1000
+ }
1001
+ function transformArrayOperators(ast) {
1002
+ const statements = Array.isArray(ast) ? ast : [ast];
1003
+ let transformed = false;
1004
+ for (const stmt of statements) {
1005
+ if (stmt.type === "select") {
1006
+ transformed = walkSelectImpl(stmt) || transformed;
1007
+ }
1008
+ }
1009
+ return transformed;
1010
+ }
1011
+
1012
+ // src/sql/visitors/column-qualifier.ts
1013
+ function getTableSource(from) {
1014
+ if ("table" in from && from.table) {
1015
+ return {
1016
+ name: from.table,
1017
+ alias: from.as ?? null
1018
+ };
1019
+ }
1020
+ if ("expr" in from && from.as) {
1021
+ return {
1022
+ name: from.as,
1023
+ alias: from.as
1024
+ };
1025
+ }
1026
+ return null;
1027
+ }
1028
+ function getQualifier(source) {
1029
+ return source.alias ?? source.name;
1030
+ }
1031
+ function isUnqualifiedColumnRef(expr) {
1032
+ return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && !(("table" in expr) && expr.table);
1033
+ }
1034
+ function getColumnName(col) {
1035
+ if (typeof col.column === "string") {
1036
+ return col.column;
1037
+ }
1038
+ if (col.column && "expr" in col.column && col.column.expr?.value) {
1039
+ return String(col.column.expr.value);
1040
+ }
1041
+ return null;
1042
+ }
1043
+ function walkOnClause(expr, leftSource, rightSource, ambiguousColumns) {
1044
+ if (!expr || typeof expr !== "object")
1045
+ return false;
1046
+ let transformed = false;
1047
+ if (expr.type === "binary_expr") {
1048
+ if (expr.operator === "=") {
1049
+ const left = expr.left;
1050
+ const right = expr.right;
1051
+ if (isUnqualifiedColumnRef(left) && isUnqualifiedColumnRef(right)) {
1052
+ const leftColName = getColumnName(left);
1053
+ const rightColName = getColumnName(right);
1054
+ if (leftColName && rightColName && leftColName === rightColName) {
1055
+ left.table = leftSource;
1056
+ right.table = rightSource;
1057
+ ambiguousColumns.add(leftColName);
1058
+ transformed = true;
1059
+ }
1060
+ }
1061
+ }
1062
+ if (expr.operator === "AND" || expr.operator === "OR") {
1063
+ transformed = walkOnClause(expr.left, leftSource, rightSource, ambiguousColumns) || transformed;
1064
+ transformed = walkOnClause(expr.right, leftSource, rightSource, ambiguousColumns) || transformed;
1065
+ }
1066
+ }
1067
+ return transformed;
1068
+ }
1069
+ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns) {
1070
+ if (!expr || typeof expr !== "object")
1071
+ return false;
1072
+ let transformed = false;
1073
+ if (isUnqualifiedColumnRef(expr)) {
1074
+ const colName = getColumnName(expr);
1075
+ if (colName && ambiguousColumns.has(colName)) {
1076
+ expr.table = defaultQualifier;
1077
+ transformed = true;
1078
+ }
1079
+ return transformed;
1080
+ }
1081
+ if ("type" in expr && expr.type === "binary_expr") {
1082
+ const binary = expr;
1083
+ transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
1084
+ transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
1085
+ return transformed;
1086
+ }
1087
+ if ("args" in expr && expr.args) {
1088
+ const args = expr.args;
1089
+ if (args.value && Array.isArray(args.value)) {
1090
+ for (const arg of args.value) {
1091
+ transformed = qualifyAmbiguousInExpression(arg, defaultQualifier, ambiguousColumns) || transformed;
1092
+ }
1093
+ }
1094
+ if (args.expr) {
1095
+ transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
1096
+ }
1097
+ }
1098
+ return transformed;
1099
+ }
1100
+ function walkSelect(select) {
1101
+ let transformed = false;
1102
+ const ambiguousColumns = new Set;
1103
+ if (Array.isArray(select.from) && select.from.length >= 2) {
1104
+ const firstSource = getTableSource(select.from[0]);
1105
+ const defaultQualifier = firstSource ? getQualifier(firstSource) : "";
1106
+ let prevSource = firstSource;
1107
+ for (const from of select.from) {
1108
+ if ("join" in from) {
1109
+ const join = from;
1110
+ const currentSource = getTableSource(join);
1111
+ if (join.on && prevSource && currentSource) {
1112
+ const leftQualifier = getQualifier(prevSource);
1113
+ const rightQualifier = getQualifier(currentSource);
1114
+ transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
1115
+ }
1116
+ prevSource = currentSource;
1117
+ } else {
1118
+ const source = getTableSource(from);
1119
+ if (source) {
1120
+ prevSource = source;
1121
+ }
1122
+ }
1123
+ if ("expr" in from && from.expr && "ast" in from.expr) {
1124
+ transformed = walkSelect(from.expr.ast) || transformed;
1125
+ }
1126
+ }
1127
+ if (ambiguousColumns.size > 0 && defaultQualifier) {
1128
+ if (Array.isArray(select.columns)) {
1129
+ for (const col of select.columns) {
1130
+ if ("expr" in col) {
1131
+ transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
1132
+ }
1133
+ }
1134
+ }
1135
+ transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
1136
+ if (Array.isArray(select.orderby)) {
1137
+ for (const order of select.orderby) {
1138
+ if (order.expr) {
1139
+ transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1144
+ }
1145
+ if (select.with) {
1146
+ for (const cte of select.with) {
1147
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
1148
+ if (cteSelect && cteSelect.type === "select") {
1149
+ transformed = walkSelect(cteSelect) || transformed;
1150
+ }
1151
+ }
1152
+ }
1153
+ if (select._next) {
1154
+ transformed = walkSelect(select._next) || transformed;
1155
+ }
1156
+ return transformed;
1157
+ }
1158
+ function qualifyJoinColumns(ast) {
1159
+ const statements = Array.isArray(ast) ? ast : [ast];
1160
+ let transformed = false;
1161
+ for (const stmt of statements) {
1162
+ if (stmt.type === "select") {
1163
+ transformed = walkSelect(stmt) || transformed;
1164
+ }
1165
+ }
1166
+ return transformed;
1167
+ }
1168
+
1169
+ // src/sql/ast-transformer.ts
1170
+ var { Parser } = nodeSqlParser;
1171
+ var parser = new Parser;
1172
+ function transformSQL(query) {
1173
+ const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
1174
+ const needsJoinTransform = query.toLowerCase().includes("join");
1175
+ if (!needsArrayTransform && !needsJoinTransform) {
1176
+ return { sql: query, transformed: false };
1177
+ }
1178
+ try {
1179
+ const ast = parser.astify(query, { database: "PostgreSQL" });
1180
+ let transformed = false;
1181
+ if (needsArrayTransform) {
1182
+ transformed = transformArrayOperators(ast) || transformed;
1183
+ }
1184
+ if (needsJoinTransform) {
1185
+ transformed = qualifyJoinColumns(ast) || transformed;
1186
+ }
1187
+ if (!transformed) {
1188
+ return { sql: query, transformed: false };
1189
+ }
1190
+ const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
1191
+ return { sql: transformedSql, transformed: true };
1192
+ } catch {
1193
+ return { sql: query, transformed: false };
1194
+ }
1195
+ }
1196
+
1197
+ // src/dialect.ts
1391
1198
  class DuckDBDialect extends PgDialect {
1392
1199
  static [entityKind2] = "DuckDBPgDialect";
1393
1200
  hasPgJsonColumn = false;
@@ -1464,6 +1271,14 @@ class DuckDBDialect extends PgDialect {
1464
1271
  return "none";
1465
1272
  }
1466
1273
  }
1274
+ sqlToQuery(sqlObj, invokeSource) {
1275
+ const result = super.sqlToQuery(sqlObj, invokeSource);
1276
+ const transformed = transformSQL(result.sql);
1277
+ return {
1278
+ ...result,
1279
+ sql: transformed.sql
1280
+ };
1281
+ }
1467
1282
  }
1468
1283
 
1469
1284
  // src/select-builder.ts
@@ -1474,10 +1289,10 @@ import {
1474
1289
  } from "drizzle-orm/pg-core/query-builders";
1475
1290
  import { Subquery, ViewBaseConfig } from "drizzle-orm";
1476
1291
  import { PgViewBase } from "drizzle-orm/pg-core/view-base";
1477
- import { SQL as SQL4 } from "drizzle-orm/sql/sql";
1292
+ import { SQL as SQL5 } from "drizzle-orm/sql/sql";
1478
1293
 
1479
1294
  // src/sql/selection.ts
1480
- import { Column as Column2, SQL as SQL3, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
1295
+ import { Column as Column2, SQL as SQL4, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
1481
1296
  function mapEntries(obj, prefix, fullJoin = false) {
1482
1297
  return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
1483
1298
  const qualified = prefix ? `${prefix}.${key}` : key;
@@ -1487,16 +1302,16 @@ function mapEntries(obj, prefix, fullJoin = false) {
1487
1302
  sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
1488
1303
  ];
1489
1304
  }
1490
- if (fullJoin && is3(value, SQL3)) {
1305
+ if (fullJoin && is3(value, SQL4)) {
1491
1306
  const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
1492
1307
  const tableName = col?.table && getTableName2(col?.table);
1493
1308
  return [key, value.as(tableName ? `${tableName}.${key}` : key)];
1494
1309
  }
1495
- if (is3(value, SQL3) || is3(value, Column2)) {
1496
- const aliased = is3(value, SQL3) ? value : sql3`${value}`.mapWith(value);
1310
+ if (is3(value, SQL4) || is3(value, Column2)) {
1311
+ const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
1497
1312
  return [key, aliased.as(qualified)];
1498
1313
  }
1499
- if (is3(value, SQL3.Aliased)) {
1314
+ if (is3(value, SQL4.Aliased)) {
1500
1315
  return [key, value];
1501
1316
  }
1502
1317
  if (typeof value === "object" && value !== null) {
@@ -1544,7 +1359,7 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
1544
1359
  ]));
1545
1360
  } else if (is4(src, PgViewBase)) {
1546
1361
  fields = src[ViewBaseConfig]?.selectedFields;
1547
- } else if (is4(src, SQL4)) {
1362
+ } else if (is4(src, SQL5)) {
1548
1363
  fields = {};
1549
1364
  } else {
1550
1365
  fields = aliasFields(getTableColumns(src), !isPartialSelect);
@@ -1735,16 +1550,6 @@ function createDuckDBConnectionPool(instance, options = {}) {
1735
1550
  }
1736
1551
 
1737
1552
  // 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
1553
  var DEFAULT_PREPARED_CACHE_SIZE = 32;
1749
1554
  function resolvePrepareCacheOption(option) {
1750
1555
  if (!option)
@@ -1774,7 +1579,6 @@ class DuckDBDriver {
1774
1579
  createSession(schema) {
1775
1580
  return new DuckDBSession(this.client, this.dialect, schema, {
1776
1581
  logger: this.options.logger,
1777
- rewriteArrays: this.options.rewriteArrays ?? "auto",
1778
1582
  rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
1779
1583
  prepareCache: this.options.prepareCache
1780
1584
  });
@@ -1789,7 +1593,6 @@ function isConfigObject(data) {
1789
1593
  }
1790
1594
  function createFromClient(client, config = {}, instance) {
1791
1595
  const dialect = new DuckDBDialect;
1792
- const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
1793
1596
  const prepareCache = resolvePrepareCacheOption(config.prepareCache);
1794
1597
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
1795
1598
  let schema;
@@ -1803,7 +1606,6 @@ function createFromClient(client, config = {}, instance) {
1803
1606
  }
1804
1607
  const driver = new DuckDBDriver(client, dialect, {
1805
1608
  logger,
1806
- rewriteArrays: rewriteArraysMode,
1807
1609
  rejectStringArrayLiterals: config.rejectStringArrayLiterals,
1808
1610
  prepareCache
1809
1611
  });