@leonardovida-md/drizzle-neo-duckdb 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/dialect.d.ts +3 -1
- package/dist/driver.d.ts +1 -3
- package/dist/duckdb-introspect.mjs +355 -829
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +371 -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 +15 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +5 -0
- package/dist/utils.d.ts +0 -1
- package/package.json +3 -2
- package/src/dialect.ts +20 -0
- package/src/driver.ts +0 -8
- package/src/index.ts +1 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +0 -15
- package/src/session.ts +10 -96
- package/src/sql/ast-transformer.ts +68 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +278 -0
- package/src/utils.ts +0 -1
- package/dist/sql/query-rewriters.d.ts +0 -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,335 @@ 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
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
if ("expr" in from && from.as) {
|
|
1072
|
+
return {
|
|
1073
|
+
name: from.as,
|
|
1074
|
+
alias: from.as
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
function getQualifier(source) {
|
|
1080
|
+
return source.alias ?? source.name;
|
|
1081
|
+
}
|
|
1082
|
+
function isUnqualifiedColumnRef(expr) {
|
|
1083
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && !(("table" in expr) && expr.table);
|
|
1084
|
+
}
|
|
1085
|
+
function getColumnName(col) {
|
|
1086
|
+
if (typeof col.column === "string") {
|
|
1087
|
+
return col.column;
|
|
1088
|
+
}
|
|
1089
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1090
|
+
return String(col.column.expr.value);
|
|
1091
|
+
}
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
function walkOnClause(expr, leftSource, rightSource, ambiguousColumns) {
|
|
1095
|
+
if (!expr || typeof expr !== "object")
|
|
1096
|
+
return false;
|
|
1097
|
+
let transformed = false;
|
|
1098
|
+
if (expr.type === "binary_expr") {
|
|
1099
|
+
if (expr.operator === "=") {
|
|
1100
|
+
const left = expr.left;
|
|
1101
|
+
const right = expr.right;
|
|
1102
|
+
if (isUnqualifiedColumnRef(left) && isUnqualifiedColumnRef(right)) {
|
|
1103
|
+
const leftColName = getColumnName(left);
|
|
1104
|
+
const rightColName = getColumnName(right);
|
|
1105
|
+
if (leftColName && rightColName && leftColName === rightColName) {
|
|
1106
|
+
left.table = leftSource;
|
|
1107
|
+
right.table = rightSource;
|
|
1108
|
+
ambiguousColumns.add(leftColName);
|
|
1109
|
+
transformed = true;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (expr.operator === "AND" || expr.operator === "OR") {
|
|
1114
|
+
transformed = walkOnClause(expr.left, leftSource, rightSource, ambiguousColumns) || transformed;
|
|
1115
|
+
transformed = walkOnClause(expr.right, leftSource, rightSource, ambiguousColumns) || transformed;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return transformed;
|
|
1119
|
+
}
|
|
1120
|
+
function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns) {
|
|
1121
|
+
if (!expr || typeof expr !== "object")
|
|
1122
|
+
return false;
|
|
1123
|
+
let transformed = false;
|
|
1124
|
+
if (isUnqualifiedColumnRef(expr)) {
|
|
1125
|
+
const colName = getColumnName(expr);
|
|
1126
|
+
if (colName && ambiguousColumns.has(colName)) {
|
|
1127
|
+
expr.table = defaultQualifier;
|
|
1128
|
+
transformed = true;
|
|
1129
|
+
}
|
|
1130
|
+
return transformed;
|
|
1131
|
+
}
|
|
1132
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1133
|
+
const binary = expr;
|
|
1134
|
+
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1135
|
+
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
1136
|
+
return transformed;
|
|
1137
|
+
}
|
|
1138
|
+
if ("args" in expr && expr.args) {
|
|
1139
|
+
const args = expr.args;
|
|
1140
|
+
if (args.value && Array.isArray(args.value)) {
|
|
1141
|
+
for (const arg of args.value) {
|
|
1142
|
+
transformed = qualifyAmbiguousInExpression(arg, defaultQualifier, ambiguousColumns) || transformed;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (args.expr) {
|
|
1146
|
+
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return transformed;
|
|
1150
|
+
}
|
|
1151
|
+
function walkSelect(select) {
|
|
1152
|
+
let transformed = false;
|
|
1153
|
+
const ambiguousColumns = new Set;
|
|
1154
|
+
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1155
|
+
const firstSource = getTableSource(select.from[0]);
|
|
1156
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : "";
|
|
1157
|
+
let prevSource = firstSource;
|
|
1158
|
+
for (const from of select.from) {
|
|
1159
|
+
if ("join" in from) {
|
|
1160
|
+
const join = from;
|
|
1161
|
+
const currentSource = getTableSource(join);
|
|
1162
|
+
if (join.on && prevSource && currentSource) {
|
|
1163
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1164
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1165
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1166
|
+
}
|
|
1167
|
+
prevSource = currentSource;
|
|
1168
|
+
} else {
|
|
1169
|
+
const source = getTableSource(from);
|
|
1170
|
+
if (source) {
|
|
1171
|
+
prevSource = source;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1175
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1179
|
+
if (Array.isArray(select.columns)) {
|
|
1180
|
+
for (const col of select.columns) {
|
|
1181
|
+
if ("expr" in col) {
|
|
1182
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1187
|
+
if (Array.isArray(select.orderby)) {
|
|
1188
|
+
for (const order of select.orderby) {
|
|
1189
|
+
if (order.expr) {
|
|
1190
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (select.with) {
|
|
1197
|
+
for (const cte of select.with) {
|
|
1198
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1199
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1200
|
+
transformed = walkSelect(cteSelect) || transformed;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (select._next) {
|
|
1205
|
+
transformed = walkSelect(select._next) || transformed;
|
|
1206
|
+
}
|
|
1207
|
+
return transformed;
|
|
1208
|
+
}
|
|
1209
|
+
function qualifyJoinColumns(ast) {
|
|
1210
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1211
|
+
let transformed = false;
|
|
1212
|
+
for (const stmt of statements) {
|
|
1213
|
+
if (stmt.type === "select") {
|
|
1214
|
+
transformed = walkSelect(stmt) || transformed;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return transformed;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/sql/ast-transformer.ts
|
|
1221
|
+
var { Parser } = nodeSqlParser;
|
|
1222
|
+
var parser = new Parser;
|
|
1223
|
+
function transformSQL(query) {
|
|
1224
|
+
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1225
|
+
const needsJoinTransform = query.toLowerCase().includes("join");
|
|
1226
|
+
if (!needsArrayTransform && !needsJoinTransform) {
|
|
1227
|
+
return { sql: query, transformed: false };
|
|
1228
|
+
}
|
|
1229
|
+
try {
|
|
1230
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1231
|
+
let transformed = false;
|
|
1232
|
+
if (needsArrayTransform) {
|
|
1233
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1234
|
+
}
|
|
1235
|
+
if (needsJoinTransform) {
|
|
1236
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1237
|
+
}
|
|
1238
|
+
if (!transformed) {
|
|
1239
|
+
return { sql: query, transformed: false };
|
|
1240
|
+
}
|
|
1241
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1242
|
+
return { sql: transformedSql, transformed: true };
|
|
1243
|
+
} catch {
|
|
1244
|
+
return { sql: query, transformed: false };
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/dialect.ts
|
|
1718
1249
|
class DuckDBDialect extends PgDialect {
|
|
1719
1250
|
static [entityKind2] = "DuckDBPgDialect";
|
|
1720
1251
|
hasPgJsonColumn = false;
|
|
@@ -1791,6 +1322,14 @@ class DuckDBDialect extends PgDialect {
|
|
|
1791
1322
|
return "none";
|
|
1792
1323
|
}
|
|
1793
1324
|
}
|
|
1325
|
+
sqlToQuery(sqlObj, invokeSource) {
|
|
1326
|
+
const result = super.sqlToQuery(sqlObj, invokeSource);
|
|
1327
|
+
const transformed = transformSQL(result.sql);
|
|
1328
|
+
return {
|
|
1329
|
+
...result,
|
|
1330
|
+
sql: transformed.sql
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1794
1333
|
}
|
|
1795
1334
|
|
|
1796
1335
|
// src/select-builder.ts
|
|
@@ -1801,10 +1340,10 @@ import {
|
|
|
1801
1340
|
} from "drizzle-orm/pg-core/query-builders";
|
|
1802
1341
|
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
1803
1342
|
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
1804
|
-
import { SQL as
|
|
1343
|
+
import { SQL as SQL5 } from "drizzle-orm/sql/sql";
|
|
1805
1344
|
|
|
1806
1345
|
// src/sql/selection.ts
|
|
1807
|
-
import { Column as Column2, SQL as
|
|
1346
|
+
import { Column as Column2, SQL as SQL4, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
1808
1347
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
1809
1348
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
1810
1349
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -1814,16 +1353,16 @@ function mapEntries(obj, prefix, fullJoin = false) {
|
|
|
1814
1353
|
sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
|
|
1815
1354
|
];
|
|
1816
1355
|
}
|
|
1817
|
-
if (fullJoin && is3(value,
|
|
1356
|
+
if (fullJoin && is3(value, SQL4)) {
|
|
1818
1357
|
const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
|
|
1819
1358
|
const tableName = col?.table && getTableName2(col?.table);
|
|
1820
1359
|
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
1821
1360
|
}
|
|
1822
|
-
if (is3(value,
|
|
1823
|
-
const aliased = is3(value,
|
|
1361
|
+
if (is3(value, SQL4) || is3(value, Column2)) {
|
|
1362
|
+
const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
|
|
1824
1363
|
return [key, aliased.as(qualified)];
|
|
1825
1364
|
}
|
|
1826
|
-
if (is3(value,
|
|
1365
|
+
if (is3(value, SQL4.Aliased)) {
|
|
1827
1366
|
return [key, value];
|
|
1828
1367
|
}
|
|
1829
1368
|
if (typeof value === "object" && value !== null) {
|
|
@@ -1871,7 +1410,7 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
1871
1410
|
]));
|
|
1872
1411
|
} else if (is4(src, PgViewBase)) {
|
|
1873
1412
|
fields = src[ViewBaseConfig]?.selectedFields;
|
|
1874
|
-
} else if (is4(src,
|
|
1413
|
+
} else if (is4(src, SQL5)) {
|
|
1875
1414
|
fields = {};
|
|
1876
1415
|
} else {
|
|
1877
1416
|
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
@@ -2062,16 +1601,6 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
2062
1601
|
}
|
|
2063
1602
|
|
|
2064
1603
|
// 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
1604
|
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
2076
1605
|
function resolvePrepareCacheOption(option) {
|
|
2077
1606
|
if (!option)
|
|
@@ -2101,7 +1630,6 @@ class DuckDBDriver {
|
|
|
2101
1630
|
createSession(schema) {
|
|
2102
1631
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
2103
1632
|
logger: this.options.logger,
|
|
2104
|
-
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
2105
1633
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
2106
1634
|
prepareCache: this.options.prepareCache
|
|
2107
1635
|
});
|
|
@@ -2116,7 +1644,6 @@ function isConfigObject(data) {
|
|
|
2116
1644
|
}
|
|
2117
1645
|
function createFromClient(client, config = {}, instance) {
|
|
2118
1646
|
const dialect = new DuckDBDialect;
|
|
2119
|
-
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
2120
1647
|
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
2121
1648
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
2122
1649
|
let schema;
|
|
@@ -2130,7 +1657,6 @@ function createFromClient(client, config = {}, instance) {
|
|
|
2130
1657
|
}
|
|
2131
1658
|
const driver = new DuckDBDriver(client, dialect, {
|
|
2132
1659
|
logger,
|
|
2133
|
-
rewriteArrays: rewriteArraysMode,
|
|
2134
1660
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
2135
1661
|
prepareCache
|
|
2136
1662
|
});
|
|
@@ -3090,7 +2616,7 @@ function renderImports(imports, importBasePath) {
|
|
|
3090
2616
|
// src/olap.ts
|
|
3091
2617
|
import { is as is5 } from "drizzle-orm/entity";
|
|
3092
2618
|
import { sql as sql6 } from "drizzle-orm";
|
|
3093
|
-
import { SQL as
|
|
2619
|
+
import { SQL as SQL6 } from "drizzle-orm/sql/sql";
|
|
3094
2620
|
import { Column as Column3, getTableName as getTableName3 } from "drizzle-orm";
|
|
3095
2621
|
var countN = (expr = sql6`*`) => sql6`count(${expr})`.mapWith(Number);
|
|
3096
2622
|
var sumN = (expr) => sql6`sum(${expr})`.mapWith(Number);
|
|
@@ -3125,7 +2651,7 @@ var denseRank = (options) => sql6`dense_rank() ${overClause(options)}`.mapWith(N
|
|
|
3125
2651
|
var lag = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lag(${expr}, ${offset}) ${overClause(options)}`;
|
|
3126
2652
|
var lead = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lead(${expr}, ${offset}) ${overClause(options)}`;
|
|
3127
2653
|
function keyAlias(key, fallback) {
|
|
3128
|
-
if (is5(key,
|
|
2654
|
+
if (is5(key, SQL6.Aliased)) {
|
|
3129
2655
|
return key.fieldAlias ?? fallback;
|
|
3130
2656
|
}
|
|
3131
2657
|
if (is5(key, Column3)) {
|
|
@@ -3196,6 +2722,17 @@ class OlapBuilder {
|
|
|
3196
2722
|
}
|
|
3197
2723
|
}
|
|
3198
2724
|
var olap = (db) => new OlapBuilder(db);
|
|
2725
|
+
// src/operators.ts
|
|
2726
|
+
import { sql as sql7 } from "drizzle-orm";
|
|
2727
|
+
function arrayHasAll(column, values) {
|
|
2728
|
+
return sql7`array_has_all(${column}, ${values})`;
|
|
2729
|
+
}
|
|
2730
|
+
function arrayHasAny(column, values) {
|
|
2731
|
+
return sql7`array_has_any(${column}, ${values})`;
|
|
2732
|
+
}
|
|
2733
|
+
function arrayContainedBy(column, values) {
|
|
2734
|
+
return sql7`array_has_all(${values}, ${column})`;
|
|
2735
|
+
}
|
|
3199
2736
|
export {
|
|
3200
2737
|
wrapperToNodeApiValue,
|
|
3201
2738
|
wrapTimestamp,
|
|
@@ -3210,7 +2747,6 @@ export {
|
|
|
3210
2747
|
sumDistinctN,
|
|
3211
2748
|
splitTopLevel,
|
|
3212
2749
|
rowNumber,
|
|
3213
|
-
resolveRewriteArraysOption,
|
|
3214
2750
|
resolvePrepareCacheOption,
|
|
3215
2751
|
resolvePoolSize,
|
|
3216
2752
|
rank,
|
|
@@ -3257,6 +2793,9 @@ export {
|
|
|
3257
2793
|
buildListLiteral,
|
|
3258
2794
|
buildDefault,
|
|
3259
2795
|
avgN,
|
|
2796
|
+
arrayHasAny,
|
|
2797
|
+
arrayHasAll,
|
|
2798
|
+
arrayContainedBy,
|
|
3260
2799
|
anyValue,
|
|
3261
2800
|
POOL_PRESETS,
|
|
3262
2801
|
OlapBuilder,
|