@leonardovida-md/drizzle-neo-duckdb 1.1.2 → 1.1.4
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/dist/duckdb-introspect.mjs +611 -2
- package/dist/helpers.mjs +10 -0
- package/dist/index.mjs +614 -2
- package/dist/sql/query-rewriters.d.ts +13 -0
- package/package.json +5 -2
- package/src/columns.ts +6 -1
- package/src/session.ts +22 -3
- package/src/sql/query-rewriters.ts +948 -0
package/dist/index.mjs
CHANGED
|
@@ -175,6 +175,604 @@ function adaptArrayOperators(query) {
|
|
|
175
175
|
}
|
|
176
176
|
return rewritten;
|
|
177
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
|
+
}
|
|
178
776
|
|
|
179
777
|
// src/sql/result-mapper.ts
|
|
180
778
|
import {
|
|
@@ -815,8 +1413,19 @@ function rewriteQuery(mode, query) {
|
|
|
815
1413
|
if (mode === "never") {
|
|
816
1414
|
return { sql: query, rewritten: false };
|
|
817
1415
|
}
|
|
818
|
-
|
|
819
|
-
|
|
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 };
|
|
820
1429
|
}
|
|
821
1430
|
|
|
822
1431
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
@@ -1729,6 +2338,9 @@ var duckDbMap = (name, valueType) => customType({
|
|
|
1729
2338
|
return `MAP (STRING, ${valueType})`;
|
|
1730
2339
|
},
|
|
1731
2340
|
toDriver(value) {
|
|
2341
|
+
if (Object.keys(value).length === 0) {
|
|
2342
|
+
return buildMapLiteral(value, valueType);
|
|
2343
|
+
}
|
|
1732
2344
|
return wrapMap(value, valueType);
|
|
1733
2345
|
},
|
|
1734
2346
|
fromDriver(value) {
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
export declare function scrubForRewrite(query: string): string;
|
|
2
2
|
export declare function adaptArrayOperators(query: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Qualifies unqualified column references in JOIN ON clauses, SELECT, WHERE,
|
|
5
|
+
* and ORDER BY clauses.
|
|
6
|
+
*
|
|
7
|
+
* Transforms patterns like:
|
|
8
|
+
* `select "col" from "a" left join "b" on "col" = "col" where "col" in (...)`
|
|
9
|
+
* To:
|
|
10
|
+
* `select "a"."col" from "a" left join "b" on "a"."col" = "b"."col" where "a"."col" in (...)`
|
|
11
|
+
*
|
|
12
|
+
* This fixes the issue where drizzle-orm generates unqualified column
|
|
13
|
+
* references when joining CTEs with eq().
|
|
14
|
+
*/
|
|
15
|
+
export declare function qualifyJoinColumns(query: string): string;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "./dist/index.mjs",
|
|
4
4
|
"main": "./dist/index.mjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.4",
|
|
7
7
|
"description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
@@ -77,5 +77,8 @@
|
|
|
77
77
|
"src/**/*.ts",
|
|
78
78
|
"dist/*.mjs",
|
|
79
79
|
"dist/**/*.d.ts"
|
|
80
|
-
]
|
|
80
|
+
],
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@duckdb/node-bindings-darwin-arm64": "^1.4.2-r.1"
|
|
83
|
+
}
|
|
81
84
|
}
|
package/src/columns.ts
CHANGED
|
@@ -237,7 +237,12 @@ export const duckDbMap = <TData extends Record<string, any>>(
|
|
|
237
237
|
dataType() {
|
|
238
238
|
return `MAP (STRING, ${valueType})`;
|
|
239
239
|
},
|
|
240
|
-
toDriver(value: TData)
|
|
240
|
+
toDriver(value: TData) {
|
|
241
|
+
// Use SQL literals for empty maps due to DuckDB type inference issues
|
|
242
|
+
// with mapValue() when there are no entries to infer types from
|
|
243
|
+
if (Object.keys(value).length === 0) {
|
|
244
|
+
return buildMapLiteral(value, valueType);
|
|
245
|
+
}
|
|
241
246
|
return wrapMap(value, valueType);
|
|
242
247
|
},
|
|
243
248
|
fromDriver(value: TData | MapValueWrapper): TData {
|