@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/index.mjs CHANGED
@@ -180,10 +180,13 @@ function extractQuotedIdentifier(original, start) {
180
180
  return null;
181
181
  }
182
182
  let pos = start + 1;
183
- while (pos < original.length && original[pos] !== '"') {
184
- if (original[pos] === '"' && original[pos + 1] === '"') {
185
- pos += 2;
186
- continue;
183
+ while (pos < original.length) {
184
+ if (original[pos] === '"') {
185
+ if (original[pos + 1] === '"') {
186
+ pos += 2;
187
+ continue;
188
+ }
189
+ break;
187
190
  }
188
191
  pos++;
189
192
  }
@@ -251,6 +254,39 @@ function parseTableRef(original, scrubbed, start) {
251
254
  while (pos < scrubbed.length && isWhitespace(scrubbed[pos])) {
252
255
  pos++;
253
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
+ }
254
290
  if (original[pos] !== '"') {
255
291
  return null;
256
292
  }
@@ -290,7 +326,8 @@ function parseTableRef(original, scrubbed, start) {
290
326
  aliasPos++;
291
327
  }
292
328
  }
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 ")) {
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 ")) {
294
331
  const aliasIdent = extractQuotedIdentifier(original, aliasPos);
295
332
  if (aliasIdent) {
296
333
  alias = aliasIdent.name;
@@ -302,10 +339,11 @@ function parseTableRef(original, scrubbed, start) {
302
339
  position: nameStart
303
340
  };
304
341
  }
305
- function findJoinClauses(original, scrubbed, sources) {
342
+ function findJoinClauses(original, scrubbed, sources, fromPos) {
306
343
  const clauses = [];
307
344
  const lowerScrubbed = scrubbed.toLowerCase();
308
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;
309
347
  let match;
310
348
  let sourceIndex = 1;
311
349
  while ((match = joinPattern.exec(lowerScrubbed)) !== null) {
@@ -377,22 +415,182 @@ function findJoinClauses(original, scrubbed, sources) {
377
415
  }
378
416
  return clauses;
379
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
+ }
380
572
  function qualifyJoinColumns(query) {
381
573
  const lowerQuery = query.toLowerCase();
382
574
  if (!lowerQuery.includes("join")) {
383
575
  return query;
384
576
  }
385
577
  const scrubbed = scrubForRewrite(query);
578
+ const fromPos = findMainFromClause(scrubbed);
579
+ if (fromPos < 0) {
580
+ return query;
581
+ }
386
582
  const sources = parseTableSources(query, scrubbed);
387
583
  if (sources.length < 2) {
388
584
  return query;
389
585
  }
390
- const joinClauses = findJoinClauses(query, scrubbed, sources);
586
+ const joinClauses = findJoinClauses(query, scrubbed, sources, fromPos);
391
587
  if (joinClauses.length === 0) {
392
588
  return query;
393
589
  }
590
+ const firstSource = sources[0];
591
+ const defaultQualifier = firstSource.alias || firstSource.name;
394
592
  let result = query;
395
- let offset = 0;
593
+ let totalOffset = 0;
396
594
  for (const join of joinClauses) {
397
595
  const scrubbedOnClause = scrubbed.slice(join.onStart, join.onEnd);
398
596
  const originalOnClause = query.slice(join.onStart, join.onEnd);
@@ -407,7 +605,14 @@ function qualifyJoinColumns(query) {
407
605
  if (scrubbedOnClause[lhsEnd] !== '"')
408
606
  continue;
409
607
  let lhsStartPos = lhsEnd - 1;
410
- while (lhsStartPos >= 0 && scrubbedOnClause[lhsStartPos] !== '"') {
608
+ while (lhsStartPos >= 0) {
609
+ if (scrubbedOnClause[lhsStartPos] === '"') {
610
+ if (lhsStartPos > 0 && scrubbedOnClause[lhsStartPos - 1] === '"') {
611
+ lhsStartPos -= 2;
612
+ continue;
613
+ }
614
+ break;
615
+ }
411
616
  lhsStartPos--;
412
617
  }
413
618
  if (lhsStartPos < 0)
@@ -491,8 +696,79 @@ function qualifyJoinColumns(query) {
491
696
  }
492
697
  }
493
698
  if (clauseResult !== originalOnClause) {
494
- result = result.slice(0, join.onStart + offset) + clauseResult + result.slice(join.onEnd + offset);
495
- offset += clauseResult.length - originalOnClause.length;
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
+ }
496
772
  }
497
773
  }
498
774
  return result;
@@ -2062,6 +2338,9 @@ var duckDbMap = (name, valueType) => customType({
2062
2338
  return `MAP (STRING, ${valueType})`;
2063
2339
  },
2064
2340
  toDriver(value) {
2341
+ if (Object.keys(value).length === 0) {
2342
+ return buildMapLiteral(value, valueType);
2343
+ }
2065
2344
  return wrapMap(value, valueType);
2066
2345
  },
2067
2346
  fromDriver(value) {
@@ -1,12 +1,13 @@
1
1
  export declare function scrubForRewrite(query: string): string;
2
2
  export declare function adaptArrayOperators(query: string): string;
3
3
  /**
4
- * Qualifies unqualified column references in JOIN ON clauses.
4
+ * Qualifies unqualified column references in JOIN ON clauses, SELECT, WHERE,
5
+ * and ORDER BY clauses.
5
6
  *
6
7
  * Transforms patterns like:
7
- * `left join "b" on "col" = "col"`
8
+ * `select "col" from "a" left join "b" on "col" = "col" where "col" in (...)`
8
9
  * To:
9
- * `left join "b" on "a"."col" = "b"."col"`
10
+ * `select "a"."col" from "a" left join "b" on "a"."col" = "b"."col" where "a"."col" in (...)`
10
11
  *
11
12
  * This fixes the issue where drizzle-orm generates unqualified column
12
13
  * references when joining CTEs with eq().
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.3",
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): MapValueWrapper {
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 {