@leonardovida-md/drizzle-neo-duckdb 1.1.3 → 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 +287 -11
- package/dist/helpers.mjs +10 -0
- package/dist/index.mjs +290 -11
- package/dist/sql/query-rewriters.d.ts +4 -3
- package/package.json +5 -2
- package/src/columns.ts +6 -1
- package/src/sql/query-rewriters.ts +481 -28
|
@@ -188,10 +188,13 @@ function extractQuotedIdentifier(original, start) {
|
|
|
188
188
|
return null;
|
|
189
189
|
}
|
|
190
190
|
let pos = start + 1;
|
|
191
|
-
while (pos < original.length
|
|
192
|
-
if (original[pos] === '"'
|
|
193
|
-
pos
|
|
194
|
-
|
|
191
|
+
while (pos < original.length) {
|
|
192
|
+
if (original[pos] === '"') {
|
|
193
|
+
if (original[pos + 1] === '"') {
|
|
194
|
+
pos += 2;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
195
198
|
}
|
|
196
199
|
pos++;
|
|
197
200
|
}
|
|
@@ -259,6 +262,39 @@ function parseTableRef(original, scrubbed, start) {
|
|
|
259
262
|
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
260
263
|
pos++;
|
|
261
264
|
}
|
|
265
|
+
if (scrubbed[pos] === "(") {
|
|
266
|
+
const nameStart2 = pos;
|
|
267
|
+
let depth = 1;
|
|
268
|
+
pos++;
|
|
269
|
+
while (pos < scrubbed.length && depth > 0) {
|
|
270
|
+
if (scrubbed[pos] === "(")
|
|
271
|
+
depth++;
|
|
272
|
+
else if (scrubbed[pos] === ")")
|
|
273
|
+
depth--;
|
|
274
|
+
pos++;
|
|
275
|
+
}
|
|
276
|
+
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
277
|
+
pos++;
|
|
278
|
+
}
|
|
279
|
+
const afterSubquery = scrubbed.slice(pos).toLowerCase();
|
|
280
|
+
if (afterSubquery.startsWith("as ")) {
|
|
281
|
+
pos += 3;
|
|
282
|
+
while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
|
|
283
|
+
pos++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (original[pos] === '"') {
|
|
287
|
+
const aliasIdent = extractQuotedIdentifier(original, pos);
|
|
288
|
+
if (aliasIdent) {
|
|
289
|
+
return {
|
|
290
|
+
name: aliasIdent.name,
|
|
291
|
+
alias: aliasIdent.name,
|
|
292
|
+
position: nameStart2
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
262
298
|
if (original[pos] !== '"') {
|
|
263
299
|
return null;
|
|
264
300
|
}
|
|
@@ -298,7 +334,8 @@ function parseTableRef(original, scrubbed, start) {
|
|
|
298
334
|
aliasPos++;
|
|
299
335
|
}
|
|
300
336
|
}
|
|
301
|
-
|
|
337
|
+
const afterAlias = scrubbed.slice(aliasPos).toLowerCase();
|
|
338
|
+
if (original[aliasPos] === '"' && !afterAlias.startsWith("on ") && !afterAlias.startsWith("left ") && !afterAlias.startsWith("right ") && !afterAlias.startsWith("inner ") && !afterAlias.startsWith("full ") && !afterAlias.startsWith("cross ") && !afterAlias.startsWith("join ") && !afterAlias.startsWith("where ") && !afterAlias.startsWith("group ") && !afterAlias.startsWith("order ") && !afterAlias.startsWith("limit ")) {
|
|
302
339
|
const aliasIdent = extractQuotedIdentifier(original, aliasPos);
|
|
303
340
|
if (aliasIdent) {
|
|
304
341
|
alias = aliasIdent.name;
|
|
@@ -310,10 +347,11 @@ function parseTableRef(original, scrubbed, start) {
|
|
|
310
347
|
position: nameStart
|
|
311
348
|
};
|
|
312
349
|
}
|
|
313
|
-
function findJoinClauses(original, scrubbed, sources) {
|
|
350
|
+
function findJoinClauses(original, scrubbed, sources, fromPos) {
|
|
314
351
|
const clauses = [];
|
|
315
352
|
const lowerScrubbed = scrubbed.toLowerCase();
|
|
316
353
|
const joinPattern = /\b(left\s+|right\s+|inner\s+|full\s+|cross\s+)?join\s+"[^"]*"(\s*\.\s*"[^"]*")?(\s+as)?(\s+"[^"]*")?\s+on\s+/gi;
|
|
354
|
+
joinPattern.lastIndex = fromPos;
|
|
317
355
|
let match;
|
|
318
356
|
let sourceIndex = 1;
|
|
319
357
|
while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
|
|
@@ -385,22 +423,182 @@ function findJoinClauses(original, scrubbed, sources) {
|
|
|
385
423
|
}
|
|
386
424
|
return clauses;
|
|
387
425
|
}
|
|
426
|
+
function findMainSelectClause(scrubbed) {
|
|
427
|
+
const lowerScrubbed = scrubbed.toLowerCase();
|
|
428
|
+
let searchStart = 0;
|
|
429
|
+
const withMatch = /\bwith\s+/i.exec(lowerScrubbed);
|
|
430
|
+
if (withMatch) {
|
|
431
|
+
let depth2 = 0;
|
|
432
|
+
let pos2 = withMatch.index + withMatch[0].length;
|
|
433
|
+
while (pos2 < scrubbed.length) {
|
|
434
|
+
const char = scrubbed[pos2];
|
|
435
|
+
if (char === "(") {
|
|
436
|
+
depth2++;
|
|
437
|
+
} else if (char === ")") {
|
|
438
|
+
depth2--;
|
|
439
|
+
} else if (depth2 === 0) {
|
|
440
|
+
const remaining = lowerScrubbed.slice(pos2);
|
|
441
|
+
if (/^\s*select\s+/i.test(remaining)) {
|
|
442
|
+
searchStart = pos2;
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
pos2++;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const selectPattern = /\bselect\s+/gi;
|
|
450
|
+
selectPattern.lastIndex = searchStart;
|
|
451
|
+
const selectMatch = selectPattern.exec(lowerScrubbed);
|
|
452
|
+
if (!selectMatch)
|
|
453
|
+
return null;
|
|
454
|
+
const selectStart = selectMatch.index + selectMatch[0].length;
|
|
455
|
+
let depth = 0;
|
|
456
|
+
let pos = selectStart;
|
|
457
|
+
while (pos < scrubbed.length) {
|
|
458
|
+
const char = scrubbed[pos];
|
|
459
|
+
if (char === "(") {
|
|
460
|
+
depth++;
|
|
461
|
+
} else if (char === ")") {
|
|
462
|
+
depth--;
|
|
463
|
+
} else if (depth === 0) {
|
|
464
|
+
const remaining = lowerScrubbed.slice(pos);
|
|
465
|
+
if (/^\s*from\s+/i.test(remaining)) {
|
|
466
|
+
return { start: selectStart, end: pos };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
pos++;
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function findWhereClause(scrubbed, fromPos) {
|
|
474
|
+
const lowerScrubbed = scrubbed.toLowerCase();
|
|
475
|
+
let depth = 0;
|
|
476
|
+
let pos = fromPos;
|
|
477
|
+
let whereStart = -1;
|
|
478
|
+
while (pos < scrubbed.length) {
|
|
479
|
+
const char = scrubbed[pos];
|
|
480
|
+
if (char === "(") {
|
|
481
|
+
depth++;
|
|
482
|
+
} else if (char === ")") {
|
|
483
|
+
depth--;
|
|
484
|
+
} else if (depth === 0) {
|
|
485
|
+
const remaining = lowerScrubbed.slice(pos);
|
|
486
|
+
if (whereStart === -1 && /^\s*where\s+/i.test(remaining)) {
|
|
487
|
+
const match = /^\s*where\s+/i.exec(remaining);
|
|
488
|
+
if (match) {
|
|
489
|
+
whereStart = pos + match[0].length;
|
|
490
|
+
}
|
|
491
|
+
} else if (whereStart !== -1 && /^\s*(group\s+by|order\s+by|limit|having|union|intersect|except|$)/i.test(remaining)) {
|
|
492
|
+
return { start: whereStart, end: pos };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
pos++;
|
|
496
|
+
}
|
|
497
|
+
if (whereStart !== -1) {
|
|
498
|
+
return { start: whereStart, end: scrubbed.length };
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
function findOrderByClause(scrubbed, fromPos) {
|
|
503
|
+
const lowerScrubbed = scrubbed.toLowerCase();
|
|
504
|
+
let depth = 0;
|
|
505
|
+
let pos = fromPos;
|
|
506
|
+
let orderStart = -1;
|
|
507
|
+
while (pos < scrubbed.length) {
|
|
508
|
+
const char = scrubbed[pos];
|
|
509
|
+
if (char === "(") {
|
|
510
|
+
depth++;
|
|
511
|
+
} else if (char === ")") {
|
|
512
|
+
depth--;
|
|
513
|
+
} else if (depth === 0) {
|
|
514
|
+
const remaining = lowerScrubbed.slice(pos);
|
|
515
|
+
if (orderStart === -1 && /^\s*order\s+by\s+/i.test(remaining)) {
|
|
516
|
+
const match = /^\s*order\s+by\s+/i.exec(remaining);
|
|
517
|
+
if (match) {
|
|
518
|
+
orderStart = pos + match[0].length;
|
|
519
|
+
}
|
|
520
|
+
} else if (orderStart !== -1 && /^\s*(limit|offset|fetch|for\s+update|$)/i.test(remaining)) {
|
|
521
|
+
return { start: orderStart, end: pos };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
pos++;
|
|
525
|
+
}
|
|
526
|
+
if (orderStart !== -1) {
|
|
527
|
+
return { start: orderStart, end: scrubbed.length };
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
function qualifyClauseColumnsSelective(original, scrubbed, clauseStart, clauseEnd, defaultSource, ambiguousColumns) {
|
|
532
|
+
const clauseOriginal = original.slice(clauseStart, clauseEnd);
|
|
533
|
+
const clauseScrubbed = scrubbed.slice(clauseStart, clauseEnd);
|
|
534
|
+
let result = clauseOriginal;
|
|
535
|
+
let offset = 0;
|
|
536
|
+
let pos = 0;
|
|
537
|
+
while (pos < clauseScrubbed.length) {
|
|
538
|
+
const quotePos = clauseScrubbed.indexOf('"', pos);
|
|
539
|
+
if (quotePos === -1)
|
|
540
|
+
break;
|
|
541
|
+
if (quotePos > 0 && clauseScrubbed[quotePos - 1] === ".") {
|
|
542
|
+
const ident2 = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
543
|
+
pos = ident2 ? ident2.end : quotePos + 1;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const ident = extractQuotedIdentifier(clauseOriginal, quotePos);
|
|
547
|
+
if (!ident) {
|
|
548
|
+
pos = quotePos + 1;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (ident.end < clauseScrubbed.length && clauseScrubbed[ident.end] === ".") {
|
|
552
|
+
pos = ident.end + 1;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (!ambiguousColumns.has(ident.name)) {
|
|
556
|
+
pos = ident.end;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
let afterIdent = ident.end;
|
|
560
|
+
while (afterIdent < clauseScrubbed.length && isWhitespace(clauseScrubbed[afterIdent])) {
|
|
561
|
+
afterIdent++;
|
|
562
|
+
}
|
|
563
|
+
if (clauseScrubbed[afterIdent] === "(") {
|
|
564
|
+
pos = ident.end;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const beforeQuote = clauseScrubbed.slice(0, quotePos).toLowerCase();
|
|
568
|
+
if (/\bas\s*$/i.test(beforeQuote)) {
|
|
569
|
+
pos = ident.end;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
const qualified = `"${defaultSource}"."${ident.name}"`;
|
|
573
|
+
const oldLength = ident.end - quotePos;
|
|
574
|
+
result = result.slice(0, quotePos + offset) + qualified + result.slice(quotePos + oldLength + offset);
|
|
575
|
+
offset += qualified.length - oldLength;
|
|
576
|
+
pos = ident.end;
|
|
577
|
+
}
|
|
578
|
+
return { result, offset };
|
|
579
|
+
}
|
|
388
580
|
function qualifyJoinColumns(query) {
|
|
389
581
|
const lowerQuery = query.toLowerCase();
|
|
390
582
|
if (!lowerQuery.includes("join")) {
|
|
391
583
|
return query;
|
|
392
584
|
}
|
|
393
585
|
const scrubbed = scrubForRewrite(query);
|
|
586
|
+
const fromPos = findMainFromClause(scrubbed);
|
|
587
|
+
if (fromPos < 0) {
|
|
588
|
+
return query;
|
|
589
|
+
}
|
|
394
590
|
const sources = parseTableSources(query, scrubbed);
|
|
395
591
|
if (sources.length < 2) {
|
|
396
592
|
return query;
|
|
397
593
|
}
|
|
398
|
-
const joinClauses = findJoinClauses(query, scrubbed, sources);
|
|
594
|
+
const joinClauses = findJoinClauses(query, scrubbed, sources, fromPos);
|
|
399
595
|
if (joinClauses.length === 0) {
|
|
400
596
|
return query;
|
|
401
597
|
}
|
|
598
|
+
const firstSource = sources[0];
|
|
599
|
+
const defaultQualifier = firstSource.alias || firstSource.name;
|
|
402
600
|
let result = query;
|
|
403
|
-
let
|
|
601
|
+
let totalOffset = 0;
|
|
404
602
|
for (const join of joinClauses) {
|
|
405
603
|
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
406
604
|
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
@@ -415,7 +613,14 @@ function qualifyJoinColumns(query) {
|
|
|
415
613
|
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
416
614
|
continue;
|
|
417
615
|
let lhsStartPos = lhsEnd - 1;
|
|
418
|
-
while (lhsStartPos >= 0
|
|
616
|
+
while (lhsStartPos >= 0) {
|
|
617
|
+
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
618
|
+
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
619
|
+
lhsStartPos -= 2;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
419
624
|
lhsStartPos--;
|
|
420
625
|
}
|
|
421
626
|
if (lhsStartPos < 0)
|
|
@@ -499,8 +704,79 @@ function qualifyJoinColumns(query) {
|
|
|
499
704
|
}
|
|
500
705
|
}
|
|
501
706
|
if (clauseResult !== originalOnClause) {
|
|
502
|
-
result = result.slice(0, join.onStart +
|
|
503
|
-
|
|
707
|
+
result = result.slice(0, join.onStart + totalOffset) + clauseResult + result.slice(join.onEnd + totalOffset);
|
|
708
|
+
totalOffset += clauseResult.length - originalOnClause.length;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const ambiguousColumns = new Set;
|
|
712
|
+
for (const join of joinClauses) {
|
|
713
|
+
const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
|
|
714
|
+
const originalOnClause = query.slice(join.onStart, join.onEnd);
|
|
715
|
+
let eqPos = -1;
|
|
716
|
+
while ((eqPos = scrubbedOnClause.indexOf("=", eqPos + 1)) !== -1) {
|
|
717
|
+
let lhsEnd = eqPos - 1;
|
|
718
|
+
while (lhsEnd >= 0 && isWhitespace(scrubbedOnClause[lhsEnd])) {
|
|
719
|
+
lhsEnd--;
|
|
720
|
+
}
|
|
721
|
+
if (scrubbedOnClause[lhsEnd] !== '"')
|
|
722
|
+
continue;
|
|
723
|
+
let lhsStartPos = lhsEnd - 1;
|
|
724
|
+
while (lhsStartPos >= 0) {
|
|
725
|
+
if (scrubbedOnClause[lhsStartPos] === '"') {
|
|
726
|
+
if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
|
|
727
|
+
lhsStartPos -= 2;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
lhsStartPos--;
|
|
733
|
+
}
|
|
734
|
+
if (lhsStartPos < 0)
|
|
735
|
+
continue;
|
|
736
|
+
let rhsStartPos = eqPos + 1;
|
|
737
|
+
while (rhsStartPos < scrubbedOnClause.length && isWhitespace(scrubbedOnClause[rhsStartPos])) {
|
|
738
|
+
rhsStartPos++;
|
|
739
|
+
}
|
|
740
|
+
if (originalOnClause[rhsStartPos] !== '"')
|
|
741
|
+
continue;
|
|
742
|
+
const lhsIdent = extractQuotedIdentifier(originalOnClause, lhsStartPos);
|
|
743
|
+
const rhsIdent = extractQuotedIdentifier(originalOnClause, rhsStartPos);
|
|
744
|
+
if (lhsIdent && rhsIdent && lhsIdent.name === rhsIdent.name) {
|
|
745
|
+
ambiguousColumns.add(lhsIdent.name);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (ambiguousColumns.size === 0) {
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
const updatedScrubbed = scrubForRewrite(result);
|
|
753
|
+
const selectClause = findMainSelectClause(updatedScrubbed);
|
|
754
|
+
if (selectClause) {
|
|
755
|
+
const { result: selectResult, offset: selectOffset } = qualifyClauseColumnsSelective(result, updatedScrubbed, selectClause.start, selectClause.end, defaultQualifier, ambiguousColumns);
|
|
756
|
+
if (selectOffset !== 0) {
|
|
757
|
+
result = result.slice(0, selectClause.start) + selectResult + result.slice(selectClause.end);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const scrubbed2 = scrubForRewrite(result);
|
|
761
|
+
const fromPos2 = findMainFromClause(scrubbed2);
|
|
762
|
+
if (fromPos2 >= 0) {
|
|
763
|
+
const whereClause = findWhereClause(scrubbed2, fromPos2);
|
|
764
|
+
if (whereClause) {
|
|
765
|
+
const { result: whereResult, offset: whereOffset } = qualifyClauseColumnsSelective(result, scrubbed2, whereClause.start, whereClause.end, defaultQualifier, ambiguousColumns);
|
|
766
|
+
if (whereOffset !== 0) {
|
|
767
|
+
result = result.slice(0, whereClause.start) + whereResult + result.slice(whereClause.end);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const scrubbed3 = scrubForRewrite(result);
|
|
772
|
+
const fromPos3 = findMainFromClause(scrubbed3);
|
|
773
|
+
if (fromPos3 >= 0) {
|
|
774
|
+
const orderByClause = findOrderByClause(scrubbed3, fromPos3);
|
|
775
|
+
if (orderByClause) {
|
|
776
|
+
const { result: orderResult, offset: orderOffset } = qualifyClauseColumnsSelective(result, scrubbed3, orderByClause.start, orderByClause.end, defaultQualifier, ambiguousColumns);
|
|
777
|
+
if (orderOffset !== 0) {
|
|
778
|
+
result = result.slice(0, orderByClause.start) + orderResult + result.slice(orderByClause.end);
|
|
779
|
+
}
|
|
504
780
|
}
|
|
505
781
|
}
|
|
506
782
|
return result;
|
package/dist/helpers.mjs
CHANGED
|
@@ -115,6 +115,13 @@ function buildStructLiteral(value, schema) {
|
|
|
115
115
|
});
|
|
116
116
|
return sql`struct_pack(${sql.join(parts, sql.raw(", "))})`;
|
|
117
117
|
}
|
|
118
|
+
function buildMapLiteral(value, valueType) {
|
|
119
|
+
const keys = Object.keys(value);
|
|
120
|
+
const vals = Object.values(value);
|
|
121
|
+
const keyList = buildListLiteral(keys, "TEXT");
|
|
122
|
+
const valList = buildListLiteral(vals, valueType?.endsWith("[]") ? valueType.slice(0, -2) : valueType);
|
|
123
|
+
return sql`map(${keyList}, ${valList})`;
|
|
124
|
+
}
|
|
118
125
|
var duckDbList = (name, elementType) => customType({
|
|
119
126
|
dataType() {
|
|
120
127
|
return `${elementType}[]`;
|
|
@@ -160,6 +167,9 @@ var duckDbMap = (name, valueType) => customType({
|
|
|
160
167
|
return `MAP (STRING, ${valueType})`;
|
|
161
168
|
},
|
|
162
169
|
toDriver(value) {
|
|
170
|
+
if (Object.keys(value).length === 0) {
|
|
171
|
+
return buildMapLiteral(value, valueType);
|
|
172
|
+
}
|
|
163
173
|
return wrapMap(value, valueType);
|
|
164
174
|
},
|
|
165
175
|
fromDriver(value) {
|