@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.
- package/README.md +2 -3
- package/dist/dialect.d.ts +3 -1
- package/dist/driver.d.ts +1 -3
- package/dist/duckdb-introspect.mjs +355 -553
- package/dist/helpers.mjs +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +374 -556
- package/dist/operators.d.ts +8 -0
- package/dist/options.d.ts +0 -3
- package/dist/session.d.ts +2 -5
- package/dist/sql/ast-transformer.d.ts +15 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +5 -0
- package/dist/utils.d.ts +0 -1
- package/package.json +6 -2
- package/src/columns.ts +6 -1
- package/src/dialect.ts +20 -0
- package/src/driver.ts +0 -8
- package/src/index.ts +1 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +0 -15
- package/src/session.ts +10 -96
- package/src/sql/ast-transformer.ts +68 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +278 -0
- package/src/utils.ts +0 -1
- package/dist/sql/query-rewriters.d.ts +0 -14
- package/src/sql/query-rewriters.ts +0 -708
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1270
|
-
|
|
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
|
-
|
|
1285
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
1496
|
-
const aliased = is3(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,
|
|
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,
|
|
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
|
});
|