@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { DuckDBInstance as DuckDBInstance3 } from "@duckdb/node-api";
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import
|
|
7
|
+
import process2 from "node:process";
|
|
8
8
|
|
|
9
9
|
// src/driver.ts
|
|
10
10
|
import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
|
|
@@ -23,765 +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) {
|
|
192
|
-
if (original[pos] === '"') {
|
|
193
|
-
if (original[pos + 1] === '"') {
|
|
194
|
-
pos += 2;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
pos++;
|
|
200
|
-
}
|
|
201
|
-
if (pos >= original.length) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
name: original.slice(start + 1, pos),
|
|
206
|
-
end: pos + 1
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function findMainFromClause(scrubbed) {
|
|
210
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
211
|
-
let searchStart = 0;
|
|
212
|
-
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
213
|
-
if (withMatch) {
|
|
214
|
-
let depth = 0;
|
|
215
|
-
let pos = withMatch.index + withMatch[0].length;
|
|
216
|
-
while (pos < scrubbed.length) {
|
|
217
|
-
const char = scrubbed[pos];
|
|
218
|
-
if (char === "(") {
|
|
219
|
-
depth++;
|
|
220
|
-
} else if (char === ")") {
|
|
221
|
-
depth--;
|
|
222
|
-
} else if (depth === 0) {
|
|
223
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
224
|
-
if (/^\s*select\s+/i.test(remaining)) {
|
|
225
|
-
searchStart = pos;
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
pos++;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const fromPattern = /\bfrom\s+/gi;
|
|
233
|
-
fromPattern.lastIndex = searchStart;
|
|
234
|
-
const fromMatch = fromPattern.exec(lowerScrubbed);
|
|
235
|
-
return fromMatch ? fromMatch.index + fromMatch[0].length : -1;
|
|
236
|
-
}
|
|
237
|
-
function parseTableSources(original, scrubbed) {
|
|
238
|
-
const sources = [];
|
|
239
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
240
|
-
const fromPos = findMainFromClause(scrubbed);
|
|
241
|
-
if (fromPos < 0) {
|
|
242
|
-
return sources;
|
|
243
|
-
}
|
|
244
|
-
const fromTable = parseTableRef(original, scrubbed, fromPos);
|
|
245
|
-
if (fromTable) {
|
|
246
|
-
sources.push(fromTable);
|
|
247
|
-
}
|
|
248
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+/gi;
|
|
249
|
-
joinPattern.lastIndex = fromPos;
|
|
250
|
-
let joinMatch;
|
|
251
|
-
while ((joinMatch = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
252
|
-
const tableStart = joinMatch.index + joinMatch[0].length;
|
|
253
|
-
const joinTable = parseTableRef(original, scrubbed, tableStart);
|
|
254
|
-
if (joinTable) {
|
|
255
|
-
sources.push(joinTable);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return sources;
|
|
259
|
-
}
|
|
260
|
-
function parseTableRef(original, scrubbed, start) {
|
|
261
|
-
let pos = start;
|
|
262
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
263
|
-
pos++;
|
|
264
|
-
}
|
|
265
|
-
if (scrubbed[pos] === "(") {
|
|
266
|
-
const nameStart2 = pos;
|
|
267
|
-
let depth = 1;
|
|
268
|
-
pos++;
|
|
269
|
-
while (pos < scrubbed.length && depth > 0) {
|
|
270
|
-
if (scrubbed[pos] === "(")
|
|
271
|
-
depth++;
|
|
272
|
-
else if (scrubbed[pos] === ")")
|
|
273
|
-
depth--;
|
|
274
|
-
pos++;
|
|
275
|
-
}
|
|
276
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
277
|
-
pos++;
|
|
278
|
-
}
|
|
279
|
-
const afterSubquery = scrubbed.slice(pos).toLowerCase();
|
|
280
|
-
if (afterSubquery.startsWith("as ")) {
|
|
281
|
-
pos += 3;
|
|
282
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
283
|
-
pos++;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (original[pos] === '"') {
|
|
287
|
-
const aliasIdent = extractQuotedIdentifier(original, pos);
|
|
288
|
-
if (aliasIdent) {
|
|
289
|
-
return {
|
|
290
|
-
name: aliasIdent.name,
|
|
291
|
-
alias: aliasIdent.name,
|
|
292
|
-
position: nameStart2
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
if (original[pos] !== '"') {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
const nameStart = pos;
|
|
302
|
-
const firstIdent = extractQuotedIdentifier(original, pos);
|
|
303
|
-
if (!firstIdent) {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
let name = firstIdent.name;
|
|
307
|
-
pos = firstIdent.end;
|
|
308
|
-
let afterName = pos;
|
|
309
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
310
|
-
afterName++;
|
|
311
|
-
}
|
|
312
|
-
if (scrubbed[afterName] === ".") {
|
|
313
|
-
afterName++;
|
|
314
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
315
|
-
afterName++;
|
|
316
|
-
}
|
|
317
|
-
if (original[afterName] === '"') {
|
|
318
|
-
const tableIdent = extractQuotedIdentifier(original, afterName);
|
|
319
|
-
if (tableIdent) {
|
|
320
|
-
name = tableIdent.name;
|
|
321
|
-
pos = tableIdent.end;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
let alias;
|
|
326
|
-
let aliasPos = pos;
|
|
327
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
328
|
-
aliasPos++;
|
|
329
|
-
}
|
|
330
|
-
const afterTable = scrubbed.slice(aliasPos).toLowerCase();
|
|
331
|
-
if (afterTable.startsWith("as ")) {
|
|
332
|
-
aliasPos += 3;
|
|
333
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
334
|
-
aliasPos++;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const afterAlias = scrubbed.slice(aliasPos).toLowerCase();
|
|
338
|
-
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 ")) {
|
|
339
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
340
|
-
if (aliasIdent) {
|
|
341
|
-
alias = aliasIdent.name;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return {
|
|
345
|
-
name,
|
|
346
|
-
alias,
|
|
347
|
-
position: nameStart
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function findJoinClauses(original, scrubbed, sources, fromPos) {
|
|
351
|
-
const clauses = [];
|
|
352
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
353
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
|
|
354
|
-
joinPattern.lastIndex = fromPos;
|
|
355
|
-
let match;
|
|
356
|
-
let sourceIndex = 1;
|
|
357
|
-
while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
358
|
-
const joinType = (match[1] || "").trim().toLowerCase();
|
|
359
|
-
const joinKeywordEnd = match.index + (match[1] || "").length + "join".length;
|
|
360
|
-
let tableStart = joinKeywordEnd;
|
|
361
|
-
while (tableStart < original.length && isWhitespace(original[tableStart])) {
|
|
362
|
-
tableStart++;
|
|
363
|
-
}
|
|
364
|
-
const tableIdent = extractQuotedIdentifier(original, tableStart);
|
|
365
|
-
if (!tableIdent)
|
|
366
|
-
continue;
|
|
367
|
-
let tableName = tableIdent.name;
|
|
368
|
-
let afterTable = tableIdent.end;
|
|
369
|
-
let checkPos = afterTable;
|
|
370
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
371
|
-
checkPos++;
|
|
372
|
-
}
|
|
373
|
-
if (scrubbed[checkPos] === ".") {
|
|
374
|
-
checkPos++;
|
|
375
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
376
|
-
checkPos++;
|
|
377
|
-
}
|
|
378
|
-
const realTableIdent = extractQuotedIdentifier(original, checkPos);
|
|
379
|
-
if (realTableIdent) {
|
|
380
|
-
tableName = realTableIdent.name;
|
|
381
|
-
afterTable = realTableIdent.end;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
let tableAlias;
|
|
385
|
-
let aliasPos = afterTable;
|
|
386
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
387
|
-
aliasPos++;
|
|
388
|
-
}
|
|
389
|
-
const afterTableStr = scrubbed.slice(aliasPos).toLowerCase();
|
|
390
|
-
if (afterTableStr.startsWith("as ")) {
|
|
391
|
-
aliasPos += 3;
|
|
392
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
393
|
-
aliasPos++;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if (original[aliasPos] === '"' && !afterTableStr.startsWith("on ")) {
|
|
397
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
398
|
-
if (aliasIdent) {
|
|
399
|
-
tableAlias = aliasIdent.name;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const onStart = match.index + match[0].length;
|
|
403
|
-
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;
|
|
404
|
-
const remaining = lowerScrubbed.slice(onStart);
|
|
405
|
-
const endMatch = endPattern.exec(remaining);
|
|
406
|
-
const onEnd = endMatch ? onStart + endMatch.index : scrubbed.length;
|
|
407
|
-
let leftSource = "";
|
|
408
|
-
if (sourceIndex > 0 && sourceIndex <= sources.length) {
|
|
409
|
-
const prev = sources[sourceIndex - 1];
|
|
410
|
-
leftSource = prev?.alias || prev?.name || "";
|
|
411
|
-
}
|
|
412
|
-
const rightSource = tableAlias || tableName;
|
|
413
|
-
clauses.push({
|
|
414
|
-
joinType,
|
|
415
|
-
tableName,
|
|
416
|
-
tableAlias,
|
|
417
|
-
onStart,
|
|
418
|
-
onEnd,
|
|
419
|
-
leftSource,
|
|
420
|
-
rightSource
|
|
421
|
-
});
|
|
422
|
-
sourceIndex++;
|
|
423
|
-
}
|
|
424
|
-
return clauses;
|
|
425
|
-
}
|
|
426
|
-
function findMainSelectClause(scrubbed) {
|
|
427
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
428
|
-
let searchStart = 0;
|
|
429
|
-
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
430
|
-
if (withMatch) {
|
|
431
|
-
let depth2 = 0;
|
|
432
|
-
let pos2 = withMatch.index + withMatch[0].length;
|
|
433
|
-
while (pos2 < scrubbed.length) {
|
|
434
|
-
const char = scrubbed[pos2];
|
|
435
|
-
if (char === "(") {
|
|
436
|
-
depth2++;
|
|
437
|
-
} else if (char === ")") {
|
|
438
|
-
depth2--;
|
|
439
|
-
} else if (depth2 === 0) {
|
|
440
|
-
const remaining = lowerScrubbed.slice(pos2);
|
|
441
|
-
if (/^\s*select\s+/i.test(remaining)) {
|
|
442
|
-
searchStart = pos2;
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
pos2++;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
const selectPattern = /\bselect\s+/gi;
|
|
450
|
-
selectPattern.lastIndex = searchStart;
|
|
451
|
-
const selectMatch = selectPattern.exec(lowerScrubbed);
|
|
452
|
-
if (!selectMatch)
|
|
453
|
-
return null;
|
|
454
|
-
const selectStart = selectMatch.index + selectMatch[0].length;
|
|
455
|
-
let depth = 0;
|
|
456
|
-
let pos = selectStart;
|
|
457
|
-
while (pos < scrubbed.length) {
|
|
458
|
-
const char = scrubbed[pos];
|
|
459
|
-
if (char === "(") {
|
|
460
|
-
depth++;
|
|
461
|
-
} else if (char === ")") {
|
|
462
|
-
depth--;
|
|
463
|
-
} else if (depth === 0) {
|
|
464
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
465
|
-
if (/^\s*from\s+/i.test(remaining)) {
|
|
466
|
-
return { start: selectStart, end: pos };
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
pos++;
|
|
470
|
-
}
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
function findWhereClause(scrubbed, fromPos) {
|
|
474
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
475
|
-
let depth = 0;
|
|
476
|
-
let pos = fromPos;
|
|
477
|
-
let whereStart = -1;
|
|
478
|
-
while (pos < scrubbed.length) {
|
|
479
|
-
const char = scrubbed[pos];
|
|
480
|
-
if (char === "(") {
|
|
481
|
-
depth++;
|
|
482
|
-
} else if (char === ")") {
|
|
483
|
-
depth--;
|
|
484
|
-
} else if (depth === 0) {
|
|
485
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
486
|
-
if (whereStart === -1 && /^\s*where\s+/i.test(remaining)) {
|
|
487
|
-
const match = /^\s*where\s+/i.exec(remaining);
|
|
488
|
-
if (match) {
|
|
489
|
-
whereStart = pos + match[0].length;
|
|
490
|
-
}
|
|
491
|
-
} else if (whereStart !== -1 && /^\s*(group\s+by|order\s+by|limit|having|union|intersect|except|$)/i.test(remaining)) {
|
|
492
|
-
return { start: whereStart, end: pos };
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
pos++;
|
|
496
|
-
}
|
|
497
|
-
if (whereStart !== -1) {
|
|
498
|
-
return { start: whereStart, end: scrubbed.length };
|
|
499
|
-
}
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
502
|
-
function findOrderByClause(scrubbed, fromPos) {
|
|
503
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
504
|
-
let depth = 0;
|
|
505
|
-
let pos = fromPos;
|
|
506
|
-
let orderStart = -1;
|
|
507
|
-
while (pos < scrubbed.length) {
|
|
508
|
-
const char = scrubbed[pos];
|
|
509
|
-
if (char === "(") {
|
|
510
|
-
depth++;
|
|
511
|
-
} else if (char === ")") {
|
|
512
|
-
depth--;
|
|
513
|
-
} else if (depth === 0) {
|
|
514
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
515
|
-
if (orderStart === -1 && /^\s*order\s+by\s+/i.test(remaining)) {
|
|
516
|
-
const match = /^\s*order\s+by\s+/i.exec(remaining);
|
|
517
|
-
if (match) {
|
|
518
|
-
orderStart = pos + match[0].length;
|
|
519
|
-
}
|
|
520
|
-
} else if (orderStart !== -1 && /^\s*(limit|offset|fetch|for\s+update|$)/i.test(remaining)) {
|
|
521
|
-
return { start: orderStart, end: pos };
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
pos++;
|
|
525
|
-
}
|
|
526
|
-
if (orderStart !== -1) {
|
|
527
|
-
return { start: orderStart, end: scrubbed.length };
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
function qualifyClauseColumnsSelective(original, scrubbed, clauseStart, clauseEnd, defaultSource, ambiguousColumns) {
|
|
532
|
-
const clauseOriginal = original.slice(clauseStart, clauseEnd);
|
|
533
|
-
const clauseScrubbed = scrubbed.slice(clauseStart, clauseEnd);
|
|
534
|
-
let result = clauseOriginal;
|
|
535
|
-
let offset = 0;
|
|
536
|
-
let pos = 0;
|
|
537
|
-
while (pos < clauseScrubbed.length) {
|
|
538
|
-
const quotePos = clauseScrubbed.indexOf('"', pos);
|
|
539
|
-
if (quotePos === -1)
|
|
540
|
-
break;
|
|
541
|
-
if (quotePos > 0 && clauseScrubbed[quotePos - 1] === ".") {
|
|
542
|
-
const ident2 = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
543
|
-
pos = ident2 ? ident2.end : quotePos + 1;
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const ident = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
547
|
-
if (!ident) {
|
|
548
|
-
pos = quotePos + 1;
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (ident.end < clauseScrubbed.length && clauseScrubbed[ident.end] === ".") {
|
|
552
|
-
pos = ident.end + 1;
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
if (!ambiguousColumns.has(ident.name)) {
|
|
556
|
-
pos = ident.end;
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
let afterIdent = ident.end;
|
|
560
|
-
while (afterIdent < clauseScrubbed.length && isWhitespace(clauseScrubbed[afterIdent])) {
|
|
561
|
-
afterIdent++;
|
|
562
|
-
}
|
|
563
|
-
if (clauseScrubbed[afterIdent] === "(") {
|
|
564
|
-
pos = ident.end;
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
const beforeQuote = clauseScrubbed.slice(0, quotePos).toLowerCase();
|
|
568
|
-
if (/\bas\s*$/i.test(beforeQuote)) {
|
|
569
|
-
pos = ident.end;
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
const qualified = `"${defaultSource}"."${ident.name}"`;
|
|
573
|
-
const oldLength = ident.end - quotePos;
|
|
574
|
-
result = result.slice(0, quotePos + offset) + qualified + result.slice(quotePos + oldLength + offset);
|
|
575
|
-
offset += qualified.length - oldLength;
|
|
576
|
-
pos = ident.end;
|
|
577
|
-
}
|
|
578
|
-
return { result, offset };
|
|
579
|
-
}
|
|
580
|
-
function qualifyJoinColumns(query) {
|
|
581
|
-
const lowerQuery = query.toLowerCase();
|
|
582
|
-
if (!lowerQuery.includes("join")) {
|
|
583
|
-
return query;
|
|
584
|
-
}
|
|
585
|
-
const scrubbed = scrubForRewrite(query);
|
|
586
|
-
const fromPos = findMainFromClause(scrubbed);
|
|
587
|
-
if (fromPos < 0) {
|
|
588
|
-
return query;
|
|
589
|
-
}
|
|
590
|
-
const sources = parseTableSources(query, scrubbed);
|
|
591
|
-
if (sources.length < 2) {
|
|
592
|
-
return query;
|
|
593
|
-
}
|
|
594
|
-
const joinClauses = findJoinClauses(query, scrubbed, sources, fromPos);
|
|
595
|
-
if (joinClauses.length === 0) {
|
|
596
|
-
return query;
|
|
597
|
-
}
|
|
598
|
-
const firstSource = sources[0];
|
|
599
|
-
const defaultQualifier = firstSource.alias || firstSource.name;
|
|
600
|
-
let result = query;
|
|
601
|
-
let totalOffset = 0;
|
|
602
|
-
for (const join of joinClauses) {
|
|
603
|
-
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
604
|
-
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
605
|
-
let clauseResult = originalOnClause;
|
|
606
|
-
let clauseOffset = 0;
|
|
607
|
-
let eqPos = -1;
|
|
608
|
-
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
609
|
-
let lhsEnd = eqPos - 1;
|
|
610
|
-
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
611
|
-
lhsEnd--;
|
|
612
|
-
}
|
|
613
|
-
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
614
|
-
continue;
|
|
615
|
-
let lhsStartPos = lhsEnd - 1;
|
|
616
|
-
while (lhsStartPos >= 0) {
|
|
617
|
-
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
618
|
-
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
619
|
-
lhsStartPos -= 2;
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
624
|
-
lhsStartPos--;
|
|
625
|
-
}
|
|
626
|
-
if (lhsStartPos < 0)
|
|
627
|
-
continue;
|
|
628
|
-
const lhsIsQualified = lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === ".";
|
|
629
|
-
let rhsStartPos = eqPos + 1;
|
|
630
|
-
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
631
|
-
rhsStartPos++;
|
|
632
|
-
}
|
|
633
|
-
const rhsChar = originalOnClause[rhsStartPos];
|
|
634
|
-
const rhsIsParam = rhsChar === "$";
|
|
635
|
-
const rhsIsStringLiteral = rhsChar === "'";
|
|
636
|
-
const rhsIsColumn = rhsChar === '"';
|
|
637
|
-
if (!rhsIsParam && !rhsIsStringLiteral && !rhsIsColumn)
|
|
638
|
-
continue;
|
|
639
|
-
const rhsIsQualified = !rhsIsColumn || rhsStartPos > 0 && scrubbedOnClause[rhsStartPos - 1] === ".";
|
|
640
|
-
if (lhsIsQualified || rhsIsQualified)
|
|
641
|
-
continue;
|
|
642
|
-
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
643
|
-
if (!lhsIdent)
|
|
644
|
-
continue;
|
|
645
|
-
let rhsIdent = null;
|
|
646
|
-
let rhsValue = "";
|
|
647
|
-
let rhsEnd = rhsStartPos;
|
|
648
|
-
if (rhsIsParam) {
|
|
649
|
-
let paramEnd = rhsStartPos + 1;
|
|
650
|
-
while (paramEnd < originalOnClause.length && /\d/.test(originalOnClause[paramEnd])) {
|
|
651
|
-
paramEnd++;
|
|
652
|
-
}
|
|
653
|
-
rhsValue = originalOnClause.slice(rhsStartPos, paramEnd);
|
|
654
|
-
rhsEnd = paramEnd;
|
|
655
|
-
} else if (rhsIsStringLiteral) {
|
|
656
|
-
let literalEnd = rhsStartPos + 1;
|
|
657
|
-
while (literalEnd < originalOnClause.length) {
|
|
658
|
-
if (originalOnClause[literalEnd] === "'") {
|
|
659
|
-
if (originalOnClause[literalEnd + 1] === "'") {
|
|
660
|
-
literalEnd += 2;
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
break;
|
|
664
|
-
}
|
|
665
|
-
literalEnd++;
|
|
666
|
-
}
|
|
667
|
-
rhsValue = originalOnClause.slice(rhsStartPos, literalEnd + 1);
|
|
668
|
-
rhsEnd = literalEnd + 1;
|
|
669
|
-
} else if (rhsIsColumn) {
|
|
670
|
-
rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
671
|
-
if (rhsIdent) {
|
|
672
|
-
if (rhsIdent.end < scrubbedOnClause.length && scrubbedOnClause[rhsIdent.end] === ".") {
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
rhsValue = `"${rhsIdent.name}"`;
|
|
676
|
-
rhsEnd = rhsIdent.end;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
if (!rhsValue)
|
|
680
|
-
continue;
|
|
681
|
-
if (!rhsIsColumn || !rhsIdent || lhsIdent.name !== rhsIdent.name) {
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
const lhsOriginal = `"${lhsIdent.name}"`;
|
|
685
|
-
let newLhs = lhsOriginal;
|
|
686
|
-
let newRhs = rhsValue;
|
|
687
|
-
if (!lhsIsQualified && join.leftSource) {
|
|
688
|
-
newLhs = `"${join.leftSource}"."${lhsIdent.name}"`;
|
|
689
|
-
}
|
|
690
|
-
if (!rhsIsQualified && rhsIsColumn && rhsIdent && join.rightSource) {
|
|
691
|
-
newRhs = `"${join.rightSource}"."${rhsIdent.name}"`;
|
|
692
|
-
}
|
|
693
|
-
if (newLhs !== lhsOriginal || newRhs !== rhsValue) {
|
|
694
|
-
const opStart = lhsIdent.end;
|
|
695
|
-
let opEnd = opStart;
|
|
696
|
-
while (opEnd < rhsEnd && (isWhitespace(originalOnClause[opEnd]) || originalOnClause[opEnd] === "=")) {
|
|
697
|
-
opEnd++;
|
|
698
|
-
}
|
|
699
|
-
const operator = originalOnClause.slice(opStart, opEnd);
|
|
700
|
-
const newExpr = `${newLhs}${operator}${newRhs}`;
|
|
701
|
-
const oldExprLength = rhsEnd - lhsStartPos;
|
|
702
|
-
clauseResult = clauseResult.slice(0, lhsStartPos + clauseOffset) + newExpr + clauseResult.slice(lhsStartPos + oldExprLength + clauseOffset);
|
|
703
|
-
clauseOffset += newExpr.length - oldExprLength;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
if (clauseResult !== originalOnClause) {
|
|
707
|
-
result = result.slice(0, join.onStart + totalOffset) + clauseResult + result.slice(join.onEnd + totalOffset);
|
|
708
|
-
totalOffset += clauseResult.length - originalOnClause.length;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
const ambiguousColumns = new Set;
|
|
712
|
-
for (const join of joinClauses) {
|
|
713
|
-
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
714
|
-
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
715
|
-
let eqPos = -1;
|
|
716
|
-
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
717
|
-
let lhsEnd = eqPos - 1;
|
|
718
|
-
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
719
|
-
lhsEnd--;
|
|
720
|
-
}
|
|
721
|
-
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
722
|
-
continue;
|
|
723
|
-
let lhsStartPos = lhsEnd - 1;
|
|
724
|
-
while (lhsStartPos >= 0) {
|
|
725
|
-
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
726
|
-
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
727
|
-
lhsStartPos -= 2;
|
|
728
|
-
continue;
|
|
729
|
-
}
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
lhsStartPos--;
|
|
733
|
-
}
|
|
734
|
-
if (lhsStartPos < 0)
|
|
735
|
-
continue;
|
|
736
|
-
let rhsStartPos = eqPos + 1;
|
|
737
|
-
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
738
|
-
rhsStartPos++;
|
|
739
|
-
}
|
|
740
|
-
if (originalOnClause[rhsStartPos] !== '"')
|
|
741
|
-
continue;
|
|
742
|
-
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
743
|
-
const rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
744
|
-
if (lhsIdent && rhsIdent && lhsIdent.name === rhsIdent.name) {
|
|
745
|
-
ambiguousColumns.add(lhsIdent.name);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
if (ambiguousColumns.size === 0) {
|
|
750
|
-
return result;
|
|
751
|
-
}
|
|
752
|
-
const updatedScrubbed = scrubForRewrite(result);
|
|
753
|
-
const selectClause = findMainSelectClause(updatedScrubbed);
|
|
754
|
-
if (selectClause) {
|
|
755
|
-
const { result: selectResult, offset: selectOffset } = qualifyClauseColumnsSelective(result, updatedScrubbed, selectClause.start, selectClause.end, defaultQualifier, ambiguousColumns);
|
|
756
|
-
if (selectOffset !== 0) {
|
|
757
|
-
result = result.slice(0, selectClause.start) + selectResult + result.slice(selectClause.end);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
const scrubbed2 = scrubForRewrite(result);
|
|
761
|
-
const fromPos2 = findMainFromClause(scrubbed2);
|
|
762
|
-
if (fromPos2 >= 0) {
|
|
763
|
-
const whereClause = findWhereClause(scrubbed2, fromPos2);
|
|
764
|
-
if (whereClause) {
|
|
765
|
-
const { result: whereResult, offset: whereOffset } = qualifyClauseColumnsSelective(result, scrubbed2, whereClause.start, whereClause.end, defaultQualifier, ambiguousColumns);
|
|
766
|
-
if (whereOffset !== 0) {
|
|
767
|
-
result = result.slice(0, whereClause.start) + whereResult + result.slice(whereClause.end);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
const scrubbed3 = scrubForRewrite(result);
|
|
772
|
-
const fromPos3 = findMainFromClause(scrubbed3);
|
|
773
|
-
if (fromPos3 >= 0) {
|
|
774
|
-
const orderByClause = findOrderByClause(scrubbed3, fromPos3);
|
|
775
|
-
if (orderByClause) {
|
|
776
|
-
const { result: orderResult, offset: orderOffset } = qualifyClauseColumnsSelective(result, scrubbed3, orderByClause.start, orderByClause.end, defaultQualifier, ambiguousColumns);
|
|
777
|
-
if (orderOffset !== 0) {
|
|
778
|
-
result = result.slice(0, orderByClause.start) + orderResult + result.slice(orderByClause.end);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return result;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
26
|
// src/sql/result-mapper.ts
|
|
786
27
|
import {
|
|
787
28
|
Column,
|
|
@@ -1358,24 +599,6 @@ function isSavepointSyntaxError(error) {
|
|
|
1358
599
|
}
|
|
1359
600
|
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
1360
601
|
}
|
|
1361
|
-
function rewriteQuery(mode, query) {
|
|
1362
|
-
if (mode === "never") {
|
|
1363
|
-
return { sql: query, rewritten: false };
|
|
1364
|
-
}
|
|
1365
|
-
let result = query;
|
|
1366
|
-
let wasRewritten = false;
|
|
1367
|
-
const arrayRewritten = adaptArrayOperators(result);
|
|
1368
|
-
if (arrayRewritten !== result) {
|
|
1369
|
-
result = arrayRewritten;
|
|
1370
|
-
wasRewritten = true;
|
|
1371
|
-
}
|
|
1372
|
-
const joinQualified = qualifyJoinColumns(result);
|
|
1373
|
-
if (joinQualified !== result) {
|
|
1374
|
-
result = joinQualified;
|
|
1375
|
-
wasRewritten = true;
|
|
1376
|
-
}
|
|
1377
|
-
return { sql: result, rewritten: wasRewritten };
|
|
1378
|
-
}
|
|
1379
602
|
|
|
1380
603
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
1381
604
|
client;
|
|
@@ -1386,12 +609,11 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1386
609
|
fields;
|
|
1387
610
|
_isResponseInArrayMode;
|
|
1388
611
|
customResultMapper;
|
|
1389
|
-
rewriteArraysMode;
|
|
1390
612
|
rejectStringArrayLiterals;
|
|
1391
613
|
prepareCache;
|
|
1392
614
|
warnOnStringArrayLiteral;
|
|
1393
615
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
1394
|
-
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper,
|
|
616
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
1395
617
|
super({ sql: queryString, params });
|
|
1396
618
|
this.client = client;
|
|
1397
619
|
this.dialect = dialect;
|
|
@@ -1401,7 +623,6 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1401
623
|
this.fields = fields;
|
|
1402
624
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
1403
625
|
this.customResultMapper = customResultMapper;
|
|
1404
|
-
this.rewriteArraysMode = rewriteArraysMode;
|
|
1405
626
|
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
1406
627
|
this.prepareCache = prepareCache;
|
|
1407
628
|
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
@@ -1412,20 +633,16 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1412
633
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1413
634
|
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
1414
635
|
});
|
|
1415
|
-
|
|
1416
|
-
if (didRewrite) {
|
|
1417
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
1418
|
-
}
|
|
1419
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
636
|
+
this.logger.logQuery(this.queryString, params);
|
|
1420
637
|
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
1421
638
|
if (fields) {
|
|
1422
|
-
const { rows: rows2 } = await executeArraysOnClient(this.client,
|
|
639
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params, { prepareCache: this.prepareCache });
|
|
1423
640
|
if (rows2.length === 0) {
|
|
1424
641
|
return [];
|
|
1425
642
|
}
|
|
1426
643
|
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
1427
644
|
}
|
|
1428
|
-
const rows = await executeOnClient(this.client,
|
|
645
|
+
const rows = await executeOnClient(this.client, this.queryString, params, {
|
|
1429
646
|
prepareCache: this.prepareCache
|
|
1430
647
|
});
|
|
1431
648
|
return rows;
|
|
@@ -1445,7 +662,6 @@ class DuckDBSession extends PgSession {
|
|
|
1445
662
|
static [entityKind] = "DuckDBSession";
|
|
1446
663
|
dialect;
|
|
1447
664
|
logger;
|
|
1448
|
-
rewriteArraysMode;
|
|
1449
665
|
rejectStringArrayLiterals;
|
|
1450
666
|
prepareCache;
|
|
1451
667
|
hasWarnedArrayLiteral = false;
|
|
@@ -1457,17 +673,15 @@ class DuckDBSession extends PgSession {
|
|
|
1457
673
|
this.options = options;
|
|
1458
674
|
this.dialect = dialect;
|
|
1459
675
|
this.logger = options.logger ?? new NoopLogger;
|
|
1460
|
-
this.rewriteArraysMode = options.rewriteArrays ?? "auto";
|
|
1461
676
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
1462
677
|
this.prepareCache = options.prepareCache;
|
|
1463
678
|
this.options = {
|
|
1464
679
|
...options,
|
|
1465
|
-
rewriteArrays: this.rewriteArraysMode,
|
|
1466
680
|
prepareCache: this.prepareCache
|
|
1467
681
|
};
|
|
1468
682
|
}
|
|
1469
683
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
1470
|
-
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);
|
|
1471
685
|
}
|
|
1472
686
|
execute(query) {
|
|
1473
687
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1527,12 +741,8 @@ query: ${query}`, []);
|
|
|
1527
741
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1528
742
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1529
743
|
});
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1533
|
-
}
|
|
1534
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1535
|
-
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
744
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
745
|
+
return executeInBatches(this.client, builtQuery.sql, params, options);
|
|
1536
746
|
}
|
|
1537
747
|
executeBatchesRaw(query, options = {}) {
|
|
1538
748
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1542,12 +752,8 @@ query: ${query}`, []);
|
|
|
1542
752
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1543
753
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1544
754
|
});
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1548
|
-
}
|
|
1549
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1550
|
-
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
755
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
756
|
+
return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
|
|
1551
757
|
}
|
|
1552
758
|
async executeArrow(query) {
|
|
1553
759
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1557,12 +763,8 @@ query: ${query}`, []);
|
|
|
1557
763
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1558
764
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1559
765
|
});
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1563
|
-
}
|
|
1564
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1565
|
-
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
766
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
767
|
+
return executeArrowOnClient(this.client, builtQuery.sql, params);
|
|
1566
768
|
}
|
|
1567
769
|
markRollbackOnly() {
|
|
1568
770
|
this.rollbackOnly = true;
|
|
@@ -1664,6 +866,524 @@ import {
|
|
|
1664
866
|
import {
|
|
1665
867
|
sql as sql2
|
|
1666
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
|
+
schema: "db" in from ? from.db ?? null : null
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
if ("expr" in from && from.as) {
|
|
1022
|
+
return {
|
|
1023
|
+
name: from.as,
|
|
1024
|
+
alias: from.as,
|
|
1025
|
+
schema: null
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
function getQualifier(source) {
|
|
1031
|
+
return {
|
|
1032
|
+
table: source.alias ?? source.name,
|
|
1033
|
+
schema: source.schema
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function isUnqualifiedColumnRef(expr) {
|
|
1037
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && (!("table" in expr) || !expr.table);
|
|
1038
|
+
}
|
|
1039
|
+
function isQualifiedColumnRef(expr) {
|
|
1040
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && "table" in expr && !!expr.table;
|
|
1041
|
+
}
|
|
1042
|
+
function getColumnName(col) {
|
|
1043
|
+
if (typeof col.column === "string") {
|
|
1044
|
+
return col.column;
|
|
1045
|
+
}
|
|
1046
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1047
|
+
return String(col.column.expr.value);
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
function applyQualifier(col, qualifier) {
|
|
1052
|
+
col.table = qualifier.table;
|
|
1053
|
+
if (!("schema" in col) || !col.schema) {
|
|
1054
|
+
col.schema = qualifier.schema;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function unwrapColumnRef(expr) {
|
|
1058
|
+
if (!expr || typeof expr !== "object")
|
|
1059
|
+
return null;
|
|
1060
|
+
if ("type" in expr && expr.type === "column_ref") {
|
|
1061
|
+
return expr;
|
|
1062
|
+
}
|
|
1063
|
+
if ("expr" in expr && expr.expr) {
|
|
1064
|
+
return unwrapColumnRef(expr.expr);
|
|
1065
|
+
}
|
|
1066
|
+
if ("ast" in expr && expr.ast && typeof expr.ast === "object") {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
if ("args" in expr && expr.args) {
|
|
1070
|
+
const args = expr.args;
|
|
1071
|
+
if (args.expr) {
|
|
1072
|
+
return unwrapColumnRef(args.expr);
|
|
1073
|
+
}
|
|
1074
|
+
if (args.value && args.value.length === 1) {
|
|
1075
|
+
return unwrapColumnRef(args.value[0]);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
function isBinaryExpr(expr) {
|
|
1081
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1082
|
+
}
|
|
1083
|
+
function walkOnClause(expr, leftQualifier, rightQualifier, ambiguousColumns) {
|
|
1084
|
+
if (!expr || typeof expr !== "object")
|
|
1085
|
+
return false;
|
|
1086
|
+
let transformed = false;
|
|
1087
|
+
if (isBinaryExpr(expr)) {
|
|
1088
|
+
const left = expr.left;
|
|
1089
|
+
const right = expr.right;
|
|
1090
|
+
const leftCol = unwrapColumnRef(left);
|
|
1091
|
+
const rightCol = unwrapColumnRef(right);
|
|
1092
|
+
const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
|
|
1093
|
+
const rightUnqualified = rightCol ? isUnqualifiedColumnRef(rightCol) : false;
|
|
1094
|
+
const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
|
|
1095
|
+
const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
|
|
1096
|
+
const leftColName = leftCol ? getColumnName(leftCol) : null;
|
|
1097
|
+
const rightColName = rightCol ? getColumnName(rightCol) : null;
|
|
1098
|
+
if (expr.operator === "=" && leftColName && rightColName && leftColName === rightColName) {
|
|
1099
|
+
if (leftUnqualified && rightUnqualified) {
|
|
1100
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1101
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1102
|
+
ambiguousColumns.add(leftColName);
|
|
1103
|
+
transformed = true;
|
|
1104
|
+
} else if (leftQualified && rightUnqualified) {
|
|
1105
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1106
|
+
ambiguousColumns.add(rightColName);
|
|
1107
|
+
transformed = true;
|
|
1108
|
+
} else if (leftUnqualified && rightQualified) {
|
|
1109
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1110
|
+
ambiguousColumns.add(leftColName);
|
|
1111
|
+
transformed = true;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1115
|
+
transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1116
|
+
}
|
|
1117
|
+
return transformed;
|
|
1118
|
+
}
|
|
1119
|
+
function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns) {
|
|
1120
|
+
if (!expr || typeof expr !== "object")
|
|
1121
|
+
return false;
|
|
1122
|
+
let transformed = false;
|
|
1123
|
+
if (isUnqualifiedColumnRef(expr)) {
|
|
1124
|
+
const colName = getColumnName(expr);
|
|
1125
|
+
if (colName && ambiguousColumns.has(colName)) {
|
|
1126
|
+
applyQualifier(expr, defaultQualifier);
|
|
1127
|
+
transformed = true;
|
|
1128
|
+
}
|
|
1129
|
+
return transformed;
|
|
1130
|
+
}
|
|
1131
|
+
if (isBinaryExpr(expr)) {
|
|
1132
|
+
const binary = expr;
|
|
1133
|
+
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1134
|
+
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
1135
|
+
return transformed;
|
|
1136
|
+
}
|
|
1137
|
+
if ("args" in expr && expr.args) {
|
|
1138
|
+
const args = expr.args;
|
|
1139
|
+
if (args.value && Array.isArray(args.value)) {
|
|
1140
|
+
for (const arg of args.value) {
|
|
1141
|
+
transformed = qualifyAmbiguousInExpression(arg, defaultQualifier, ambiguousColumns) || transformed;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (args.expr) {
|
|
1145
|
+
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if ("over" in expr && expr.over && typeof expr.over === "object") {
|
|
1149
|
+
const over = expr.over;
|
|
1150
|
+
if (Array.isArray(over.partition)) {
|
|
1151
|
+
for (const part of over.partition) {
|
|
1152
|
+
transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (Array.isArray(over.orderby)) {
|
|
1156
|
+
for (const order of over.orderby) {
|
|
1157
|
+
transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return transformed;
|
|
1162
|
+
}
|
|
1163
|
+
function hasUnqualifiedColumns(expr) {
|
|
1164
|
+
if (!expr || typeof expr !== "object")
|
|
1165
|
+
return false;
|
|
1166
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1167
|
+
const left = expr.left;
|
|
1168
|
+
const right = expr.right;
|
|
1169
|
+
const leftCol = unwrapColumnRef(left);
|
|
1170
|
+
const rightCol = unwrapColumnRef(right);
|
|
1171
|
+
if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
|
|
1175
|
+
return true;
|
|
1176
|
+
if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
if ("args" in expr && expr.args) {
|
|
1180
|
+
const args = expr.args;
|
|
1181
|
+
if (args.expr && isUnqualifiedColumnRef(args.expr))
|
|
1182
|
+
return true;
|
|
1183
|
+
if (args.value) {
|
|
1184
|
+
for (const arg of args.value) {
|
|
1185
|
+
if (isUnqualifiedColumnRef(arg))
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
function walkSelect(select) {
|
|
1193
|
+
let transformed = false;
|
|
1194
|
+
const ambiguousColumns = new Set;
|
|
1195
|
+
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1196
|
+
const firstSource = getTableSource(select.from[0]);
|
|
1197
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
|
|
1198
|
+
let prevSource = firstSource;
|
|
1199
|
+
let hasAnyUnqualified = false;
|
|
1200
|
+
for (const from of select.from) {
|
|
1201
|
+
if ("join" in from) {
|
|
1202
|
+
const join = from;
|
|
1203
|
+
if (join.on && hasUnqualifiedColumns(join.on)) {
|
|
1204
|
+
hasAnyUnqualified = true;
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (!hasAnyUnqualified) {
|
|
1210
|
+
for (const from of select.from) {
|
|
1211
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1212
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} else {
|
|
1216
|
+
for (const from of select.from) {
|
|
1217
|
+
if ("join" in from) {
|
|
1218
|
+
const join = from;
|
|
1219
|
+
const currentSource = getTableSource(join);
|
|
1220
|
+
if (join.on && prevSource && currentSource) {
|
|
1221
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1222
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1223
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1224
|
+
}
|
|
1225
|
+
if (join.using && prevSource && currentSource) {
|
|
1226
|
+
for (const usingCol of join.using) {
|
|
1227
|
+
if (typeof usingCol === "string") {
|
|
1228
|
+
ambiguousColumns.add(usingCol);
|
|
1229
|
+
} else if ("value" in usingCol) {
|
|
1230
|
+
ambiguousColumns.add(String(usingCol.value));
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
prevSource = currentSource;
|
|
1235
|
+
} else {
|
|
1236
|
+
const source = getTableSource(from);
|
|
1237
|
+
if (source) {
|
|
1238
|
+
prevSource = source;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1242
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1246
|
+
if (Array.isArray(select.columns)) {
|
|
1247
|
+
for (const col of select.columns) {
|
|
1248
|
+
if ("expr" in col) {
|
|
1249
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1254
|
+
if (Array.isArray(select.orderby)) {
|
|
1255
|
+
for (const order of select.orderby) {
|
|
1256
|
+
if (order.expr) {
|
|
1257
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if (select.with) {
|
|
1265
|
+
for (const cte of select.with) {
|
|
1266
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1267
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1268
|
+
transformed = walkSelect(cteSelect) || transformed;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (select._next) {
|
|
1273
|
+
transformed = walkSelect(select._next) || transformed;
|
|
1274
|
+
}
|
|
1275
|
+
return transformed;
|
|
1276
|
+
}
|
|
1277
|
+
function qualifyJoinColumns(ast) {
|
|
1278
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1279
|
+
let transformed = false;
|
|
1280
|
+
for (const stmt of statements) {
|
|
1281
|
+
if (stmt.type === "select") {
|
|
1282
|
+
transformed = walkSelect(stmt) || transformed;
|
|
1283
|
+
} else if (stmt.type === "insert") {
|
|
1284
|
+
const insert = stmt;
|
|
1285
|
+
if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
|
|
1286
|
+
transformed = walkSelect(insert.values) || transformed;
|
|
1287
|
+
}
|
|
1288
|
+
} else if (stmt.type === "update") {
|
|
1289
|
+
const update = stmt;
|
|
1290
|
+
const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
|
|
1291
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1292
|
+
const fromSources = update.from ?? [];
|
|
1293
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1294
|
+
if (update.where && defaultQualifier && firstFrom) {
|
|
1295
|
+
const ambiguous = new Set;
|
|
1296
|
+
transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1297
|
+
transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
|
|
1298
|
+
}
|
|
1299
|
+
if (Array.isArray(update.returning) && defaultQualifier) {
|
|
1300
|
+
for (const ret of update.returning) {
|
|
1301
|
+
transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
} else if (stmt.type === "delete") {
|
|
1305
|
+
const del = stmt;
|
|
1306
|
+
const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
|
|
1307
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1308
|
+
const fromSources = del.from ?? [];
|
|
1309
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1310
|
+
if (del.where && defaultQualifier && firstFrom) {
|
|
1311
|
+
const ambiguous = new Set;
|
|
1312
|
+
transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1313
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
|
|
1314
|
+
} else if (del.where && defaultQualifier) {
|
|
1315
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return transformed;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/sql/ast-transformer.ts
|
|
1323
|
+
var { Parser } = nodeSqlParser;
|
|
1324
|
+
var parser = new Parser;
|
|
1325
|
+
var CACHE_SIZE = 500;
|
|
1326
|
+
var transformCache = new Map;
|
|
1327
|
+
function getCachedOrTransform(query, transform) {
|
|
1328
|
+
const cached = transformCache.get(query);
|
|
1329
|
+
if (cached) {
|
|
1330
|
+
transformCache.delete(query);
|
|
1331
|
+
transformCache.set(query, cached);
|
|
1332
|
+
return cached;
|
|
1333
|
+
}
|
|
1334
|
+
const result = transform();
|
|
1335
|
+
if (transformCache.size >= CACHE_SIZE) {
|
|
1336
|
+
const oldestKey = transformCache.keys().next().value;
|
|
1337
|
+
if (oldestKey) {
|
|
1338
|
+
transformCache.delete(oldestKey);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
transformCache.set(query, result);
|
|
1342
|
+
return result;
|
|
1343
|
+
}
|
|
1344
|
+
var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
|
|
1345
|
+
function hasJoin(query) {
|
|
1346
|
+
return /\bjoin\b/i.test(query);
|
|
1347
|
+
}
|
|
1348
|
+
function debugLog(message, payload) {
|
|
1349
|
+
if (process?.env?.[DEBUG_ENV]) {
|
|
1350
|
+
console.debug("[duckdb-ast]", message, payload ?? "");
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function transformSQL(query) {
|
|
1354
|
+
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1355
|
+
const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
|
|
1356
|
+
if (!needsArrayTransform && !needsJoinTransform) {
|
|
1357
|
+
return { sql: query, transformed: false };
|
|
1358
|
+
}
|
|
1359
|
+
return getCachedOrTransform(query, () => {
|
|
1360
|
+
try {
|
|
1361
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1362
|
+
let transformed = false;
|
|
1363
|
+
if (needsArrayTransform) {
|
|
1364
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1365
|
+
}
|
|
1366
|
+
if (needsJoinTransform) {
|
|
1367
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1368
|
+
}
|
|
1369
|
+
if (!transformed) {
|
|
1370
|
+
debugLog("AST parsed but no transformation applied", {
|
|
1371
|
+
join: needsJoinTransform
|
|
1372
|
+
});
|
|
1373
|
+
return { sql: query, transformed: false };
|
|
1374
|
+
}
|
|
1375
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1376
|
+
return { sql: transformedSql, transformed: true };
|
|
1377
|
+
} catch (err) {
|
|
1378
|
+
debugLog("AST transform failed; returning original SQL", {
|
|
1379
|
+
error: err.message
|
|
1380
|
+
});
|
|
1381
|
+
return { sql: query, transformed: false };
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/dialect.ts
|
|
1667
1387
|
class DuckDBDialect extends PgDialect {
|
|
1668
1388
|
static [entityKind2] = "DuckDBPgDialect";
|
|
1669
1389
|
hasPgJsonColumn = false;
|
|
@@ -1740,6 +1460,14 @@ class DuckDBDialect extends PgDialect {
|
|
|
1740
1460
|
return "none";
|
|
1741
1461
|
}
|
|
1742
1462
|
}
|
|
1463
|
+
sqlToQuery(sqlObj, invokeSource) {
|
|
1464
|
+
const result = super.sqlToQuery(sqlObj, invokeSource);
|
|
1465
|
+
const transformed = transformSQL(result.sql);
|
|
1466
|
+
return {
|
|
1467
|
+
...result,
|
|
1468
|
+
sql: transformed.sql
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1743
1471
|
}
|
|
1744
1472
|
|
|
1745
1473
|
// src/select-builder.ts
|
|
@@ -1750,10 +1478,10 @@ import {
|
|
|
1750
1478
|
} from "drizzle-orm/pg-core/query-builders";
|
|
1751
1479
|
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
1752
1480
|
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
1753
|
-
import { SQL as
|
|
1481
|
+
import { SQL as SQL5 } from "drizzle-orm/sql/sql";
|
|
1754
1482
|
|
|
1755
1483
|
// src/sql/selection.ts
|
|
1756
|
-
import { Column as Column2, SQL as
|
|
1484
|
+
import { Column as Column2, SQL as SQL4, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
1757
1485
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
1758
1486
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
1759
1487
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -1763,16 +1491,16 @@ function mapEntries(obj, prefix, fullJoin = false) {
|
|
|
1763
1491
|
sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
|
|
1764
1492
|
];
|
|
1765
1493
|
}
|
|
1766
|
-
if (fullJoin && is3(value,
|
|
1494
|
+
if (fullJoin && is3(value, SQL4)) {
|
|
1767
1495
|
const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
|
|
1768
1496
|
const tableName = col?.table && getTableName2(col?.table);
|
|
1769
1497
|
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
1770
1498
|
}
|
|
1771
|
-
if (is3(value,
|
|
1772
|
-
const aliased = is3(value,
|
|
1499
|
+
if (is3(value, SQL4) || is3(value, Column2)) {
|
|
1500
|
+
const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
|
|
1773
1501
|
return [key, aliased.as(qualified)];
|
|
1774
1502
|
}
|
|
1775
|
-
if (is3(value,
|
|
1503
|
+
if (is3(value, SQL4.Aliased)) {
|
|
1776
1504
|
return [key, value];
|
|
1777
1505
|
}
|
|
1778
1506
|
if (typeof value === "object" && value !== null) {
|
|
@@ -1820,7 +1548,7 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
1820
1548
|
]));
|
|
1821
1549
|
} else if (is4(src, PgViewBase)) {
|
|
1822
1550
|
fields = src[ViewBaseConfig]?.selectedFields;
|
|
1823
|
-
} else if (is4(src,
|
|
1551
|
+
} else if (is4(src, SQL5)) {
|
|
1824
1552
|
fields = {};
|
|
1825
1553
|
} else {
|
|
1826
1554
|
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
@@ -2011,16 +1739,6 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
2011
1739
|
}
|
|
2012
1740
|
|
|
2013
1741
|
// src/options.ts
|
|
2014
|
-
var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
|
|
2015
|
-
function resolveRewriteArraysOption(value) {
|
|
2016
|
-
if (value === undefined)
|
|
2017
|
-
return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
2018
|
-
if (value === true)
|
|
2019
|
-
return "auto";
|
|
2020
|
-
if (value === false)
|
|
2021
|
-
return "never";
|
|
2022
|
-
return value;
|
|
2023
|
-
}
|
|
2024
1742
|
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
2025
1743
|
function resolvePrepareCacheOption(option) {
|
|
2026
1744
|
if (!option)
|
|
@@ -2050,7 +1768,6 @@ class DuckDBDriver {
|
|
|
2050
1768
|
createSession(schema) {
|
|
2051
1769
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
2052
1770
|
logger: this.options.logger,
|
|
2053
|
-
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
2054
1771
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
2055
1772
|
prepareCache: this.options.prepareCache
|
|
2056
1773
|
});
|
|
@@ -2065,7 +1782,6 @@ function isConfigObject(data) {
|
|
|
2065
1782
|
}
|
|
2066
1783
|
function createFromClient(client, config = {}, instance) {
|
|
2067
1784
|
const dialect = new DuckDBDialect;
|
|
2068
|
-
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
2069
1785
|
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
2070
1786
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
2071
1787
|
let schema;
|
|
@@ -2079,7 +1795,6 @@ function createFromClient(client, config = {}, instance) {
|
|
|
2079
1795
|
}
|
|
2080
1796
|
const driver = new DuckDBDriver(client, dialect, {
|
|
2081
1797
|
logger,
|
|
2082
|
-
rewriteArrays: rewriteArraysMode,
|
|
2083
1798
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
2084
1799
|
prepareCache
|
|
2085
1800
|
});
|
|
@@ -2743,7 +2458,7 @@ function renderImports(imports, importBasePath) {
|
|
|
2743
2458
|
// src/bin/duckdb-introspect.ts
|
|
2744
2459
|
function parseArgs(argv) {
|
|
2745
2460
|
const options = {
|
|
2746
|
-
outFile: path.resolve(
|
|
2461
|
+
outFile: path.resolve(process2.cwd(), "drizzle/schema.ts"),
|
|
2747
2462
|
outMeta: undefined,
|
|
2748
2463
|
allDatabases: false,
|
|
2749
2464
|
includeViews: false,
|
|
@@ -2768,12 +2483,12 @@ function parseArgs(argv) {
|
|
|
2768
2483
|
break;
|
|
2769
2484
|
case "--out":
|
|
2770
2485
|
case "--outFile":
|
|
2771
|
-
options.outFile = path.resolve(
|
|
2486
|
+
options.outFile = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.ts");
|
|
2772
2487
|
break;
|
|
2773
2488
|
case "--out-json":
|
|
2774
2489
|
case "--outJson":
|
|
2775
2490
|
case "--json":
|
|
2776
|
-
options.outMeta = path.resolve(
|
|
2491
|
+
options.outMeta = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.meta.json");
|
|
2777
2492
|
break;
|
|
2778
2493
|
case "--include-views":
|
|
2779
2494
|
case "--includeViews":
|
|
@@ -2788,7 +2503,7 @@ function parseArgs(argv) {
|
|
|
2788
2503
|
case "--help":
|
|
2789
2504
|
case "-h":
|
|
2790
2505
|
printHelp();
|
|
2791
|
-
|
|
2506
|
+
process2.exit(0);
|
|
2792
2507
|
default:
|
|
2793
2508
|
if (arg.startsWith("-")) {
|
|
2794
2509
|
console.warn(`Unknown option ${arg}`);
|
|
@@ -2830,12 +2545,12 @@ Examples:
|
|
|
2830
2545
|
`);
|
|
2831
2546
|
}
|
|
2832
2547
|
async function main() {
|
|
2833
|
-
const options = parseArgs(
|
|
2548
|
+
const options = parseArgs(process2.argv.slice(2));
|
|
2834
2549
|
if (!options.url) {
|
|
2835
2550
|
printHelp();
|
|
2836
2551
|
throw new Error("Missing required --url");
|
|
2837
2552
|
}
|
|
2838
|
-
const instanceOptions = options.url.startsWith("md:") &&
|
|
2553
|
+
const instanceOptions = options.url.startsWith("md:") && process2.env.MOTHERDUCK_TOKEN ? { motherduck_token: process2.env.MOTHERDUCK_TOKEN } : undefined;
|
|
2839
2554
|
const instance = await DuckDBInstance3.create(options.url, instanceOptions);
|
|
2840
2555
|
const connection = await instance.connect();
|
|
2841
2556
|
const db = drizzle(connection);
|
|
@@ -2871,5 +2586,5 @@ async function main() {
|
|
|
2871
2586
|
}
|
|
2872
2587
|
main().catch((err) => {
|
|
2873
2588
|
console.error(err instanceof Error ? err.message : err);
|
|
2874
|
-
|
|
2589
|
+
process2.exit(1);
|
|
2875
2590
|
});
|