@leonardovida-md/drizzle-neo-duckdb 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/dialect.d.ts +3 -1
- package/dist/driver.d.ts +1 -3
- package/dist/duckdb-introspect.mjs +355 -553
- package/dist/helpers.mjs +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +374 -556
- package/dist/operators.d.ts +8 -0
- package/dist/options.d.ts +0 -3
- package/dist/session.d.ts +2 -5
- package/dist/sql/ast-transformer.d.ts +15 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +5 -0
- package/dist/utils.d.ts +0 -1
- package/package.json +6 -2
- package/src/columns.ts +6 -1
- package/src/dialect.ts +20 -0
- package/src/driver.ts +0 -8
- package/src/index.ts +1 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +0 -15
- package/src/session.ts +10 -96
- package/src/sql/ast-transformer.ts +68 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +278 -0
- package/src/utils.ts +0 -1
- package/dist/sql/query-rewriters.d.ts +0 -14
- package/src/sql/query-rewriters.ts +0 -708
package/dist/index.mjs
CHANGED
|
@@ -15,489 +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 && original[pos] !== '"') {
|
|
184
|
-
if (original[pos] === '"' && original[pos + 1] === '"') {
|
|
185
|
-
pos += 2;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
pos++;
|
|
189
|
-
}
|
|
190
|
-
if (pos >= original.length) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
name: original.slice(start + 1, pos),
|
|
195
|
-
end: pos + 1
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function findMainFromClause(scrubbed) {
|
|
199
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
200
|
-
let searchStart = 0;
|
|
201
|
-
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
202
|
-
if (withMatch) {
|
|
203
|
-
let depth = 0;
|
|
204
|
-
let pos = withMatch.index + withMatch[0].length;
|
|
205
|
-
while (pos < scrubbed.length) {
|
|
206
|
-
const char = scrubbed[pos];
|
|
207
|
-
if (char === "(") {
|
|
208
|
-
depth++;
|
|
209
|
-
} else if (char === ")") {
|
|
210
|
-
depth--;
|
|
211
|
-
} else if (depth === 0) {
|
|
212
|
-
const remaining = lowerScrubbed.slice(pos);
|
|
213
|
-
if (/^\s*select\s+/i.test(remaining)) {
|
|
214
|
-
searchStart = pos;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
pos++;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const fromPattern = /\bfrom\s+/gi;
|
|
222
|
-
fromPattern.lastIndex = searchStart;
|
|
223
|
-
const fromMatch = fromPattern.exec(lowerScrubbed);
|
|
224
|
-
return fromMatch ? fromMatch.index + fromMatch[0].length : -1;
|
|
225
|
-
}
|
|
226
|
-
function parseTableSources(original, scrubbed) {
|
|
227
|
-
const sources = [];
|
|
228
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
229
|
-
const fromPos = findMainFromClause(scrubbed);
|
|
230
|
-
if (fromPos < 0) {
|
|
231
|
-
return sources;
|
|
232
|
-
}
|
|
233
|
-
const fromTable = parseTableRef(original, scrubbed, fromPos);
|
|
234
|
-
if (fromTable) {
|
|
235
|
-
sources.push(fromTable);
|
|
236
|
-
}
|
|
237
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+/gi;
|
|
238
|
-
joinPattern.lastIndex = fromPos;
|
|
239
|
-
let joinMatch;
|
|
240
|
-
while ((joinMatch = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
241
|
-
const tableStart = joinMatch.index + joinMatch[0].length;
|
|
242
|
-
const joinTable = parseTableRef(original, scrubbed, tableStart);
|
|
243
|
-
if (joinTable) {
|
|
244
|
-
sources.push(joinTable);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return sources;
|
|
248
|
-
}
|
|
249
|
-
function parseTableRef(original, scrubbed, start) {
|
|
250
|
-
let pos = start;
|
|
251
|
-
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
252
|
-
pos++;
|
|
253
|
-
}
|
|
254
|
-
if (original[pos] !== '"') {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
const nameStart = pos;
|
|
258
|
-
const firstIdent = extractQuotedIdentifier(original, pos);
|
|
259
|
-
if (!firstIdent) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
let name = firstIdent.name;
|
|
263
|
-
pos = firstIdent.end;
|
|
264
|
-
let afterName = pos;
|
|
265
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
266
|
-
afterName++;
|
|
267
|
-
}
|
|
268
|
-
if (scrubbed[afterName] === ".") {
|
|
269
|
-
afterName++;
|
|
270
|
-
while (afterName < scrubbed.length && isWhitespace(scrubbed[afterName])) {
|
|
271
|
-
afterName++;
|
|
272
|
-
}
|
|
273
|
-
if (original[afterName] === '"') {
|
|
274
|
-
const tableIdent = extractQuotedIdentifier(original, afterName);
|
|
275
|
-
if (tableIdent) {
|
|
276
|
-
name = tableIdent.name;
|
|
277
|
-
pos = tableIdent.end;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
let alias;
|
|
282
|
-
let aliasPos = pos;
|
|
283
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
284
|
-
aliasPos++;
|
|
285
|
-
}
|
|
286
|
-
const afterTable = scrubbed.slice(aliasPos).toLowerCase();
|
|
287
|
-
if (afterTable.startsWith("as ")) {
|
|
288
|
-
aliasPos += 3;
|
|
289
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
290
|
-
aliasPos++;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (original[aliasPos] === '"' && !afterTable.startsWith("on ") && !afterTable.startsWith("left ") && !afterTable.startsWith("right ") && !afterTable.startsWith("inner ") && !afterTable.startsWith("full ") && !afterTable.startsWith("cross ") && !afterTable.startsWith("join ") && !afterTable.startsWith("where ") && !afterTable.startsWith("group ") && !afterTable.startsWith("order ") && !afterTable.startsWith("limit ") && !afterTable.startsWith("as ")) {
|
|
294
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
295
|
-
if (aliasIdent) {
|
|
296
|
-
alias = aliasIdent.name;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return {
|
|
300
|
-
name,
|
|
301
|
-
alias,
|
|
302
|
-
position: nameStart
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function findJoinClauses(original, scrubbed, sources) {
|
|
306
|
-
const clauses = [];
|
|
307
|
-
const lowerScrubbed = scrubbed.toLowerCase();
|
|
308
|
-
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
|
|
309
|
-
let match;
|
|
310
|
-
let sourceIndex = 1;
|
|
311
|
-
while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
312
|
-
const joinType = (match[1] || "").trim().toLowerCase();
|
|
313
|
-
const joinKeywordEnd = match.index + (match[1] || "").length + "join".length;
|
|
314
|
-
let tableStart = joinKeywordEnd;
|
|
315
|
-
while (tableStart < original.length && isWhitespace(original[tableStart])) {
|
|
316
|
-
tableStart++;
|
|
317
|
-
}
|
|
318
|
-
const tableIdent = extractQuotedIdentifier(original, tableStart);
|
|
319
|
-
if (!tableIdent)
|
|
320
|
-
continue;
|
|
321
|
-
let tableName = tableIdent.name;
|
|
322
|
-
let afterTable = tableIdent.end;
|
|
323
|
-
let checkPos = afterTable;
|
|
324
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
325
|
-
checkPos++;
|
|
326
|
-
}
|
|
327
|
-
if (scrubbed[checkPos] === ".") {
|
|
328
|
-
checkPos++;
|
|
329
|
-
while (checkPos < scrubbed.length && isWhitespace(scrubbed[checkPos])) {
|
|
330
|
-
checkPos++;
|
|
331
|
-
}
|
|
332
|
-
const realTableIdent = extractQuotedIdentifier(original, checkPos);
|
|
333
|
-
if (realTableIdent) {
|
|
334
|
-
tableName = realTableIdent.name;
|
|
335
|
-
afterTable = realTableIdent.end;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
let tableAlias;
|
|
339
|
-
let aliasPos = afterTable;
|
|
340
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
341
|
-
aliasPos++;
|
|
342
|
-
}
|
|
343
|
-
const afterTableStr = scrubbed.slice(aliasPos).toLowerCase();
|
|
344
|
-
if (afterTableStr.startsWith("as ")) {
|
|
345
|
-
aliasPos += 3;
|
|
346
|
-
while (aliasPos < scrubbed.length && isWhitespace(scrubbed[aliasPos])) {
|
|
347
|
-
aliasPos++;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (original[aliasPos] === '"' && !afterTableStr.startsWith("on ")) {
|
|
351
|
-
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
352
|
-
if (aliasIdent) {
|
|
353
|
-
tableAlias = aliasIdent.name;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
const onStart = match.index + match[0].length;
|
|
357
|
-
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;
|
|
358
|
-
const remaining = lowerScrubbed.slice(onStart);
|
|
359
|
-
const endMatch = endPattern.exec(remaining);
|
|
360
|
-
const onEnd = endMatch ? onStart + endMatch.index : scrubbed.length;
|
|
361
|
-
let leftSource = "";
|
|
362
|
-
if (sourceIndex > 0 && sourceIndex <= sources.length) {
|
|
363
|
-
const prev = sources[sourceIndex - 1];
|
|
364
|
-
leftSource = prev?.alias || prev?.name || "";
|
|
365
|
-
}
|
|
366
|
-
const rightSource = tableAlias || tableName;
|
|
367
|
-
clauses.push({
|
|
368
|
-
joinType,
|
|
369
|
-
tableName,
|
|
370
|
-
tableAlias,
|
|
371
|
-
onStart,
|
|
372
|
-
onEnd,
|
|
373
|
-
leftSource,
|
|
374
|
-
rightSource
|
|
375
|
-
});
|
|
376
|
-
sourceIndex++;
|
|
377
|
-
}
|
|
378
|
-
return clauses;
|
|
379
|
-
}
|
|
380
|
-
function qualifyJoinColumns(query) {
|
|
381
|
-
const lowerQuery = query.toLowerCase();
|
|
382
|
-
if (!lowerQuery.includes("join")) {
|
|
383
|
-
return query;
|
|
384
|
-
}
|
|
385
|
-
const scrubbed = scrubForRewrite(query);
|
|
386
|
-
const sources = parseTableSources(query, scrubbed);
|
|
387
|
-
if (sources.length < 2) {
|
|
388
|
-
return query;
|
|
389
|
-
}
|
|
390
|
-
const joinClauses = findJoinClauses(query, scrubbed, sources);
|
|
391
|
-
if (joinClauses.length === 0) {
|
|
392
|
-
return query;
|
|
393
|
-
}
|
|
394
|
-
let result = query;
|
|
395
|
-
let offset = 0;
|
|
396
|
-
for (const join of joinClauses) {
|
|
397
|
-
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
398
|
-
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
399
|
-
let clauseResult = originalOnClause;
|
|
400
|
-
let clauseOffset = 0;
|
|
401
|
-
let eqPos = -1;
|
|
402
|
-
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
403
|
-
let lhsEnd = eqPos - 1;
|
|
404
|
-
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
405
|
-
lhsEnd--;
|
|
406
|
-
}
|
|
407
|
-
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
408
|
-
continue;
|
|
409
|
-
let lhsStartPos = lhsEnd - 1;
|
|
410
|
-
while (lhsStartPos >= 0 && scrubbedOnClause[lhsStartPos] !== '"') {
|
|
411
|
-
lhsStartPos--;
|
|
412
|
-
}
|
|
413
|
-
if (lhsStartPos < 0)
|
|
414
|
-
continue;
|
|
415
|
-
const lhsIsQualified = lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === ".";
|
|
416
|
-
let rhsStartPos = eqPos + 1;
|
|
417
|
-
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
418
|
-
rhsStartPos++;
|
|
419
|
-
}
|
|
420
|
-
const rhsChar = originalOnClause[rhsStartPos];
|
|
421
|
-
const rhsIsParam = rhsChar === "$";
|
|
422
|
-
const rhsIsStringLiteral = rhsChar === "'";
|
|
423
|
-
const rhsIsColumn = rhsChar === '"';
|
|
424
|
-
if (!rhsIsParam && !rhsIsStringLiteral && !rhsIsColumn)
|
|
425
|
-
continue;
|
|
426
|
-
const rhsIsQualified = !rhsIsColumn || rhsStartPos > 0 && scrubbedOnClause[rhsStartPos - 1] === ".";
|
|
427
|
-
if (lhsIsQualified || rhsIsQualified)
|
|
428
|
-
continue;
|
|
429
|
-
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
430
|
-
if (!lhsIdent)
|
|
431
|
-
continue;
|
|
432
|
-
let rhsIdent = null;
|
|
433
|
-
let rhsValue = "";
|
|
434
|
-
let rhsEnd = rhsStartPos;
|
|
435
|
-
if (rhsIsParam) {
|
|
436
|
-
let paramEnd = rhsStartPos + 1;
|
|
437
|
-
while (paramEnd < originalOnClause.length && /\d/.test(originalOnClause[paramEnd])) {
|
|
438
|
-
paramEnd++;
|
|
439
|
-
}
|
|
440
|
-
rhsValue = originalOnClause.slice(rhsStartPos, paramEnd);
|
|
441
|
-
rhsEnd = paramEnd;
|
|
442
|
-
} else if (rhsIsStringLiteral) {
|
|
443
|
-
let literalEnd = rhsStartPos + 1;
|
|
444
|
-
while (literalEnd < originalOnClause.length) {
|
|
445
|
-
if (originalOnClause[literalEnd] === "'") {
|
|
446
|
-
if (originalOnClause[literalEnd + 1] === "'") {
|
|
447
|
-
literalEnd += 2;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
literalEnd++;
|
|
453
|
-
}
|
|
454
|
-
rhsValue = originalOnClause.slice(rhsStartPos, literalEnd + 1);
|
|
455
|
-
rhsEnd = literalEnd + 1;
|
|
456
|
-
} else if (rhsIsColumn) {
|
|
457
|
-
rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
458
|
-
if (rhsIdent) {
|
|
459
|
-
if (rhsIdent.end < scrubbedOnClause.length && scrubbedOnClause[rhsIdent.end] === ".") {
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
rhsValue = `"${rhsIdent.name}"`;
|
|
463
|
-
rhsEnd = rhsIdent.end;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
if (!rhsValue)
|
|
467
|
-
continue;
|
|
468
|
-
if (!rhsIsColumn || !rhsIdent || lhsIdent.name !== rhsIdent.name) {
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
const lhsOriginal = `"${lhsIdent.name}"`;
|
|
472
|
-
let newLhs = lhsOriginal;
|
|
473
|
-
let newRhs = rhsValue;
|
|
474
|
-
if (!lhsIsQualified && join.leftSource) {
|
|
475
|
-
newLhs = `"${join.leftSource}"."${lhsIdent.name}"`;
|
|
476
|
-
}
|
|
477
|
-
if (!rhsIsQualified && rhsIsColumn && rhsIdent && join.rightSource) {
|
|
478
|
-
newRhs = `"${join.rightSource}"."${rhsIdent.name}"`;
|
|
479
|
-
}
|
|
480
|
-
if (newLhs !== lhsOriginal || newRhs !== rhsValue) {
|
|
481
|
-
const opStart = lhsIdent.end;
|
|
482
|
-
let opEnd = opStart;
|
|
483
|
-
while (opEnd < rhsEnd && (isWhitespace(originalOnClause[opEnd]) || originalOnClause[opEnd] === "=")) {
|
|
484
|
-
opEnd++;
|
|
485
|
-
}
|
|
486
|
-
const operator = originalOnClause.slice(opStart, opEnd);
|
|
487
|
-
const newExpr = `${newLhs}${operator}${newRhs}`;
|
|
488
|
-
const oldExprLength = rhsEnd - lhsStartPos;
|
|
489
|
-
clauseResult = clauseResult.slice(0, lhsStartPos + clauseOffset) + newExpr + clauseResult.slice(lhsStartPos + oldExprLength + clauseOffset);
|
|
490
|
-
clauseOffset += newExpr.length - oldExprLength;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (clauseResult !== originalOnClause) {
|
|
494
|
-
result = result.slice(0, join.onStart + offset) + clauseResult + result.slice(join.onEnd + offset);
|
|
495
|
-
offset += clauseResult.length - originalOnClause.length;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return result;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
18
|
// src/sql/result-mapper.ts
|
|
502
19
|
import {
|
|
503
20
|
Column,
|
|
@@ -1133,24 +650,6 @@ function isSavepointSyntaxError(error) {
|
|
|
1133
650
|
}
|
|
1134
651
|
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
1135
652
|
}
|
|
1136
|
-
function rewriteQuery(mode, query) {
|
|
1137
|
-
if (mode === "never") {
|
|
1138
|
-
return { sql: query, rewritten: false };
|
|
1139
|
-
}
|
|
1140
|
-
let result = query;
|
|
1141
|
-
let wasRewritten = false;
|
|
1142
|
-
const arrayRewritten = adaptArrayOperators(result);
|
|
1143
|
-
if (arrayRewritten !== result) {
|
|
1144
|
-
result = arrayRewritten;
|
|
1145
|
-
wasRewritten = true;
|
|
1146
|
-
}
|
|
1147
|
-
const joinQualified = qualifyJoinColumns(result);
|
|
1148
|
-
if (joinQualified !== result) {
|
|
1149
|
-
result = joinQualified;
|
|
1150
|
-
wasRewritten = true;
|
|
1151
|
-
}
|
|
1152
|
-
return { sql: result, rewritten: wasRewritten };
|
|
1153
|
-
}
|
|
1154
653
|
|
|
1155
654
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
1156
655
|
client;
|
|
@@ -1161,12 +660,11 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1161
660
|
fields;
|
|
1162
661
|
_isResponseInArrayMode;
|
|
1163
662
|
customResultMapper;
|
|
1164
|
-
rewriteArraysMode;
|
|
1165
663
|
rejectStringArrayLiterals;
|
|
1166
664
|
prepareCache;
|
|
1167
665
|
warnOnStringArrayLiteral;
|
|
1168
666
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
1169
|
-
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper,
|
|
667
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
1170
668
|
super({ sql: queryString, params });
|
|
1171
669
|
this.client = client;
|
|
1172
670
|
this.dialect = dialect;
|
|
@@ -1176,7 +674,6 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1176
674
|
this.fields = fields;
|
|
1177
675
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
1178
676
|
this.customResultMapper = customResultMapper;
|
|
1179
|
-
this.rewriteArraysMode = rewriteArraysMode;
|
|
1180
677
|
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
1181
678
|
this.prepareCache = prepareCache;
|
|
1182
679
|
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
@@ -1187,20 +684,16 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
1187
684
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1188
685
|
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
1189
686
|
});
|
|
1190
|
-
|
|
1191
|
-
if (didRewrite) {
|
|
1192
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
1193
|
-
}
|
|
1194
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
687
|
+
this.logger.logQuery(this.queryString, params);
|
|
1195
688
|
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
1196
689
|
if (fields) {
|
|
1197
|
-
const { rows: rows2 } = await executeArraysOnClient(this.client,
|
|
690
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params, { prepareCache: this.prepareCache });
|
|
1198
691
|
if (rows2.length === 0) {
|
|
1199
692
|
return [];
|
|
1200
693
|
}
|
|
1201
694
|
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
1202
695
|
}
|
|
1203
|
-
const rows = await executeOnClient(this.client,
|
|
696
|
+
const rows = await executeOnClient(this.client, this.queryString, params, {
|
|
1204
697
|
prepareCache: this.prepareCache
|
|
1205
698
|
});
|
|
1206
699
|
return rows;
|
|
@@ -1220,7 +713,6 @@ class DuckDBSession extends PgSession {
|
|
|
1220
713
|
static [entityKind] = "DuckDBSession";
|
|
1221
714
|
dialect;
|
|
1222
715
|
logger;
|
|
1223
|
-
rewriteArraysMode;
|
|
1224
716
|
rejectStringArrayLiterals;
|
|
1225
717
|
prepareCache;
|
|
1226
718
|
hasWarnedArrayLiteral = false;
|
|
@@ -1232,17 +724,15 @@ class DuckDBSession extends PgSession {
|
|
|
1232
724
|
this.options = options;
|
|
1233
725
|
this.dialect = dialect;
|
|
1234
726
|
this.logger = options.logger ?? new NoopLogger;
|
|
1235
|
-
this.rewriteArraysMode = options.rewriteArrays ?? "auto";
|
|
1236
727
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
1237
728
|
this.prepareCache = options.prepareCache;
|
|
1238
729
|
this.options = {
|
|
1239
730
|
...options,
|
|
1240
|
-
rewriteArrays: this.rewriteArraysMode,
|
|
1241
731
|
prepareCache: this.prepareCache
|
|
1242
732
|
};
|
|
1243
733
|
}
|
|
1244
734
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
1245
|
-
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);
|
|
1246
736
|
}
|
|
1247
737
|
execute(query) {
|
|
1248
738
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1302,12 +792,8 @@ query: ${query}`, []);
|
|
|
1302
792
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1303
793
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1304
794
|
});
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1308
|
-
}
|
|
1309
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1310
|
-
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
795
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
796
|
+
return executeInBatches(this.client, builtQuery.sql, params, options);
|
|
1311
797
|
}
|
|
1312
798
|
executeBatchesRaw(query, options = {}) {
|
|
1313
799
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1317,12 +803,8 @@ query: ${query}`, []);
|
|
|
1317
803
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1318
804
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1319
805
|
});
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1323
|
-
}
|
|
1324
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1325
|
-
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
806
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
807
|
+
return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
|
|
1326
808
|
}
|
|
1327
809
|
async executeArrow(query) {
|
|
1328
810
|
this.dialect.resetPgJsonFlag();
|
|
@@ -1332,12 +814,8 @@ query: ${query}`, []);
|
|
|
1332
814
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
1333
815
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
1334
816
|
});
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
1338
|
-
}
|
|
1339
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
1340
|
-
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
817
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
818
|
+
return executeArrowOnClient(this.client, builtQuery.sql, params);
|
|
1341
819
|
}
|
|
1342
820
|
markRollbackOnly() {
|
|
1343
821
|
this.rollbackOnly = true;
|
|
@@ -1439,6 +917,335 @@ import {
|
|
|
1439
917
|
import {
|
|
1440
918
|
sql as sql2
|
|
1441
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
|
|
1442
1249
|
class DuckDBDialect extends PgDialect {
|
|
1443
1250
|
static [entityKind2] = "DuckDBPgDialect";
|
|
1444
1251
|
hasPgJsonColumn = false;
|
|
@@ -1515,6 +1322,14 @@ class DuckDBDialect extends PgDialect {
|
|
|
1515
1322
|
return "none";
|
|
1516
1323
|
}
|
|
1517
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
|
+
}
|
|
1518
1333
|
}
|
|
1519
1334
|
|
|
1520
1335
|
// src/select-builder.ts
|
|
@@ -1525,10 +1340,10 @@ import {
|
|
|
1525
1340
|
} from "drizzle-orm/pg-core/query-builders";
|
|
1526
1341
|
import { Subquery, ViewBaseConfig } from "drizzle-orm";
|
|
1527
1342
|
import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
1528
|
-
import { SQL as
|
|
1343
|
+
import { SQL as SQL5 } from "drizzle-orm/sql/sql";
|
|
1529
1344
|
|
|
1530
1345
|
// src/sql/selection.ts
|
|
1531
|
-
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";
|
|
1532
1347
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
1533
1348
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
1534
1349
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -1538,16 +1353,16 @@ function mapEntries(obj, prefix, fullJoin = false) {
|
|
|
1538
1353
|
sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
|
|
1539
1354
|
];
|
|
1540
1355
|
}
|
|
1541
|
-
if (fullJoin && is3(value,
|
|
1356
|
+
if (fullJoin && is3(value, SQL4)) {
|
|
1542
1357
|
const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
|
|
1543
1358
|
const tableName = col?.table && getTableName2(col?.table);
|
|
1544
1359
|
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
1545
1360
|
}
|
|
1546
|
-
if (is3(value,
|
|
1547
|
-
const aliased = is3(value,
|
|
1361
|
+
if (is3(value, SQL4) || is3(value, Column2)) {
|
|
1362
|
+
const aliased = is3(value, SQL4) ? value : sql3`${value}`.mapWith(value);
|
|
1548
1363
|
return [key, aliased.as(qualified)];
|
|
1549
1364
|
}
|
|
1550
|
-
if (is3(value,
|
|
1365
|
+
if (is3(value, SQL4.Aliased)) {
|
|
1551
1366
|
return [key, value];
|
|
1552
1367
|
}
|
|
1553
1368
|
if (typeof value === "object" && value !== null) {
|
|
@@ -1595,7 +1410,7 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
1595
1410
|
]));
|
|
1596
1411
|
} else if (is4(src, PgViewBase)) {
|
|
1597
1412
|
fields = src[ViewBaseConfig]?.selectedFields;
|
|
1598
|
-
} else if (is4(src,
|
|
1413
|
+
} else if (is4(src, SQL5)) {
|
|
1599
1414
|
fields = {};
|
|
1600
1415
|
} else {
|
|
1601
1416
|
fields = aliasFields(getTableColumns(src), !isPartialSelect);
|
|
@@ -1786,16 +1601,6 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
1786
1601
|
}
|
|
1787
1602
|
|
|
1788
1603
|
// src/options.ts
|
|
1789
|
-
var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
|
|
1790
|
-
function resolveRewriteArraysOption(value) {
|
|
1791
|
-
if (value === undefined)
|
|
1792
|
-
return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
1793
|
-
if (value === true)
|
|
1794
|
-
return "auto";
|
|
1795
|
-
if (value === false)
|
|
1796
|
-
return "never";
|
|
1797
|
-
return value;
|
|
1798
|
-
}
|
|
1799
1604
|
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
1800
1605
|
function resolvePrepareCacheOption(option) {
|
|
1801
1606
|
if (!option)
|
|
@@ -1825,7 +1630,6 @@ class DuckDBDriver {
|
|
|
1825
1630
|
createSession(schema) {
|
|
1826
1631
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
1827
1632
|
logger: this.options.logger,
|
|
1828
|
-
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
1829
1633
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
1830
1634
|
prepareCache: this.options.prepareCache
|
|
1831
1635
|
});
|
|
@@ -1840,7 +1644,6 @@ function isConfigObject(data) {
|
|
|
1840
1644
|
}
|
|
1841
1645
|
function createFromClient(client, config = {}, instance) {
|
|
1842
1646
|
const dialect = new DuckDBDialect;
|
|
1843
|
-
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
1844
1647
|
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
1845
1648
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
1846
1649
|
let schema;
|
|
@@ -1854,7 +1657,6 @@ function createFromClient(client, config = {}, instance) {
|
|
|
1854
1657
|
}
|
|
1855
1658
|
const driver = new DuckDBDriver(client, dialect, {
|
|
1856
1659
|
logger,
|
|
1857
|
-
rewriteArrays: rewriteArraysMode,
|
|
1858
1660
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
1859
1661
|
prepareCache
|
|
1860
1662
|
});
|
|
@@ -2062,6 +1864,9 @@ var duckDbMap = (name, valueType) => customType({
|
|
|
2062
1864
|
return `MAP (STRING, ${valueType})`;
|
|
2063
1865
|
},
|
|
2064
1866
|
toDriver(value) {
|
|
1867
|
+
if (Object.keys(value).length === 0) {
|
|
1868
|
+
return buildMapLiteral(value, valueType);
|
|
1869
|
+
}
|
|
2065
1870
|
return wrapMap(value, valueType);
|
|
2066
1871
|
},
|
|
2067
1872
|
fromDriver(value) {
|
|
@@ -2811,7 +2616,7 @@ function renderImports(imports, importBasePath) {
|
|
|
2811
2616
|
// src/olap.ts
|
|
2812
2617
|
import { is as is5 } from "drizzle-orm/entity";
|
|
2813
2618
|
import { sql as sql6 } from "drizzle-orm";
|
|
2814
|
-
import { SQL as
|
|
2619
|
+
import { SQL as SQL6 } from "drizzle-orm/sql/sql";
|
|
2815
2620
|
import { Column as Column3, getTableName as getTableName3 } from "drizzle-orm";
|
|
2816
2621
|
var countN = (expr = sql6`*`) => sql6`count(${expr})`.mapWith(Number);
|
|
2817
2622
|
var sumN = (expr) => sql6`sum(${expr})`.mapWith(Number);
|
|
@@ -2846,7 +2651,7 @@ var denseRank = (options) => sql6`dense_rank() ${overClause(options)}`.mapWith(N
|
|
|
2846
2651
|
var lag = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lag(${expr}, ${offset}) ${overClause(options)}`;
|
|
2847
2652
|
var lead = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lead(${expr}, ${offset}) ${overClause(options)}`;
|
|
2848
2653
|
function keyAlias(key, fallback) {
|
|
2849
|
-
if (is5(key,
|
|
2654
|
+
if (is5(key, SQL6.Aliased)) {
|
|
2850
2655
|
return key.fieldAlias ?? fallback;
|
|
2851
2656
|
}
|
|
2852
2657
|
if (is5(key, Column3)) {
|
|
@@ -2917,6 +2722,17 @@ class OlapBuilder {
|
|
|
2917
2722
|
}
|
|
2918
2723
|
}
|
|
2919
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
|
+
}
|
|
2920
2736
|
export {
|
|
2921
2737
|
wrapperToNodeApiValue,
|
|
2922
2738
|
wrapTimestamp,
|
|
@@ -2931,7 +2747,6 @@ export {
|
|
|
2931
2747
|
sumDistinctN,
|
|
2932
2748
|
splitTopLevel,
|
|
2933
2749
|
rowNumber,
|
|
2934
|
-
resolveRewriteArraysOption,
|
|
2935
2750
|
resolvePrepareCacheOption,
|
|
2936
2751
|
resolvePoolSize,
|
|
2937
2752
|
rank,
|
|
@@ -2978,6 +2793,9 @@ export {
|
|
|
2978
2793
|
buildListLiteral,
|
|
2979
2794
|
buildDefault,
|
|
2980
2795
|
avgN,
|
|
2796
|
+
arrayHasAny,
|
|
2797
|
+
arrayHasAll,
|
|
2798
|
+
arrayContainedBy,
|
|
2981
2799
|
anyValue,
|
|
2982
2800
|
POOL_PRESETS,
|
|
2983
2801
|
OlapBuilder,
|