@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.
@@ -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 && original[pos] !== '"') {
192
- if (original[pos] === '"' && original[pos + 1] === '"') {
193
- pos += 2;
194
- continue;
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
- 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 ")) {
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 offset = 0;
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 && scrubbedOnClause[lhsStartPos] !== '"') {
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 + offset) + clauseResult + result.slice(join.onEnd + offset);
503
- offset += clauseResult.length - originalOnClause.length;
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) {