@leonardovida-md/drizzle-neo-duckdb 1.1.4 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/dialect.d.ts +3 -1
- package/dist/driver.d.ts +1 -3
- package/dist/duckdb-introspect.mjs +552 -837
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +560 -832
- 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 +31 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +10 -0
- package/dist/utils.d.ts +0 -1
- package/package.json +4 -3
- 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 +144 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +565 -0
- package/src/utils.ts +0 -1
- package/dist/sql/query-rewriters.d.ts +0 -15
- package/src/sql/query-rewriters.ts +0 -1161
package/dist/index.mjs
CHANGED
|
@@ -15,765 +15,6 @@ import { PgTransaction } from "drizzle-orm/pg-core";
|
|
|
15
15
|
import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
|
|
16
16
|
import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
|
|
17
17
|
|
|
18
|
-
// src/sql/query-rewriters.ts
|
|
19
|
-
var OPERATORS = [
|
|
20
|
-
{ token: "@>", fn: "array_has_all" },
|
|
21
|
-
{ token: "<@", fn: "array_has_all", swap: true },
|
|
22
|
-
{ token: "&&", fn: "array_has_any" }
|
|
23
|
-
];
|
|
24
|
-
var isWhitespace = (char) => char !== undefined && /\s/.test(char);
|
|
25
|
-
function scrubForRewrite(query) {
|
|
26
|
-
let scrubbed = "";
|
|
27
|
-
let state = "code";
|
|
28
|
-
for (let i = 0;i < query.length; i += 1) {
|
|
29
|
-
const char = query[i];
|
|
30
|
-
const next = query[i + 1];
|
|
31
|
-
if (state === "code") {
|
|
32
|
-
if (char === "'") {
|
|
33
|
-
scrubbed += "'";
|
|
34
|
-
state = "single";
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (char === '"') {
|
|
38
|
-
scrubbed += '"';
|
|
39
|
-
state = "double";
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (char === "-" && next === "-") {
|
|
43
|
-
scrubbed += " ";
|
|
44
|
-
i += 1;
|
|
45
|
-
state = "lineComment";
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (char === "/" && next === "*") {
|
|
49
|
-
scrubbed += " ";
|
|
50
|
-
i += 1;
|
|
51
|
-
state = "blockComment";
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
scrubbed += char;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (state === "single") {
|
|
58
|
-
if (char === "'" && next === "'") {
|
|
59
|
-
scrubbed += "''";
|
|
60
|
-
i += 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
scrubbed += char === "'" ? "'" : ".";
|
|
64
|
-
if (char === "'") {
|
|
65
|
-
state = "code";
|
|
66
|
-
}
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (state === "double") {
|
|
70
|
-
if (char === '"' && next === '"') {
|
|
71
|
-
scrubbed += '""';
|
|
72
|
-
i += 1;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
scrubbed += char === '"' ? '"' : ".";
|
|
76
|
-
if (char === '"') {
|
|
77
|
-
state = "code";
|
|
78
|
-
}
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (state === "lineComment") {
|
|
82
|
-
scrubbed += char === `
|
|
83
|
-
` ? `
|
|
84
|
-
` : " ";
|
|
85
|
-
if (char === `
|
|
86
|
-
`) {
|
|
87
|
-
state = "code";
|
|
88
|
-
}
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (state === "blockComment") {
|
|
92
|
-
if (char === "*" && next === "/") {
|
|
93
|
-
scrubbed += " ";
|
|
94
|
-
i += 1;
|
|
95
|
-
state = "code";
|
|
96
|
-
} else {
|
|
97
|
-
scrubbed += " ";
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return scrubbed;
|
|
102
|
-
}
|
|
103
|
-
function findNextOperator(scrubbed, start) {
|
|
104
|
-
for (let idx = start;idx < scrubbed.length; idx += 1) {
|
|
105
|
-
for (const operator of OPERATORS) {
|
|
106
|
-
if (scrubbed.startsWith(operator.token, idx)) {
|
|
107
|
-
return { index: idx, operator };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
function walkLeft(source, scrubbed, start) {
|
|
114
|
-
let idx = start;
|
|
115
|
-
while (idx >= 0 && isWhitespace(scrubbed[idx])) {
|
|
116
|
-
idx -= 1;
|
|
117
|
-
}
|
|
118
|
-
let depth = 0;
|
|
119
|
-
for (;idx >= 0; idx -= 1) {
|
|
120
|
-
const ch = scrubbed[idx];
|
|
121
|
-
if (ch === ")" || ch === "]") {
|
|
122
|
-
depth += 1;
|
|
123
|
-
} else if (ch === "(" || ch === "[") {
|
|
124
|
-
if (depth === 0) {
|
|
125
|
-
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
126
|
-
}
|
|
127
|
-
depth = Math.max(0, depth - 1);
|
|
128
|
-
} else if (depth === 0 && isWhitespace(ch)) {
|
|
129
|
-
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return [0, source.slice(0, start + 1)];
|
|
133
|
-
}
|
|
134
|
-
function walkRight(source, scrubbed, start) {
|
|
135
|
-
let idx = start;
|
|
136
|
-
while (idx < scrubbed.length && isWhitespace(scrubbed[idx])) {
|
|
137
|
-
idx += 1;
|
|
138
|
-
}
|
|
139
|
-
let depth = 0;
|
|
140
|
-
for (;idx < scrubbed.length; idx += 1) {
|
|
141
|
-
const ch = scrubbed[idx];
|
|
142
|
-
if (ch === "(" || ch === "[") {
|
|
143
|
-
depth += 1;
|
|
144
|
-
} else if (ch === ")" || ch === "]") {
|
|
145
|
-
if (depth === 0) {
|
|
146
|
-
return [idx, source.slice(start, idx)];
|
|
147
|
-
}
|
|
148
|
-
depth = Math.max(0, depth - 1);
|
|
149
|
-
} else if (depth === 0 && isWhitespace(ch)) {
|
|
150
|
-
return [idx, source.slice(start, idx)];
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return [scrubbed.length, source.slice(start)];
|
|
154
|
-
}
|
|
155
|
-
function adaptArrayOperators(query) {
|
|
156
|
-
if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
|
|
157
|
-
return query;
|
|
158
|
-
}
|
|
159
|
-
let rewritten = query;
|
|
160
|
-
let scrubbed = scrubForRewrite(query);
|
|
161
|
-
let searchStart = 0;
|
|
162
|
-
while (true) {
|
|
163
|
-
const next = findNextOperator(scrubbed, searchStart);
|
|
164
|
-
if (!next)
|
|
165
|
-
break;
|
|
166
|
-
const { index, operator } = next;
|
|
167
|
-
const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
|
|
168
|
-
const [rightEnd, rightExpr] = walkRight(rewritten, scrubbed, index + operator.token.length);
|
|
169
|
-
const left = leftExpr.trim();
|
|
170
|
-
const right = rightExpr.trim();
|
|
171
|
-
const replacement = `${operator.fn}(${operator.swap ? right : left}, ${operator.swap ? left : right})`;
|
|
172
|
-
rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
|
|
173
|
-
scrubbed = scrubForRewrite(rewritten);
|
|
174
|
-
searchStart = leftStart + replacement.length;
|
|
175
|
-
}
|
|
176
|
-
return rewritten;
|
|
177
|
-
}
|
|
178
|
-
function extractQuotedIdentifier(original, start) {
|
|
179
|
-
if (original[start] !== '"') {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
let pos = start + 1;
|
|
183
|
-
while (pos < original.length) {
|
|
184
|
-
if (original[pos] === '"') {
|
|
185
|
-
if (original[pos + 1] === '"') {
|
|
186
|
-
pos += 2;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
pos++;
|
|
192
|
-
}
|
|
193
|
-
if (pos >= original.length) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
name: original.slice(start + 1, pos),
|
|
198
|
-
end: pos + 1
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
function findMainFromClause(scrubbed) {
|
|
202
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
203
|
-
let searchStart = 0;
|
|
204
|
-
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
205
|
-
if (withMatch) {
|
|
206
|
-
let depth = 0;
|
|
207
|
-
let pos = withMatch.index + withMatch[0].length;
|
|
208
|
-
while (pos < scrubbed.length) {
|
|
209
|
-
const char = scrubbed[pos];
|
|
210
|
-
if (char === "(") {
|
|
211
|
-
depth++;
|
|
212
|
-
} else if (char === ")") {
|
|
213
|
-
depth--;
|
|
214
|
-
} else if (depth === 0) {
|
|
215
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
216
|
-
if (/^\s*select\s+/i.test(remaining)) {
|
|
217
|
-
searchStart = pos;
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
pos++;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
const fromPattern = /\bfrom\s+/gi;
|
|
225
|
-
fromPattern.lastIndex = searchStart;
|
|
226
|
-
const fromMatch = fromPattern.exec(lowerScrubbed);
|
|
227
|
-
return fromMatch ? fromMatch.index + fromMatch[0].length : -1;
|
|
228
|
-
}
|
|
229
|
-
function parseTableSources(original, scrubbed) {
|
|
230
|
-
const sources = [];
|
|
231
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
232
|
-
const fromPos = findMainFromClause(scrubbed);
|
|
233
|
-
if (fromPos < 0) {
|
|
234
|
-
return sources;
|
|
235
|
-
}
|
|
236
|
-
const fromTable = parseTableRef(original, scrubbed, fromPos);
|
|
237
|
-
if (fromTable) {
|
|
238
|
-
sources.push(fromTable);
|
|
239
|
-
}
|
|
240
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+/gi;
|
|
241
|
-
joinPattern.lastIndex = fromPos;
|
|
242
|
-
let joinMatch;
|
|
243
|
-
while ((joinMatch = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
244
|
-
const tableStart = joinMatch.index + joinMatch[0].length;
|
|
245
|
-
const joinTable = parseTableRef(original, scrubbed, tableStart);
|
|
246
|
-
if (joinTable) {
|
|
247
|
-
sources.push(joinTable);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return sources;
|
|
251
|
-
}
|
|
252
|
-
function parseTableRef(original, scrubbed, start) {
|
|
253
|
-
let pos = start;
|
|
254
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
255
|
-
pos++;
|
|
256
|
-
}
|
|
257
|
-
if (scrubbed[pos] === "(") {
|
|
258
|
-
const nameStart2 = pos;
|
|
259
|
-
let depth = 1;
|
|
260
|
-
pos++;
|
|
261
|
-
while (pos < scrubbed.length && depth > 0) {
|
|
262
|
-
if (scrubbed[pos] === "(")
|
|
263
|
-
depth++;
|
|
264
|
-
else if (scrubbed[pos] === ")")
|
|
265
|
-
depth--;
|
|
266
|
-
pos++;
|
|
267
|
-
}
|
|
268
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
269
|
-
pos++;
|
|
270
|
-
}
|
|
271
|
-
const afterSubquery = scrubbed.slice(pos).toLowerCase();
|
|
272
|
-
if (afterSubquery.startsWith("as ")) {
|
|
273
|
-
pos += 3;
|
|
274
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
275
|
-
pos++;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (original[pos] === '"') {
|
|
279
|
-
const aliasIdent = extractQuotedIdentifier(original, pos);
|
|
280
|
-
if (aliasIdent) {
|
|
281
|
-
return {
|
|
282
|
-
name: aliasIdent.name,
|
|
283
|
-
alias: aliasIdent.name,
|
|
284
|
-
position: nameStart2
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
if (original[pos] !== '"') {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
const nameStart = pos;
|
|
294
|
-
const firstIdent = extractQuotedIdentifier(original, pos);
|
|
295
|
-
if (!firstIdent) {
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
let name = firstIdent.name;
|
|
299
|
-
pos = firstIdent.end;
|
|
300
|
-
let afterName = pos;
|
|
301
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
302
|
-
afterName++;
|
|
303
|
-
}
|
|
304
|
-
if (scrubbed[afterName] === ".") {
|
|
305
|
-
afterName++;
|
|
306
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
307
|
-
afterName++;
|
|
308
|
-
}
|
|
309
|
-
if (original[afterName] === '"') {
|
|
310
|
-
const tableIdent = extractQuotedIdentifier(original, afterName);
|
|
311
|
-
if (tableIdent) {
|
|
312
|
-
name = tableIdent.name;
|
|
313
|
-
pos = tableIdent.end;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
let alias;
|
|
318
|
-
let aliasPos = pos;
|
|
319
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
320
|
-
aliasPos++;
|
|
321
|
-
}
|
|
322
|
-
const afterTable = scrubbed.slice(aliasPos).toLowerCase();
|
|
323
|
-
if (afterTable.startsWith("as ")) {
|
|
324
|
-
aliasPos += 3;
|
|
325
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
326
|
-
aliasPos++;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const afterAlias = scrubbed.slice(aliasPos).toLowerCase();
|
|
330
|
-
if (original[aliasPos] === '"' && !afterAlias.startsWith("on ") && !afterAlias.startsWith("left ") && !afterAlias.startsWith("right ") && !afterAlias.startsWith("inner ") && !afterAlias.startsWith("full ") && !afterAlias.startsWith("cross ") && !afterAlias.startsWith("join ") && !afterAlias.startsWith("where ") && !afterAlias.startsWith("group ") && !afterAlias.startsWith("order ") && !afterAlias.startsWith("limit ")) {
|
|
331
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
332
|
-
if (aliasIdent) {
|
|
333
|
-
alias = aliasIdent.name;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
name,
|
|
338
|
-
alias,
|
|
339
|
-
position: nameStart
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
function findJoinClauses(original, scrubbed, sources, fromPos) {
|
|
343
|
-
const clauses = [];
|
|
344
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
345
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
|
|
346
|
-
joinPattern.lastIndex = fromPos;
|
|
347
|
-
let match;
|
|
348
|
-
let sourceIndex = 1;
|
|
349
|
-
while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
350
|
-
const joinType = (match[1] || "").trim().toLowerCase();
|
|
351
|
-
const joinKeywordEnd = match.index + (match[1] || "").length + "join".length;
|
|
352
|
-
let tableStart = joinKeywordEnd;
|
|
353
|
-
while (tableStart < original.length && isWhitespace(original[tableStart])) {
|
|
354
|
-
tableStart++;
|
|
355
|
-
}
|
|
356
|
-
const tableIdent = extractQuotedIdentifier(original, tableStart);
|
|
357
|
-
if (!tableIdent)
|
|
358
|
-
continue;
|
|
359
|
-
let tableName = tableIdent.name;
|
|
360
|
-
let afterTable = tableIdent.end;
|
|
361
|
-
let checkPos = afterTable;
|
|
362
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
363
|
-
checkPos++;
|
|
364
|
-
}
|
|
365
|
-
if (scrubbed[checkPos] === ".") {
|
|
366
|
-
checkPos++;
|
|
367
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
368
|
-
checkPos++;
|
|
369
|
-
}
|
|
370
|
-
const realTableIdent = extractQuotedIdentifier(original, checkPos);
|
|
371
|
-
if (realTableIdent) {
|
|
372
|
-
tableName = realTableIdent.name;
|
|
373
|
-
afterTable = realTableIdent.end;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
let tableAlias;
|
|
377
|
-
let aliasPos = afterTable;
|
|
378
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
379
|
-
aliasPos++;
|
|
380
|
-
}
|
|
381
|
-
const afterTableStr = scrubbed.slice(aliasPos).toLowerCase();
|
|
382
|
-
if (afterTableStr.startsWith("as ")) {
|
|
383
|
-
aliasPos += 3;
|
|
384
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
385
|
-
aliasPos++;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (original[aliasPos] === '"' && !afterTableStr.startsWith("on ")) {
|
|
389
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
390
|
-
if (aliasIdent) {
|
|
391
|
-
tableAlias = aliasIdent.name;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
const onStart = match.index + match[0].length;
|
|
395
|
-
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;
|
|
396
|
-
const remaining = lowerScrubbed.slice(onStart);
|
|
397
|
-
const endMatch = endPattern.exec(remaining);
|
|
398
|
-
const onEnd = endMatch ? onStart + endMatch.index : scrubbed.length;
|
|
399
|
-
let leftSource = "";
|
|
400
|
-
if (sourceIndex > 0 && sourceIndex <= sources.length) {
|
|
401
|
-
const prev = sources[sourceIndex - 1];
|
|
402
|
-
leftSource = prev?.alias || prev?.name || "";
|
|
403
|
-
}
|
|
404
|
-
const rightSource = tableAlias || tableName;
|
|
405
|
-
clauses.push({
|
|
406
|
-
joinType,
|
|
407
|
-
tableName,
|
|
408
|
-
tableAlias,
|
|
409
|
-
onStart,
|
|
410
|
-
onEnd,
|
|
411
|
-
leftSource,
|
|
412
|
-
rightSource
|
|
413
|
-
});
|
|
414
|
-
sourceIndex++;
|
|
415
|
-
}
|
|
416
|
-
return clauses;
|
|
417
|
-
}
|
|
418
|
-
function findMainSelectClause(scrubbed) {
|
|
419
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
420
|
-
let searchStart = 0;
|
|
421
|
-
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
422
|
-
if (withMatch) {
|
|
423
|
-
let depth2 = 0;
|
|
424
|
-
let pos2 = withMatch.index + withMatch[0].length;
|
|
425
|
-
while (pos2 < scrubbed.length) {
|
|
426
|
-
const char = scrubbed[pos2];
|
|
427
|
-
if (char === "(") {
|
|
428
|
-
depth2++;
|
|
429
|
-
} else if (char === ")") {
|
|
430
|
-
depth2--;
|
|
431
|
-
} else if (depth2 === 0) {
|
|
432
|
-
const remaining = lowerScrubbed.slice(pos2);
|
|
433
|
-
if (/^\s*select\s+/i.test(remaining)) {
|
|
434
|
-
searchStart = pos2;
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
pos2++;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
const selectPattern = /\bselect\s+/gi;
|
|
442
|
-
selectPattern.lastIndex = searchStart;
|
|
443
|
-
const selectMatch = selectPattern.exec(lowerScrubbed);
|
|
444
|
-
if (!selectMatch)
|
|
445
|
-
return null;
|
|
446
|
-
const selectStart = selectMatch.index + selectMatch[0].length;
|
|
447
|
-
let depth = 0;
|
|
448
|
-
let pos = selectStart;
|
|
449
|
-
while (pos < scrubbed.length) {
|
|
450
|
-
const char = scrubbed[pos];
|
|
451
|
-
if (char === "(") {
|
|
452
|
-
depth++;
|
|
453
|
-
} else if (char === ")") {
|
|
454
|
-
depth--;
|
|
455
|
-
} else if (depth === 0) {
|
|
456
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
457
|
-
if (/^\s*from\s+/i.test(remaining)) {
|
|
458
|
-
return { start: selectStart, end: pos };
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
pos++;
|
|
462
|
-
}
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
function findWhereClause(scrubbed, fromPos) {
|
|
466
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
467
|
-
let depth = 0;
|
|
468
|
-
let pos = fromPos;
|
|
469
|
-
let whereStart = -1;
|
|
470
|
-
while (pos < scrubbed.length) {
|
|
471
|
-
const char = scrubbed[pos];
|
|
472
|
-
if (char === "(") {
|
|
473
|
-
depth++;
|
|
474
|
-
} else if (char === ")") {
|
|
475
|
-
depth--;
|
|
476
|
-
} else if (depth === 0) {
|
|
477
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
478
|
-
if (whereStart === -1 && /^\s*where\s+/i.test(remaining)) {
|
|
479
|
-
const match = /^\s*where\s+/i.exec(remaining);
|
|
480
|
-
if (match) {
|
|
481
|
-
whereStart = pos + match[0].length;
|
|
482
|
-
}
|
|
483
|
-
} else if (whereStart !== -1 && /^\s*(group\s+by|order\s+by|limit|having|union|intersect|except|$)/i.test(remaining)) {
|
|
484
|
-
return { start: whereStart, end: pos };
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
pos++;
|
|
488
|
-
}
|
|
489
|
-
if (whereStart !== -1) {
|
|
490
|
-
return { start: whereStart, end: scrubbed.length };
|
|
491
|
-
}
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
function findOrderByClause(scrubbed, fromPos) {
|
|
495
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
496
|
-
let depth = 0;
|
|
497
|
-
let pos = fromPos;
|
|
498
|
-
let orderStart = -1;
|
|
499
|
-
while (pos < scrubbed.length) {
|
|
500
|
-
const char = scrubbed[pos];
|
|
501
|
-
if (char === "(") {
|
|
502
|
-
depth++;
|
|
503
|
-
} else if (char === ")") {
|
|
504
|
-
depth--;
|
|
505
|
-
} else if (depth === 0) {
|
|
506
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
507
|
-
if (orderStart === -1 && /^\s*order\s+by\s+/i.test(remaining)) {
|
|
508
|
-
const match = /^\s*order\s+by\s+/i.exec(remaining);
|
|
509
|
-
if (match) {
|
|
510
|
-
orderStart = pos + match[0].length;
|
|
511
|
-
}
|
|
512
|
-
} else if (orderStart !== -1 && /^\s*(limit|offset|fetch|for\s+update|$)/i.test(remaining)) {
|
|
513
|
-
return { start: orderStart, end: pos };
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
pos++;
|
|
517
|
-
}
|
|
518
|
-
if (orderStart !== -1) {
|
|
519
|
-
return { start: orderStart, end: scrubbed.length };
|
|
520
|
-
}
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
function qualifyClauseColumnsSelective(original, scrubbed, clauseStart, clauseEnd, defaultSource, ambiguousColumns) {
|
|
524
|
-
const clauseOriginal = original.slice(clauseStart, clauseEnd);
|
|
525
|
-
const clauseScrubbed = scrubbed.slice(clauseStart, clauseEnd);
|
|
526
|
-
let result = clauseOriginal;
|
|
527
|
-
let offset = 0;
|
|
528
|
-
let pos = 0;
|
|
529
|
-
while (pos < clauseScrubbed.length) {
|
|
530
|
-
const quotePos = clauseScrubbed.indexOf('"', pos);
|
|
531
|
-
if (quotePos === -1)
|
|
532
|
-
break;
|
|
533
|
-
if (quotePos > 0 && clauseScrubbed[quotePos - 1] === ".") {
|
|
534
|
-
const ident2 = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
535
|
-
pos = ident2 ? ident2.end : quotePos + 1;
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
const ident = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
539
|
-
if (!ident) {
|
|
540
|
-
pos = quotePos + 1;
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
if (ident.end < clauseScrubbed.length && clauseScrubbed[ident.end] === ".") {
|
|
544
|
-
pos = ident.end + 1;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
if (!ambiguousColumns.has(ident.name)) {
|
|
548
|
-
pos = ident.end;
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
let afterIdent = ident.end;
|
|
552
|
-
while (afterIdent < clauseScrubbed.length && isWhitespace(clauseScrubbed[afterIdent])) {
|
|
553
|
-
afterIdent++;
|
|
554
|
-
}
|
|
555
|
-
if (clauseScrubbed[afterIdent] === "(") {
|
|
556
|
-
pos = ident.end;
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
const beforeQuote = clauseScrubbed.slice(0, quotePos).toLowerCase();
|
|
560
|
-
if (/\bas\s*$/i.test(beforeQuote)) {
|
|
561
|
-
pos = ident.end;
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
const qualified = `"${defaultSource}"."${ident.name}"`;
|
|
565
|
-
const oldLength = ident.end - quotePos;
|
|
566
|
-
result = result.slice(0, quotePos + offset) + qualified + result.slice(quotePos + oldLength + offset);
|
|
567
|
-
offset += qualified.length - oldLength;
|
|
568
|
-
pos = ident.end;
|
|
569
|
-
}
|
|
570
|
-
return { result, offset };
|
|
571
|
-
}
|
|
572
|
-
function qualifyJoinColumns(query) {
|
|
573
|
-
const lowerQuery = query.toLowerCase();
|
|
574
|
-
if (!lowerQuery.includes("join")) {
|
|
575
|
-
return query;
|
|
576
|
-
}
|
|
577
|
-
const scrubbed = scrubForRewrite(query);
|
|
578
|
-
const fromPos = findMainFromClause(scrubbed);
|
|
579
|
-
if (fromPos < 0) {
|
|
580
|
-
return query;
|
|
581
|
-
}
|
|
582
|
-
const sources = parseTableSources(query, scrubbed);
|
|
583
|
-
if (sources.length < 2) {
|
|
584
|
-
return query;
|
|
585
|
-
}
|
|
586
|
-
const joinClauses = findJoinClauses(query, scrubbed, sources, fromPos);
|
|
587
|
-
if (joinClauses.length === 0) {
|
|
588
|
-
return query;
|
|
589
|
-
}
|
|
590
|
-
const firstSource = sources[0];
|
|
591
|
-
const defaultQualifier = firstSource.alias || firstSource.name;
|
|
592
|
-
let result = query;
|
|
593
|
-
let totalOffset = 0;
|
|
594
|
-
for (const join of joinClauses) {
|
|
595
|
-
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
596
|
-
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
597
|
-
let clauseResult = originalOnClause;
|
|
598
|
-
let clauseOffset = 0;
|
|
599
|
-
let eqPos = -1;
|
|
600
|
-
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
601
|
-
let lhsEnd = eqPos - 1;
|
|
602
|
-
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
603
|
-
lhsEnd--;
|
|
604
|
-
}
|
|
605
|
-
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
606
|
-
continue;
|
|
607
|
-
let lhsStartPos = lhsEnd - 1;
|
|
608
|
-
while (lhsStartPos >= 0) {
|
|
609
|
-
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
610
|
-
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
611
|
-
lhsStartPos -= 2;
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
616
|
-
lhsStartPos--;
|
|
617
|
-
}
|
|
618
|
-
if (lhsStartPos < 0)
|
|
619
|
-
continue;
|
|
620
|
-
const lhsIsQualified = lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === ".";
|
|
621
|
-
let rhsStartPos = eqPos + 1;
|
|
622
|
-
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
623
|
-
rhsStartPos++;
|
|
624
|
-
}
|
|
625
|
-
const rhsChar = originalOnClause[rhsStartPos];
|
|
626
|
-
const rhsIsParam = rhsChar === "$";
|
|
627
|
-
const rhsIsStringLiteral = rhsChar === "'";
|
|
628
|
-
const rhsIsColumn = rhsChar === '"';
|
|
629
|
-
if (!rhsIsParam && !rhsIsStringLiteral && !rhsIsColumn)
|
|
630
|
-
continue;
|
|
631
|
-
const rhsIsQualified = !rhsIsColumn || rhsStartPos > 0 && scrubbedOnClause[rhsStartPos - 1] === ".";
|
|
632
|
-
if (lhsIsQualified || rhsIsQualified)
|
|
633
|
-
continue;
|
|
634
|
-
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
635
|
-
if (!lhsIdent)
|
|
636
|
-
continue;
|
|
637
|
-
let rhsIdent = null;
|
|
638
|
-
let rhsValue = "";
|
|
639
|
-
let rhsEnd = rhsStartPos;
|
|
640
|
-
if (rhsIsParam) {
|
|
641
|
-
let paramEnd = rhsStartPos + 1;
|
|
642
|
-
while (paramEnd < originalOnClause.length && /\d/.test(originalOnClause[paramEnd])) {
|
|
643
|
-
paramEnd++;
|
|
644
|
-
}
|
|
645
|
-
rhsValue = originalOnClause.slice(rhsStartPos, paramEnd);
|
|
646
|
-
rhsEnd = paramEnd;
|
|
647
|
-
} else if (rhsIsStringLiteral) {
|
|
648
|
-
let literalEnd = rhsStartPos + 1;
|
|
649
|
-
while (literalEnd < originalOnClause.length) {
|
|
650
|
-
if (originalOnClause[literalEnd] === "'") {
|
|
651
|
-
if (originalOnClause[literalEnd + 1] === "'") {
|
|
652
|
-
literalEnd += 2;
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
literalEnd++;
|
|
658
|
-
}
|
|
659
|
-
rhsValue = originalOnClause.slice(rhsStartPos, literalEnd + 1);
|
|
660
|
-
rhsEnd = literalEnd + 1;
|
|
661
|
-
} else if (rhsIsColumn) {
|
|
662
|
-
rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
663
|
-
if (rhsIdent) {
|
|
664
|
-
if (rhsIdent.end < scrubbedOnClause.length && scrubbedOnClause[rhsIdent.end] === ".") {
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
rhsValue = `"${rhsIdent.name}"`;
|
|
668
|
-
rhsEnd = rhsIdent.end;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
if (!rhsValue)
|
|
672
|
-
continue;
|
|
673
|
-
if (!rhsIsColumn || !rhsIdent || lhsIdent.name !== rhsIdent.name) {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
const lhsOriginal = `"${lhsIdent.name}"`;
|
|
677
|
-
let newLhs = lhsOriginal;
|
|
678
|
-
let newRhs = rhsValue;
|
|
679
|
-
if (!lhsIsQualified && join.leftSource) {
|
|
680
|
-
newLhs = `"${join.leftSource}"."${lhsIdent.name}"`;
|
|
681
|
-
}
|
|
682
|
-
if (!rhsIsQualified && rhsIsColumn && rhsIdent && join.rightSource) {
|
|
683
|
-
newRhs = `"${join.rightSource}"."${rhsIdent.name}"`;
|
|
684
|
-
}
|
|
685
|
-
if (newLhs !== lhsOriginal || newRhs !== rhsValue) {
|
|
686
|
-
const opStart = lhsIdent.end;
|
|
687
|
-
let opEnd = opStart;
|
|
688
|
-
while (opEnd < rhsEnd && (isWhitespace(originalOnClause[opEnd]) || originalOnClause[opEnd] === "=")) {
|
|
689
|
-
opEnd++;
|
|
690
|
-
}
|
|
691
|
-
const operator = originalOnClause.slice(opStart, opEnd);
|
|
692
|
-
const newExpr = `${newLhs}${operator}${newRhs}`;
|
|
693
|
-
const oldExprLength = rhsEnd - lhsStartPos;
|
|
694
|
-
clauseResult = clauseResult.slice(0, lhsStartPos + clauseOffset) + newExpr + clauseResult.slice(lhsStartPos + oldExprLength + clauseOffset);
|
|
695
|
-
clauseOffset += newExpr.length - oldExprLength;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
if (clauseResult !== originalOnClause) {
|
|
699
|
-
result = result.slice(0, join.onStart + totalOffset) + clauseResult + result.slice(join.onEnd + totalOffset);
|
|
700
|
-
totalOffset += clauseResult.length - originalOnClause.length;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
const ambiguousColumns = new Set;
|
|
704
|
-
for (const join of joinClauses) {
|
|
705
|
-
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
706
|
-
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
707
|
-
let eqPos = -1;
|
|
708
|
-
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
709
|
-
let lhsEnd = eqPos - 1;
|
|
710
|
-
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
711
|
-
lhsEnd--;
|
|
712
|
-
}
|
|
713
|
-
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
714
|
-
continue;
|
|
715
|
-
let lhsStartPos = lhsEnd - 1;
|
|
716
|
-
while (lhsStartPos >= 0) {
|
|
717
|
-
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
718
|
-
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
719
|
-
lhsStartPos -= 2;
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
break;
|
|
723
|
-
}
|
|
724
|
-
lhsStartPos--;
|
|
725
|
-
}
|
|
726
|
-
if (lhsStartPos < 0)
|
|
727
|
-
continue;
|
|
728
|
-
let rhsStartPos = eqPos + 1;
|
|
729
|
-
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
730
|
-
rhsStartPos++;
|
|
731
|
-
}
|
|
732
|
-
if (originalOnClause[rhsStartPos] !== '"')
|
|
733
|
-
continue;
|
|
734
|
-
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
735
|
-
const rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
736
|
-
if (lhsIdent && rhsIdent && lhsIdent.name === rhsIdent.name) {
|
|
737
|
-
ambiguousColumns.add(lhsIdent.name);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (ambiguousColumns.size === 0) {
|
|
742
|
-
return result;
|
|
743
|
-
}
|
|
744
|
-
const updatedScrubbed = scrubForRewrite(result);
|
|
745
|
-
const selectClause = findMainSelectClause(updatedScrubbed);
|
|
746
|
-
if (selectClause) {
|
|
747
|
-
const { result: selectResult, offset: selectOffset } = qualifyClauseColumnsSelective(result, updatedScrubbed, selectClause.start, selectClause.end, defaultQualifier, ambiguousColumns);
|
|
748
|
-
if (selectOffset !== 0) {
|
|
749
|
-
result = result.slice(0, selectClause.start) + selectResult + result.slice(selectClause.end);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
const scrubbed2 = scrubForRewrite(result);
|
|
753
|
-
const fromPos2 = findMainFromClause(scrubbed2);
|
|
754
|
-
if (fromPos2 >= 0) {
|
|
755
|
-
const whereClause = findWhereClause(scrubbed2, fromPos2);
|
|
756
|
-
if (whereClause) {
|
|
757
|
-
const { result: whereResult, offset: whereOffset } = qualifyClauseColumnsSelective(result, scrubbed2, whereClause.start, whereClause.end, defaultQualifier, ambiguousColumns);
|
|
758
|
-
if (whereOffset !== 0) {
|
|
759
|
-
result = result.slice(0, whereClause.start) + whereResult + result.slice(whereClause.end);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
const scrubbed3 = scrubForRewrite(result);
|
|
764
|
-
const fromPos3 = findMainFromClause(scrubbed3);
|
|
765
|
-
if (fromPos3 >= 0) {
|
|
766
|
-
const orderByClause = findOrderByClause(scrubbed3, fromPos3);
|
|
767
|
-
if (orderByClause) {
|
|
768
|
-
const { result: orderResult, offset: orderOffset } = qualifyClauseColumnsSelective(result, scrubbed3, orderByClause.start, orderByClause.end, defaultQualifier, ambiguousColumns);
|
|
769
|
-
if (orderOffset !== 0) {
|
|
770
|
-
result = result.slice(0, orderByClause.start) + orderResult + result.slice(orderByClause.end);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return result;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
18
|
// src/sql/result-mapper.ts
|
|
778
19
|
import {
|
|
779
20
|
Column,
|
|
@@ -1409,24 +650,6 @@ function isSavepointSyntaxError(error) {
|
|
|
1409
650
|
}
|
|
1410
651
|
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
1411
652
|
}
|
|
1412
|
-
function rewriteQuery(mode, query) {
|
|
1413
|
-
if (mode === "never") {
|
|
1414
|
-
return { sql: query, rewritten: false };
|
|
1415
|
-
}
|
|
1416
|
-
let result = query;
|
|
1417
|
-
let wasRewritten = false;
|
|
1418
|
-
const arrayRewritten = adaptArrayOperators(result);
|
|
1419
|
-
if (arrayRewritten !== result) {
|
|
1420
|
-
result = arrayRewritten;
|
|
1421
|
-
wasRewritten = true;
|
|
1422
|
-
}
|
|
1423
|
-
const joinQualified = qualifyJoinColumns(result);
|
|
1424
|
-
if (joinQualified !== result) {
|
|
1425
|
-
result = joinQualified;
|
|
1426
|
-
wasRewritten = true;
|
|
1427
|
-
}
|
|
1428
|
-
return { sql: result, rewritten: wasRewritten };
|
|
1429
|
-
}
|
|
1430
653
|
|
|
1431
654
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
1432
655
|
client;
|
|
@@ -1437,12 +660,11 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1437
660
|
fields;
|
|
1438
661
|
_isResponseInArrayMode;
|
|
1439
662
|
customResultMapper;
|
|
1440
|
-
rewriteArraysMode;
|
|
1441
663
|
rejectStringArrayLiterals;
|
|
1442
664
|
prepareCache;
|
|
1443
665
|
warnOnStringArrayLiteral;
|
|
1444
666
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
1445
|
-
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper,
|
|
667
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
1446
668
|
super({ sql: queryString, params });
|
|
1447
669
|
this.client = client;
|
|
1448
670
|
this.dialect = dialect;
|
|
@@ -1452,7 +674,6 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1452
674
|
this.fields = fields;
|
|
1453
675
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
1454
676
|
this.customResultMapper = customResultMapper;
|
|
1455
|
-
this.rewriteArraysMode = rewriteArraysMode;
|
|
1456
677
|
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
1457
678
|
this.prepareCache = prepareCache;
|
|
1458
679
|
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
@@ -1463,20 +684,16 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1463
684
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1464
685
|
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
1465
686
|
});
|
|
1466
|
-
|
|
1467
|
-
if (didRewrite) {
|
|
1468
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
1469
|
-
}
|
|
1470
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
687
|
+
this.logger.logQuery(this.queryString, params);
|
|
1471
688
|
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
1472
689
|
if (fields) {
|
|
1473
|
-
const { rows: rows2 } = await executeArraysOnClient(this.client,
|
|
690
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params, { prepareCache: this.prepareCache });
|
|
1474
691
|
if (rows2.length === 0) {
|
|
1475
692
|
return [];
|
|
1476
693
|
}
|
|
1477
694
|
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
1478
695
|
}
|
|
1479
|
-
const rows = await executeOnClient(this.client,
|
|
696
|
+
const rows = await executeOnClient(this.client, this.queryString, params, {
|
|
1480
697
|
prepareCache: this.prepareCache
|
|
1481
698
|
});
|
|
1482
699
|
return rows;
|
|
@@ -1496,7 +713,6 @@ class DuckDBSession extends PgSession {
|
|
|
1496
713
|
static [entityKind] = "DuckDBSession";
|
|
1497
714
|
dialect;
|
|
1498
715
|
logger;
|
|
1499
|
-
rewriteArraysMode;
|
|
1500
716
|
rejectStringArrayLiterals;
|
|
1501
717
|
prepareCache;
|
|
1502
718
|
hasWarnedArrayLiteral = false;
|
|
@@ -1508,17 +724,15 @@ class DuckDBSession extends PgSession {
|
|
|
1508
724
|
this.options = options;
|
|
1509
725
|
this.dialect = dialect;
|
|
1510
726
|
this.logger = options.logger ?? new NoopLogger;
|
|
1511
|
-
this.rewriteArraysMode = options.rewriteArrays ?? "auto";
|
|
1512
727
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
1513
728
|
this.prepareCache = options.prepareCache;
|
|
1514
729
|
this.options = {
|
|
1515
730
|
...options,
|
|
1516
|
-
rewriteArrays: this.rewriteArraysMode,
|
|
1517
731
|
prepareCache: this.prepareCache
|
|
1518
732
|
};
|
|
1519
733
|
}
|
|
1520
734
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
1521
|
-
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.
|
|
735
|
+
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);
|
|
1522
736
|
}
|
|
1523
737
|
execute(query) {
|
|
1524
738
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1578,12 +792,8 @@ query: ${query}`, []);
|
|
|
1578
792
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1579
793
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1580
794
|
});
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1584
|
-
}
|
|
1585
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1586
|
-
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
795
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
796
|
+
return executeInBatches(this.client, builtQuery.sql, params, options);
|
|
1587
797
|
}
|
|
1588
798
|
executeBatchesRaw(query, options = {}) {
|
|
1589
799
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1593,12 +803,8 @@ query: ${query}`, []);
|
|
|
1593
803
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1594
804
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1595
805
|
});
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1599
|
-
}
|
|
1600
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1601
|
-
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
806
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
807
|
+
return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
|
|
1602
808
|
}
|
|
1603
809
|
async executeArrow(query) {
|
|
1604
810
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1608,12 +814,8 @@ query: ${query}`, []);
|
|
|
1608
814
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1609
815
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1610
816
|
});
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1614
|
-
}
|
|
1615
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1616
|
-
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
817
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
818
|
+
return executeArrowOnClient(this.client, builtQuery.sql, params);
|
|
1617
819
|
}
|
|
1618
820
|
markRollbackOnly() {
|
|
1619
821
|
this.rollbackOnly = true;
|
|
@@ -1715,6 +917,524 @@ import {
|
|
|
1715
917
|
import {
|
|
1716
918
|
sql as sql2
|
|
1717
919
|
} from "drizzle-orm";
|
|
920
|
+
|
|
921
|
+
// src/sql/ast-transformer.ts
|
|
922
|
+
import nodeSqlParser from "node-sql-parser";
|
|
923
|
+
|
|
924
|
+
// src/sql/visitors/array-operators.ts
|
|
925
|
+
var OPERATOR_MAP = {
|
|
926
|
+
"@>": { fn: "array_has_all" },
|
|
927
|
+
"<@": { fn: "array_has_all", swap: true },
|
|
928
|
+
"&&": { fn: "array_has_any" }
|
|
929
|
+
};
|
|
930
|
+
function walkExpression(expr, parent, key) {
|
|
931
|
+
if (!expr || typeof expr !== "object")
|
|
932
|
+
return false;
|
|
933
|
+
let transformed = false;
|
|
934
|
+
const exprObj = expr;
|
|
935
|
+
if ("type" in expr && exprObj.type === "binary_expr") {
|
|
936
|
+
const binary = expr;
|
|
937
|
+
const mapping = OPERATOR_MAP[binary.operator];
|
|
938
|
+
if (mapping) {
|
|
939
|
+
const fnExpr = {
|
|
940
|
+
type: "function",
|
|
941
|
+
name: { name: [{ type: "default", value: mapping.fn }] },
|
|
942
|
+
args: {
|
|
943
|
+
type: "expr_list",
|
|
944
|
+
value: mapping.swap ? [binary.right, binary.left] : [binary.left, binary.right]
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
if (parent && key) {
|
|
948
|
+
parent[key] = fnExpr;
|
|
949
|
+
}
|
|
950
|
+
transformed = true;
|
|
951
|
+
} else {
|
|
952
|
+
transformed = walkExpression(binary.left, binary, "left") || transformed;
|
|
953
|
+
transformed = walkExpression(binary.right, binary, "right") || transformed;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if ("type" in expr && exprObj.type === "unary_expr") {
|
|
957
|
+
if ("expr" in exprObj) {
|
|
958
|
+
transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if ("type" in expr && exprObj.type === "case") {
|
|
962
|
+
if ("expr" in exprObj && exprObj.expr) {
|
|
963
|
+
transformed = walkExpression(exprObj.expr, exprObj, "expr") || transformed;
|
|
964
|
+
}
|
|
965
|
+
if ("args" in exprObj && Array.isArray(exprObj.args)) {
|
|
966
|
+
for (let i = 0;i < exprObj.args.length; i++) {
|
|
967
|
+
const whenClause = exprObj.args[i];
|
|
968
|
+
if (whenClause.cond) {
|
|
969
|
+
transformed = walkExpression(whenClause.cond, whenClause, "cond") || transformed;
|
|
970
|
+
}
|
|
971
|
+
if (whenClause.result) {
|
|
972
|
+
transformed = walkExpression(whenClause.result, whenClause, "result") || transformed;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if ("args" in expr && exprObj.args) {
|
|
978
|
+
const args = exprObj.args;
|
|
979
|
+
if ("value" in args && Array.isArray(args.value)) {
|
|
980
|
+
for (let i = 0;i < args.value.length; i++) {
|
|
981
|
+
transformed = walkExpression(args.value[i], args.value, String(i)) || transformed;
|
|
982
|
+
}
|
|
983
|
+
} else if ("expr" in args) {
|
|
984
|
+
transformed = walkExpression(args.expr, args, "expr") || transformed;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if ("ast" in exprObj && exprObj.ast) {
|
|
988
|
+
const subAst = exprObj.ast;
|
|
989
|
+
if (subAst.type === "select") {
|
|
990
|
+
transformed = walkSelectImpl(subAst) || transformed;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if ("type" in expr && exprObj.type === "expr_list") {
|
|
994
|
+
if ("value" in exprObj && Array.isArray(exprObj.value)) {
|
|
995
|
+
for (let i = 0;i < exprObj.value.length; i++) {
|
|
996
|
+
transformed = walkExpression(exprObj.value[i], exprObj.value, String(i)) || transformed;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return transformed;
|
|
1001
|
+
}
|
|
1002
|
+
function walkFrom(from) {
|
|
1003
|
+
if (!from || !Array.isArray(from))
|
|
1004
|
+
return false;
|
|
1005
|
+
let transformed = false;
|
|
1006
|
+
for (const f of from) {
|
|
1007
|
+
if ("join" in f) {
|
|
1008
|
+
const join = f;
|
|
1009
|
+
transformed = walkExpression(join.on, join, "on") || transformed;
|
|
1010
|
+
}
|
|
1011
|
+
if ("expr" in f && f.expr && "ast" in f.expr) {
|
|
1012
|
+
transformed = walkSelectImpl(f.expr.ast) || transformed;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return transformed;
|
|
1016
|
+
}
|
|
1017
|
+
function walkSelectImpl(select) {
|
|
1018
|
+
let transformed = false;
|
|
1019
|
+
if (select.with) {
|
|
1020
|
+
for (const cte of select.with) {
|
|
1021
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1022
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1023
|
+
transformed = walkSelectImpl(cteSelect) || transformed;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (Array.isArray(select.from)) {
|
|
1028
|
+
transformed = walkFrom(select.from) || transformed;
|
|
1029
|
+
}
|
|
1030
|
+
transformed = walkExpression(select.where, select, "where") || transformed;
|
|
1031
|
+
if (select.having) {
|
|
1032
|
+
if (Array.isArray(select.having)) {
|
|
1033
|
+
for (let i = 0;i < select.having.length; i++) {
|
|
1034
|
+
transformed = walkExpression(select.having[i], select.having, String(i)) || transformed;
|
|
1035
|
+
}
|
|
1036
|
+
} else {
|
|
1037
|
+
transformed = walkExpression(select.having, select, "having") || transformed;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (Array.isArray(select.columns)) {
|
|
1041
|
+
for (const col of select.columns) {
|
|
1042
|
+
if ("expr" in col) {
|
|
1043
|
+
transformed = walkExpression(col.expr, col, "expr") || transformed;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (select._next) {
|
|
1048
|
+
transformed = walkSelectImpl(select._next) || transformed;
|
|
1049
|
+
}
|
|
1050
|
+
return transformed;
|
|
1051
|
+
}
|
|
1052
|
+
function transformArrayOperators(ast) {
|
|
1053
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1054
|
+
let transformed = false;
|
|
1055
|
+
for (const stmt of statements) {
|
|
1056
|
+
if (stmt.type === "select") {
|
|
1057
|
+
transformed = walkSelectImpl(stmt) || transformed;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return transformed;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/sql/visitors/column-qualifier.ts
|
|
1064
|
+
function getTableSource(from) {
|
|
1065
|
+
if ("table" in from && from.table) {
|
|
1066
|
+
return {
|
|
1067
|
+
name: from.table,
|
|
1068
|
+
alias: from.as ?? null,
|
|
1069
|
+
schema: "db" in from ? from.db ?? null : null
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if ("expr" in from && from.as) {
|
|
1073
|
+
return {
|
|
1074
|
+
name: from.as,
|
|
1075
|
+
alias: from.as,
|
|
1076
|
+
schema: null
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
function getQualifier(source) {
|
|
1082
|
+
return {
|
|
1083
|
+
table: source.alias ?? source.name,
|
|
1084
|
+
schema: source.schema
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function isUnqualifiedColumnRef(expr) {
|
|
1088
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && (!("table" in expr) || !expr.table);
|
|
1089
|
+
}
|
|
1090
|
+
function isQualifiedColumnRef(expr) {
|
|
1091
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && "table" in expr && !!expr.table;
|
|
1092
|
+
}
|
|
1093
|
+
function getColumnName(col) {
|
|
1094
|
+
if (typeof col.column === "string") {
|
|
1095
|
+
return col.column;
|
|
1096
|
+
}
|
|
1097
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1098
|
+
return String(col.column.expr.value);
|
|
1099
|
+
}
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
function applyQualifier(col, qualifier) {
|
|
1103
|
+
col.table = qualifier.table;
|
|
1104
|
+
if (!("schema" in col) || !col.schema) {
|
|
1105
|
+
col.schema = qualifier.schema;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function unwrapColumnRef(expr) {
|
|
1109
|
+
if (!expr || typeof expr !== "object")
|
|
1110
|
+
return null;
|
|
1111
|
+
if ("type" in expr && expr.type === "column_ref") {
|
|
1112
|
+
return expr;
|
|
1113
|
+
}
|
|
1114
|
+
if ("expr" in expr && expr.expr) {
|
|
1115
|
+
return unwrapColumnRef(expr.expr);
|
|
1116
|
+
}
|
|
1117
|
+
if ("ast" in expr && expr.ast && typeof expr.ast === "object") {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
if ("args" in expr && expr.args) {
|
|
1121
|
+
const args = expr.args;
|
|
1122
|
+
if (args.expr) {
|
|
1123
|
+
return unwrapColumnRef(args.expr);
|
|
1124
|
+
}
|
|
1125
|
+
if (args.value && args.value.length === 1) {
|
|
1126
|
+
return unwrapColumnRef(args.value[0]);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
function isBinaryExpr(expr) {
|
|
1132
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1133
|
+
}
|
|
1134
|
+
function walkOnClause(expr, leftQualifier, rightQualifier, ambiguousColumns) {
|
|
1135
|
+
if (!expr || typeof expr !== "object")
|
|
1136
|
+
return false;
|
|
1137
|
+
let transformed = false;
|
|
1138
|
+
if (isBinaryExpr(expr)) {
|
|
1139
|
+
const left = expr.left;
|
|
1140
|
+
const right = expr.right;
|
|
1141
|
+
const leftCol = unwrapColumnRef(left);
|
|
1142
|
+
const rightCol = unwrapColumnRef(right);
|
|
1143
|
+
const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
|
|
1144
|
+
const rightUnqualified = rightCol ? isUnqualifiedColumnRef(rightCol) : false;
|
|
1145
|
+
const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
|
|
1146
|
+
const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
|
|
1147
|
+
const leftColName = leftCol ? getColumnName(leftCol) : null;
|
|
1148
|
+
const rightColName = rightCol ? getColumnName(rightCol) : null;
|
|
1149
|
+
if (expr.operator === "=" && leftColName && rightColName && leftColName === rightColName) {
|
|
1150
|
+
if (leftUnqualified && rightUnqualified) {
|
|
1151
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1152
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1153
|
+
ambiguousColumns.add(leftColName);
|
|
1154
|
+
transformed = true;
|
|
1155
|
+
} else if (leftQualified && rightUnqualified) {
|
|
1156
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1157
|
+
ambiguousColumns.add(rightColName);
|
|
1158
|
+
transformed = true;
|
|
1159
|
+
} else if (leftUnqualified && rightQualified) {
|
|
1160
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1161
|
+
ambiguousColumns.add(leftColName);
|
|
1162
|
+
transformed = true;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1166
|
+
transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1167
|
+
}
|
|
1168
|
+
return transformed;
|
|
1169
|
+
}
|
|
1170
|
+
function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns) {
|
|
1171
|
+
if (!expr || typeof expr !== "object")
|
|
1172
|
+
return false;
|
|
1173
|
+
let transformed = false;
|
|
1174
|
+
if (isUnqualifiedColumnRef(expr)) {
|
|
1175
|
+
const colName = getColumnName(expr);
|
|
1176
|
+
if (colName && ambiguousColumns.has(colName)) {
|
|
1177
|
+
applyQualifier(expr, defaultQualifier);
|
|
1178
|
+
transformed = true;
|
|
1179
|
+
}
|
|
1180
|
+
return transformed;
|
|
1181
|
+
}
|
|
1182
|
+
if (isBinaryExpr(expr)) {
|
|
1183
|
+
const binary = expr;
|
|
1184
|
+
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1185
|
+
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
1186
|
+
return transformed;
|
|
1187
|
+
}
|
|
1188
|
+
if ("args" in expr && expr.args) {
|
|
1189
|
+
const args = expr.args;
|
|
1190
|
+
if (args.value && Array.isArray(args.value)) {
|
|
1191
|
+
for (const arg of args.value) {
|
|
1192
|
+
transformed = qualifyAmbiguousInExpression(arg, defaultQualifier, ambiguousColumns) || transformed;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (args.expr) {
|
|
1196
|
+
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if ("over" in expr && expr.over && typeof expr.over === "object") {
|
|
1200
|
+
const over = expr.over;
|
|
1201
|
+
if (Array.isArray(over.partition)) {
|
|
1202
|
+
for (const part of over.partition) {
|
|
1203
|
+
transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (Array.isArray(over.orderby)) {
|
|
1207
|
+
for (const order of over.orderby) {
|
|
1208
|
+
transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return transformed;
|
|
1213
|
+
}
|
|
1214
|
+
function hasUnqualifiedColumns(expr) {
|
|
1215
|
+
if (!expr || typeof expr !== "object")
|
|
1216
|
+
return false;
|
|
1217
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1218
|
+
const left = expr.left;
|
|
1219
|
+
const right = expr.right;
|
|
1220
|
+
const leftCol = unwrapColumnRef(left);
|
|
1221
|
+
const rightCol = unwrapColumnRef(right);
|
|
1222
|
+
if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
|
|
1226
|
+
return true;
|
|
1227
|
+
if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1230
|
+
if ("args" in expr && expr.args) {
|
|
1231
|
+
const args = expr.args;
|
|
1232
|
+
if (args.expr && isUnqualifiedColumnRef(args.expr))
|
|
1233
|
+
return true;
|
|
1234
|
+
if (args.value) {
|
|
1235
|
+
for (const arg of args.value) {
|
|
1236
|
+
if (isUnqualifiedColumnRef(arg))
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
function walkSelect(select) {
|
|
1244
|
+
let transformed = false;
|
|
1245
|
+
const ambiguousColumns = new Set;
|
|
1246
|
+
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1247
|
+
const firstSource = getTableSource(select.from[0]);
|
|
1248
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
|
|
1249
|
+
let prevSource = firstSource;
|
|
1250
|
+
let hasAnyUnqualified = false;
|
|
1251
|
+
for (const from of select.from) {
|
|
1252
|
+
if ("join" in from) {
|
|
1253
|
+
const join = from;
|
|
1254
|
+
if (join.on && hasUnqualifiedColumns(join.on)) {
|
|
1255
|
+
hasAnyUnqualified = true;
|
|
1256
|
+
break;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (!hasAnyUnqualified) {
|
|
1261
|
+
for (const from of select.from) {
|
|
1262
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1263
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
for (const from of select.from) {
|
|
1268
|
+
if ("join" in from) {
|
|
1269
|
+
const join = from;
|
|
1270
|
+
const currentSource = getTableSource(join);
|
|
1271
|
+
if (join.on && prevSource && currentSource) {
|
|
1272
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1273
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1274
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1275
|
+
}
|
|
1276
|
+
if (join.using && prevSource && currentSource) {
|
|
1277
|
+
for (const usingCol of join.using) {
|
|
1278
|
+
if (typeof usingCol === "string") {
|
|
1279
|
+
ambiguousColumns.add(usingCol);
|
|
1280
|
+
} else if ("value" in usingCol) {
|
|
1281
|
+
ambiguousColumns.add(String(usingCol.value));
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
prevSource = currentSource;
|
|
1286
|
+
} else {
|
|
1287
|
+
const source = getTableSource(from);
|
|
1288
|
+
if (source) {
|
|
1289
|
+
prevSource = source;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1293
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1297
|
+
if (Array.isArray(select.columns)) {
|
|
1298
|
+
for (const col of select.columns) {
|
|
1299
|
+
if ("expr" in col) {
|
|
1300
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1305
|
+
if (Array.isArray(select.orderby)) {
|
|
1306
|
+
for (const order of select.orderby) {
|
|
1307
|
+
if (order.expr) {
|
|
1308
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (select.with) {
|
|
1316
|
+
for (const cte of select.with) {
|
|
1317
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1318
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1319
|
+
transformed = walkSelect(cteSelect) || transformed;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (select._next) {
|
|
1324
|
+
transformed = walkSelect(select._next) || transformed;
|
|
1325
|
+
}
|
|
1326
|
+
return transformed;
|
|
1327
|
+
}
|
|
1328
|
+
function qualifyJoinColumns(ast) {
|
|
1329
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1330
|
+
let transformed = false;
|
|
1331
|
+
for (const stmt of statements) {
|
|
1332
|
+
if (stmt.type === "select") {
|
|
1333
|
+
transformed = walkSelect(stmt) || transformed;
|
|
1334
|
+
} else if (stmt.type === "insert") {
|
|
1335
|
+
const insert = stmt;
|
|
1336
|
+
if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
|
|
1337
|
+
transformed = walkSelect(insert.values) || transformed;
|
|
1338
|
+
}
|
|
1339
|
+
} else if (stmt.type === "update") {
|
|
1340
|
+
const update = stmt;
|
|
1341
|
+
const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
|
|
1342
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1343
|
+
const fromSources = update.from ?? [];
|
|
1344
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1345
|
+
if (update.where && defaultQualifier && firstFrom) {
|
|
1346
|
+
const ambiguous = new Set;
|
|
1347
|
+
transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1348
|
+
transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
|
|
1349
|
+
}
|
|
1350
|
+
if (Array.isArray(update.returning) && defaultQualifier) {
|
|
1351
|
+
for (const ret of update.returning) {
|
|
1352
|
+
transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
} else if (stmt.type === "delete") {
|
|
1356
|
+
const del = stmt;
|
|
1357
|
+
const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
|
|
1358
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1359
|
+
const fromSources = del.from ?? [];
|
|
1360
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1361
|
+
if (del.where && defaultQualifier && firstFrom) {
|
|
1362
|
+
const ambiguous = new Set;
|
|
1363
|
+
transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1364
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
|
|
1365
|
+
} else if (del.where && defaultQualifier) {
|
|
1366
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return transformed;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// src/sql/ast-transformer.ts
|
|
1374
|
+
var { Parser } = nodeSqlParser;
|
|
1375
|
+
var parser = new Parser;
|
|
1376
|
+
var CACHE_SIZE = 500;
|
|
1377
|
+
var transformCache = new Map;
|
|
1378
|
+
function getCachedOrTransform(query, transform) {
|
|
1379
|
+
const cached = transformCache.get(query);
|
|
1380
|
+
if (cached) {
|
|
1381
|
+
transformCache.delete(query);
|
|
1382
|
+
transformCache.set(query, cached);
|
|
1383
|
+
return cached;
|
|
1384
|
+
}
|
|
1385
|
+
const result = transform();
|
|
1386
|
+
if (transformCache.size >= CACHE_SIZE) {
|
|
1387
|
+
const oldestKey = transformCache.keys().next().value;
|
|
1388
|
+
if (oldestKey) {
|
|
1389
|
+
transformCache.delete(oldestKey);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
transformCache.set(query, result);
|
|
1393
|
+
return result;
|
|
1394
|
+
}
|
|
1395
|
+
var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
|
|
1396
|
+
function hasJoin(query) {
|
|
1397
|
+
return /\bjoin\b/i.test(query);
|
|
1398
|
+
}
|
|
1399
|
+
function debugLog(message, payload) {
|
|
1400
|
+
if (process?.env?.[DEBUG_ENV]) {
|
|
1401
|
+
console.debug("[duckdb-ast]", message, payload ?? "");
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function transformSQL(query) {
|
|
1405
|
+
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1406
|
+
const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
|
|
1407
|
+
if (!needsArrayTransform && !needsJoinTransform) {
|
|
1408
|
+
return { sql: query, transformed: false };
|
|
1409
|
+
}
|
|
1410
|
+
return getCachedOrTransform(query, () => {
|
|
1411
|
+
try {
|
|
1412
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1413
|
+
let transformed = false;
|
|
1414
|
+
if (needsArrayTransform) {
|
|
1415
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1416
|
+
}
|
|
1417
|
+
if (needsJoinTransform) {
|
|
1418
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1419
|
+
}
|
|
1420
|
+
if (!transformed) {
|
|
1421
|
+
debugLog("AST parsed but no transformation applied", {
|
|
1422
|
+
join: needsJoinTransform
|
|
1423
|
+
});
|
|
1424
|
+
return { sql: query, transformed: false };
|
|
1425
|
+
}
|
|
1426
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1427
|
+
return { sql: transformedSql, transformed: true };
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
debugLog("AST transform failed; returning original SQL", {
|
|
1430
|
+
error: err.message
|
|
1431
|
+
});
|
|
1432
|
+
return { sql: query, transformed: false };
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// src/dialect.ts
|
|
1718
1438
|
class DuckDBDialect extends PgDialect {
|
|
1719
1439
|
static [entityKind2] = "DuckDBPgDialect";
|
|
1720
1440
|
hasPgJsonColumn = false;
|
|
@@ -1791,6 +1511,14 @@ class DuckDBDialect extends PgDialect {
|
|
|
1791
1511
|
return "none";
|
|
1792
1512
|
}
|
|
1793
1513
|
}
|
|
1514
|
+
sqlToQuery(sqlObj, invokeSource) {
|
|
1515
|
+
const result = super.sqlToQuery(sqlObj, invokeSource);
|
|
1516
|
+
const transformed = transformSQL(result.sql);
|
|
1517
|
+
return {
|
|
1518
|
+
...result,
|
|
1519
|
+
sql: transformed.sql
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1794
1522
|
}
|
|
1795
1523
|
|
|
1796
1524
|
// src/select-builder.ts
|
|
@@ -1801,10 +1529,10 @@ import {
|
|
|
1801
1529
|
} from "drizzle-orm/pg-core/query-builders";
|
|
1802
1530
|
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
1803
1531
|
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
1804
|
-
import { SQL as
|
|
1532
|
+
import { SQL as SQL5 } from "drizzle-orm/sql/sql";
|
|
1805
1533
|
|
|
1806
1534
|
// src/sql/selection.ts
|
|
1807
|
-
import { Column as Column2, SQL as
|
|
1535
|
+
import { Column as Column2, SQL as SQL4, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
1808
1536
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
1809
1537
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
1810
1538
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -1814,16 +1542,16 @@ function mapEntries(obj, prefix, fullJoin = false) {
|
|
|
1814
1542
|
sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
|
|
1815
1543
|
];
|
|
1816
1544
|
}
|
|
1817
|
-
if (fullJoin && is3(value,
|
|
1545
|
+
if (fullJoin && is3(value, SQL4)) {
|
|
1818
1546
|
const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
|
|
1819
1547
|
const tableName = col?.table && getTableName2(col?.table);
|
|
1820
1548
|
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
1821
1549
|
}
|
|
1822
|
-
if (is3(value,
|
|
1823
|
-
const aliased = is3(value,
|
|
1550
|
+
if (is3(value, SQL4) || is3(value, Column2)) {
|
|
1551
|
+
const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
|
|
1824
1552
|
return [key, aliased.as(qualified)];
|
|
1825
1553
|
}
|
|
1826
|
-
if (is3(value,
|
|
1554
|
+
if (is3(value, SQL4.Aliased)) {
|
|
1827
1555
|
return [key, value];
|
|
1828
1556
|
}
|
|
1829
1557
|
if (typeof value === "object" && value !== null) {
|
|
@@ -1871,7 +1599,7 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
1871
1599
|
]));
|
|
1872
1600
|
} else if (is4(src, PgViewBase)) {
|
|
1873
1601
|
fields = src[ViewBaseConfig]?.selectedFields;
|
|
1874
|
-
} else if (is4(src,
|
|
1602
|
+
} else if (is4(src, SQL5)) {
|
|
1875
1603
|
fields = {};
|
|
1876
1604
|
} else {
|
|
1877
1605
|
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
@@ -2062,16 +1790,6 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
2062
1790
|
}
|
|
2063
1791
|
|
|
2064
1792
|
// src/options.ts
|
|
2065
|
-
var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
|
|
2066
|
-
function resolveRewriteArraysOption(value) {
|
|
2067
|
-
if (value === undefined)
|
|
2068
|
-
return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
2069
|
-
if (value === true)
|
|
2070
|
-
return "auto";
|
|
2071
|
-
if (value === false)
|
|
2072
|
-
return "never";
|
|
2073
|
-
return value;
|
|
2074
|
-
}
|
|
2075
1793
|
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
2076
1794
|
function resolvePrepareCacheOption(option) {
|
|
2077
1795
|
if (!option)
|
|
@@ -2101,7 +1819,6 @@ class DuckDBDriver {
|
|
|
2101
1819
|
createSession(schema) {
|
|
2102
1820
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
2103
1821
|
logger: this.options.logger,
|
|
2104
|
-
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
2105
1822
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
2106
1823
|
prepareCache: this.options.prepareCache
|
|
2107
1824
|
});
|
|
@@ -2116,7 +1833,6 @@ function isConfigObject(data) {
|
|
|
2116
1833
|
}
|
|
2117
1834
|
function createFromClient(client, config = {}, instance) {
|
|
2118
1835
|
const dialect = new DuckDBDialect;
|
|
2119
|
-
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
2120
1836
|
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
2121
1837
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
2122
1838
|
let schema;
|
|
@@ -2130,7 +1846,6 @@ function createFromClient(client, config = {}, instance) {
|
|
|
2130
1846
|
}
|
|
2131
1847
|
const driver = new DuckDBDriver(client, dialect, {
|
|
2132
1848
|
logger,
|
|
2133
|
-
rewriteArrays: rewriteArraysMode,
|
|
2134
1849
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
2135
1850
|
prepareCache
|
|
2136
1851
|
});
|
|
@@ -3090,7 +2805,7 @@ function renderImports(imports, importBasePath) {
|
|
|
3090
2805
|
// src/olap.ts
|
|
3091
2806
|
import { is as is5 } from "drizzle-orm/entity";
|
|
3092
2807
|
import { sql as sql6 } from "drizzle-orm";
|
|
3093
|
-
import { SQL as
|
|
2808
|
+
import { SQL as SQL6 } from "drizzle-orm/sql/sql";
|
|
3094
2809
|
import { Column as Column3, getTableName as getTableName3 } from "drizzle-orm";
|
|
3095
2810
|
var countN = (expr = sql6`*`) => sql6`count(${expr})`.mapWith(Number);
|
|
3096
2811
|
var sumN = (expr) => sql6`sum(${expr})`.mapWith(Number);
|
|
@@ -3125,7 +2840,7 @@ var denseRank = (options) => sql6`dense_rank() ${overClause(options)}`.mapWith(N
|
|
|
3125
2840
|
var lag = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lag(${expr}, ${offset}) ${overClause(options)}`;
|
|
3126
2841
|
var lead = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lead(${expr}, ${offset}) ${overClause(options)}`;
|
|
3127
2842
|
function keyAlias(key, fallback) {
|
|
3128
|
-
if (is5(key,
|
|
2843
|
+
if (is5(key, SQL6.Aliased)) {
|
|
3129
2844
|
return key.fieldAlias ?? fallback;
|
|
3130
2845
|
}
|
|
3131
2846
|
if (is5(key, Column3)) {
|
|
@@ -3196,6 +2911,17 @@ class OlapBuilder {
|
|
|
3196
2911
|
}
|
|
3197
2912
|
}
|
|
3198
2913
|
var olap = (db) => new OlapBuilder(db);
|
|
2914
|
+
// src/operators.ts
|
|
2915
|
+
import { sql as sql7 } from "drizzle-orm";
|
|
2916
|
+
function arrayHasAll(column, values) {
|
|
2917
|
+
return sql7`array_has_all(${column}, ${values})`;
|
|
2918
|
+
}
|
|
2919
|
+
function arrayHasAny(column, values) {
|
|
2920
|
+
return sql7`array_has_any(${column}, ${values})`;
|
|
2921
|
+
}
|
|
2922
|
+
function arrayContainedBy(column, values) {
|
|
2923
|
+
return sql7`array_has_all(${values}, ${column})`;
|
|
2924
|
+
}
|
|
3199
2925
|
export {
|
|
3200
2926
|
wrapperToNodeApiValue,
|
|
3201
2927
|
wrapTimestamp,
|
|
@@ -3210,7 +2936,6 @@ export {
|
|
|
3210
2936
|
sumDistinctN,
|
|
3211
2937
|
splitTopLevel,
|
|
3212
2938
|
rowNumber,
|
|
3213
|
-
resolveRewriteArraysOption,
|
|
3214
2939
|
resolvePrepareCacheOption,
|
|
3215
2940
|
resolvePoolSize,
|
|
3216
2941
|
rank,
|
|
@@ -3257,6 +2982,9 @@ export {
|
|
|
3257
2982
|
buildListLiteral,
|
|
3258
2983
|
buildDefault,
|
|
3259
2984
|
avgN,
|
|
2985
|
+
arrayHasAny,
|
|
2986
|
+
arrayHasAll,
|
|
2987
|
+
arrayContainedBy,
|
|
3260
2988
|
anyValue,
|
|
3261
2989
|
POOL_PRESETS,
|
|
3262
2990
|
OlapBuilder,
|