@routier/core 0.5.0 → 0.7.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 +1 -1
- package/dist/assertions/index.cjs +19 -8
- package/dist/assertions/index.cjs.map +1 -1
- package/dist/assertions/index.d.ts +5 -1
- package/dist/assertions/index.js +21 -9
- package/dist/assertions/index.js.map +1 -1
- package/dist/collections/MemoryDataCollection.d.ts +10 -0
- package/dist/collections/index.cjs +29 -4
- package/dist/collections/index.cjs.map +1 -1
- package/dist/collections/index.js +29 -4
- package/dist/collections/index.js.map +1 -1
- package/dist/expressions/callSource.d.ts +41 -0
- package/dist/expressions/evaluate.d.ts +3 -0
- package/dist/expressions/fold.d.ts +7 -0
- package/dist/expressions/index.cjs +1754 -233
- package/dist/expressions/index.cjs.map +1 -1
- package/dist/expressions/index.d.ts +2 -0
- package/dist/expressions/index.js +1765 -234
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/types.d.ts +45 -26
- package/dist/expressions/utils.d.ts +19 -1
- package/dist/index.cjs +2429 -363
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2769 -682
- package/dist/index.js.map +1 -1
- package/dist/performance/index.cjs +6 -4
- package/dist/performance/index.cjs.map +1 -1
- package/dist/performance/index.js +6 -4
- package/dist/performance/index.js.map +1 -1
- package/dist/pipeline/index.cjs +6 -4
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.js +6 -4
- package/dist/pipeline/index.js.map +1 -1
- package/dist/plugins/index.cjs +2323 -316
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +2328 -311
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +38 -10
- package/dist/plugins/query/describeFilter.d.ts +83 -0
- package/dist/plugins/query/explain.d.ts +71 -9
- package/dist/plugins/query/index.d.ts +1 -0
- package/dist/plugins/query/join.d.ts +4 -1
- package/dist/plugins/query/types.d.ts +36 -4
- package/dist/plugins/resultShape.d.ts +35 -0
- package/dist/schema/PropertyInfo.d.ts +0 -1
- package/dist/schema/index.cjs +7 -14
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.js +7 -14
- package/dist/schema/index.js.map +1 -1
- package/dist/transfer/ChunkEncoder.d.ts +60 -0
- package/dist/transfer/decoder.d.ts +29 -0
- package/dist/transfer/fillers.d.ts +36 -0
- package/dist/transfer/index.cjs +873 -0
- package/dist/transfer/index.cjs.map +1 -0
- package/dist/transfer/index.d.ts +47 -0
- package/dist/transfer/index.js +872 -0
- package/dist/transfer/index.js.map +1 -0
- package/dist/transfer/plan.d.ts +94 -0
- package/dist/transfer/types.d.ts +138 -0
- package/dist/utilities/index.cjs +242 -49
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +242 -49
- package/dist/utilities/index.js.map +1 -1
- package/package.json +9 -1
package/dist/plugins/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
6
6
|
assertIsArray: () => (assertIsArray),
|
|
7
7
|
assertIsNotNull: () => (assertIsNotNull),
|
|
8
8
|
assertString: () => (assertString),
|
|
9
|
+
isCallExpression: () => (isCallExpression),
|
|
9
10
|
isComparatorExpression: () => (isComparatorExpression),
|
|
10
11
|
isOperatorExpression: () => (isOperatorExpression),
|
|
11
12
|
isPropertyExpression: () => (isPropertyExpression),
|
|
@@ -81,6 +82,11 @@ function isObjectWithType(value) {
|
|
|
81
82
|
*/ function isValueExpression(value) {
|
|
82
83
|
return isObjectWithType(value) && value.type === "value";
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Type guard: narrows `value` to `CallExpression` when it is an object with `type === "call"`.
|
|
87
|
+
*/ function isCallExpression(value) {
|
|
88
|
+
return isObjectWithType(value) && value.type === "call";
|
|
89
|
+
}
|
|
84
90
|
/**
|
|
85
91
|
* Type guard: narrows `value` to `EmptyExpression` when it is an object with `type === "empty"`.
|
|
86
92
|
*/ function isEmptyExpression(value) {
|
|
@@ -401,45 +407,374 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
401
407
|
}
|
|
402
408
|
|
|
403
409
|
|
|
410
|
+
},
|
|
411
|
+
429(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
412
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
413
|
+
a: () => (renderCallAsJs)
|
|
414
|
+
});
|
|
415
|
+
const CALL_SOURCE = {
|
|
416
|
+
"to-lower-case": {
|
|
417
|
+
form: "method",
|
|
418
|
+
name: "toLowerCase"
|
|
419
|
+
},
|
|
420
|
+
"to-upper-case": {
|
|
421
|
+
form: "method",
|
|
422
|
+
name: "toUpperCase"
|
|
423
|
+
},
|
|
424
|
+
"length": {
|
|
425
|
+
form: "property",
|
|
426
|
+
name: "length"
|
|
427
|
+
},
|
|
428
|
+
"trim": {
|
|
429
|
+
form: "method",
|
|
430
|
+
name: "trim"
|
|
431
|
+
},
|
|
432
|
+
"trim-start": {
|
|
433
|
+
form: "method",
|
|
434
|
+
name: "trimStart"
|
|
435
|
+
},
|
|
436
|
+
"trim-end": {
|
|
437
|
+
form: "method",
|
|
438
|
+
name: "trimEnd"
|
|
439
|
+
},
|
|
440
|
+
"index-of": {
|
|
441
|
+
form: "method",
|
|
442
|
+
name: "indexOf"
|
|
443
|
+
},
|
|
444
|
+
"substring": {
|
|
445
|
+
form: "method",
|
|
446
|
+
name: "substring"
|
|
447
|
+
},
|
|
448
|
+
"concat": {
|
|
449
|
+
form: "method",
|
|
450
|
+
name: "concat"
|
|
451
|
+
},
|
|
452
|
+
"replace": {
|
|
453
|
+
form: "method",
|
|
454
|
+
name: "replace"
|
|
455
|
+
},
|
|
456
|
+
"replace-all": {
|
|
457
|
+
form: "method",
|
|
458
|
+
name: "replaceAll"
|
|
459
|
+
},
|
|
460
|
+
"absolute": {
|
|
461
|
+
form: "function",
|
|
462
|
+
name: "Math.abs"
|
|
463
|
+
},
|
|
464
|
+
"floor": {
|
|
465
|
+
form: "function",
|
|
466
|
+
name: "Math.floor"
|
|
467
|
+
},
|
|
468
|
+
"ceiling": {
|
|
469
|
+
form: "function",
|
|
470
|
+
name: "Math.ceil"
|
|
471
|
+
},
|
|
472
|
+
"round": {
|
|
473
|
+
form: "function",
|
|
474
|
+
name: "Math.round"
|
|
475
|
+
},
|
|
476
|
+
"sign": {
|
|
477
|
+
form: "function",
|
|
478
|
+
name: "Math.sign"
|
|
479
|
+
},
|
|
480
|
+
"square-root": {
|
|
481
|
+
form: "function",
|
|
482
|
+
name: "Math.sqrt"
|
|
483
|
+
},
|
|
484
|
+
"add": {
|
|
485
|
+
form: "operator",
|
|
486
|
+
symbol: "+"
|
|
487
|
+
},
|
|
488
|
+
"subtract": {
|
|
489
|
+
form: "operator",
|
|
490
|
+
symbol: "-"
|
|
491
|
+
},
|
|
492
|
+
"multiply": {
|
|
493
|
+
form: "operator",
|
|
494
|
+
symbol: "*"
|
|
495
|
+
},
|
|
496
|
+
"divide": {
|
|
497
|
+
form: "operator",
|
|
498
|
+
symbol: "/"
|
|
499
|
+
},
|
|
500
|
+
"modulo": {
|
|
501
|
+
form: "operator",
|
|
502
|
+
symbol: "%"
|
|
503
|
+
},
|
|
504
|
+
"utc-year": {
|
|
505
|
+
form: "method",
|
|
506
|
+
name: "getUTCFullYear"
|
|
507
|
+
},
|
|
508
|
+
"utc-month": {
|
|
509
|
+
form: "method",
|
|
510
|
+
name: "getUTCMonth"
|
|
511
|
+
},
|
|
512
|
+
"utc-day-of-month": {
|
|
513
|
+
form: "method",
|
|
514
|
+
name: "getUTCDate"
|
|
515
|
+
},
|
|
516
|
+
"utc-day-of-week": {
|
|
517
|
+
form: "method",
|
|
518
|
+
name: "getUTCDay"
|
|
519
|
+
},
|
|
520
|
+
"utc-hour": {
|
|
521
|
+
form: "method",
|
|
522
|
+
name: "getUTCHours"
|
|
523
|
+
},
|
|
524
|
+
"utc-minute": {
|
|
525
|
+
form: "method",
|
|
526
|
+
name: "getUTCMinutes"
|
|
527
|
+
},
|
|
528
|
+
"utc-second": {
|
|
529
|
+
form: "method",
|
|
530
|
+
name: "getUTCSeconds"
|
|
531
|
+
},
|
|
532
|
+
"utc-millisecond": {
|
|
533
|
+
form: "method",
|
|
534
|
+
name: "getUTCMilliseconds"
|
|
535
|
+
},
|
|
536
|
+
"epoch-ms": {
|
|
537
|
+
form: "method",
|
|
538
|
+
name: "getTime"
|
|
539
|
+
},
|
|
540
|
+
"to-string": {
|
|
541
|
+
form: "function",
|
|
542
|
+
name: "String"
|
|
543
|
+
},
|
|
544
|
+
"to-number": {
|
|
545
|
+
form: "function",
|
|
546
|
+
name: "Number"
|
|
547
|
+
},
|
|
548
|
+
"to-boolean": {
|
|
549
|
+
form: "function",
|
|
550
|
+
name: "Boolean"
|
|
551
|
+
},
|
|
552
|
+
"type-of": {
|
|
553
|
+
form: "prefix",
|
|
554
|
+
keyword: "typeof"
|
|
555
|
+
},
|
|
556
|
+
"some": {
|
|
557
|
+
form: "method",
|
|
558
|
+
name: "some"
|
|
559
|
+
},
|
|
560
|
+
"every": {
|
|
561
|
+
form: "method",
|
|
562
|
+
name: "every"
|
|
563
|
+
},
|
|
564
|
+
// `Math.pow(a, b)` parses to the same call; `**` is the shorter of the two spellings
|
|
565
|
+
"power": {
|
|
566
|
+
form: "operator",
|
|
567
|
+
symbol: "**"
|
|
568
|
+
},
|
|
569
|
+
"bit-and": {
|
|
570
|
+
form: "operator",
|
|
571
|
+
symbol: "&"
|
|
572
|
+
},
|
|
573
|
+
"bit-or": {
|
|
574
|
+
form: "operator",
|
|
575
|
+
symbol: "|"
|
|
576
|
+
},
|
|
577
|
+
"bit-xor": {
|
|
578
|
+
form: "operator",
|
|
579
|
+
symbol: "^"
|
|
580
|
+
},
|
|
581
|
+
"shift-left": {
|
|
582
|
+
form: "operator",
|
|
583
|
+
symbol: "<<"
|
|
584
|
+
},
|
|
585
|
+
"shift-right": {
|
|
586
|
+
form: "operator",
|
|
587
|
+
symbol: ">>"
|
|
588
|
+
},
|
|
589
|
+
"shift-right-unsigned": {
|
|
590
|
+
form: "operator",
|
|
591
|
+
symbol: ">>>"
|
|
592
|
+
},
|
|
593
|
+
"bit-not": {
|
|
594
|
+
form: "prefix",
|
|
595
|
+
keyword: "~"
|
|
596
|
+
},
|
|
597
|
+
"coalesce": {
|
|
598
|
+
form: "operator",
|
|
599
|
+
symbol: "??"
|
|
600
|
+
},
|
|
601
|
+
"conditional": {
|
|
602
|
+
form: "conditional"
|
|
603
|
+
},
|
|
604
|
+
"matches": {
|
|
605
|
+
form: "regex-test"
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* A call rendered as the JavaScript that produced it, from operand and argument text already
|
|
610
|
+
* rendered by the caller.
|
|
611
|
+
*
|
|
612
|
+
* Takes strings so one implementation serves a live tree and a serialized one.
|
|
613
|
+
*/ /**
|
|
614
|
+
* Thunked because rendering a side can record a parameter, and `regex-test` emits its argument
|
|
615
|
+
* before its operand — so the two orders have to agree.
|
|
616
|
+
*/ const renderCallAsJs = (call, renderOperand, renderArgs)=>{
|
|
617
|
+
const source = CALL_SOURCE[call];
|
|
618
|
+
if (source == null) {
|
|
619
|
+
const operand = renderOperand();
|
|
620
|
+
return `${operand}.${call}(${renderArgs().join(", ")})`;
|
|
621
|
+
}
|
|
622
|
+
if (source.form === "property") {
|
|
623
|
+
return `${renderOperand()}.${source.name}`;
|
|
624
|
+
}
|
|
625
|
+
if (source.form === "regex-test") {
|
|
626
|
+
const pattern = renderArgs()[0] ?? "?";
|
|
627
|
+
return `${pattern}.test(${renderOperand()})`;
|
|
628
|
+
}
|
|
629
|
+
if (source.form === "method") {
|
|
630
|
+
const operand = renderOperand();
|
|
631
|
+
return `${operand}.${source.name}(${renderArgs().join(", ")})`;
|
|
632
|
+
}
|
|
633
|
+
if (source.form === "function") {
|
|
634
|
+
const operand = renderOperand();
|
|
635
|
+
return `${source.name}(${[
|
|
636
|
+
operand,
|
|
637
|
+
...renderArgs()
|
|
638
|
+
].join(", ")})`;
|
|
639
|
+
}
|
|
640
|
+
if (source.form === "prefix") {
|
|
641
|
+
// `~x`, not `~ x` — a bitwise complement is written tight, unlike `typeof`
|
|
642
|
+
const operand = renderOperand();
|
|
643
|
+
return source.keyword === "~" ? `${source.keyword}${operand}` : `${source.keyword} ${operand}`;
|
|
644
|
+
}
|
|
645
|
+
if (source.form === "conditional") {
|
|
646
|
+
const operand = renderOperand();
|
|
647
|
+
const args = renderArgs();
|
|
648
|
+
return `${operand} ? ${args[0] ?? "?"} : ${args[1] ?? "?"}`;
|
|
649
|
+
}
|
|
650
|
+
const operand = renderOperand();
|
|
651
|
+
return `${[
|
|
652
|
+
operand,
|
|
653
|
+
...renderArgs()
|
|
654
|
+
].join(` ${source.symbol} `)}`;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
|
|
404
658
|
},
|
|
405
659
|
379(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
406
660
|
__webpack_require__.d(__webpack_exports__, {
|
|
661
|
+
Vv: () => (operandValue),
|
|
662
|
+
_3: () => (evaluate),
|
|
663
|
+
gm: () => (UNRESOLVED),
|
|
407
664
|
wS: () => (toStrictPredicate)
|
|
408
665
|
});
|
|
409
666
|
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
410
667
|
|
|
411
668
|
/** Reads a property or literal operand, or `UNRESOLVED` when the node is not one. */ const UNRESOLVED = Symbol("unresolved");
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
669
|
+
const ARITHMETIC = {
|
|
670
|
+
"add": (left, right)=>left + right,
|
|
671
|
+
"subtract": (left, right)=>left - right,
|
|
672
|
+
"multiply": (left, right)=>left * right,
|
|
673
|
+
"divide": (left, right)=>left / right,
|
|
674
|
+
"modulo": (left, right)=>left % right,
|
|
675
|
+
"power": (left, right)=>left ** right,
|
|
676
|
+
"bit-and": (left, right)=>left & right,
|
|
677
|
+
"bit-or": (left, right)=>left | right,
|
|
678
|
+
"bit-xor": (left, right)=>left ^ right,
|
|
679
|
+
"shift-left": (left, right)=>left << right,
|
|
680
|
+
"shift-right": (left, right)=>left >> right,
|
|
681
|
+
"shift-right-unsigned": (left, right)=>left >>> right
|
|
682
|
+
};
|
|
683
|
+
const applyCall = (call, value, args)=>{
|
|
684
|
+
// Above the guard: a template renders null as "null" in JavaScript, so these two are total.
|
|
685
|
+
if (call === "to-string") {
|
|
686
|
+
return String(value);
|
|
415
687
|
}
|
|
416
|
-
|
|
417
|
-
|
|
688
|
+
if (call === "concat") {
|
|
689
|
+
return [
|
|
690
|
+
value,
|
|
691
|
+
...args
|
|
692
|
+
].map(String).join("");
|
|
693
|
+
}
|
|
694
|
+
// A call applied to an absent value has no answer, and inventing one ("" for a missing string)
|
|
695
|
+
// is how a filter starts matching rows it should not.
|
|
418
696
|
if (value == null) {
|
|
419
697
|
return UNRESOLVED;
|
|
420
698
|
}
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
699
|
+
if (call === "to-lower-case" || call === "to-upper-case") {
|
|
700
|
+
if (typeof value !== "string") {
|
|
701
|
+
return UNRESOLVED;
|
|
702
|
+
}
|
|
703
|
+
const lower = call === "to-lower-case";
|
|
704
|
+
if (args.length === 0 || args[0] == null) {
|
|
705
|
+
return lower ? value.toLowerCase() : value.toUpperCase();
|
|
706
|
+
}
|
|
707
|
+
if (typeof args[0] !== "string") {
|
|
708
|
+
return UNRESOLVED;
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
// An explicit locale is deterministic; dropping it answers a different question in Turkish.
|
|
712
|
+
return lower ? value.toLocaleLowerCase(args[0]) : value.toLocaleUpperCase(args[0]);
|
|
713
|
+
} catch {
|
|
714
|
+
// An invalid language tag throws RangeError; no answer beats the host's default.
|
|
715
|
+
return UNRESOLVED;
|
|
716
|
+
}
|
|
426
717
|
}
|
|
427
|
-
if (
|
|
718
|
+
if (call === "length") {
|
|
428
719
|
return typeof value === "string" || Array.isArray(value) ? value.length : UNRESOLVED;
|
|
429
720
|
}
|
|
721
|
+
if (call === "bit-not") {
|
|
722
|
+
return typeof value === "number" ? ~value : UNRESOLVED;
|
|
723
|
+
}
|
|
724
|
+
if (call === "matches") {
|
|
725
|
+
if (typeof value !== "string" || !(args[0] instanceof RegExp)) {
|
|
726
|
+
return UNRESOLVED;
|
|
727
|
+
}
|
|
728
|
+
// `test` advances `lastIndex` on a global or sticky pattern, and the pattern is shared with
|
|
729
|
+
// the cached template, where a source evaluates fresh in JavaScript.
|
|
730
|
+
return args[0].global || args[0].sticky ? new RegExp(args[0].source, args[0].flags).test(value) : args[0].test(value);
|
|
731
|
+
}
|
|
732
|
+
const arithmetic = ARITHMETIC[call];
|
|
733
|
+
if (arithmetic != null) {
|
|
734
|
+
return typeof value === "number" && typeof args[0] === "number" ? arithmetic(value, args[0]) : UNRESOLVED;
|
|
735
|
+
}
|
|
430
736
|
return UNRESOLVED;
|
|
431
737
|
};
|
|
432
|
-
const
|
|
738
|
+
const operandValue = (expression, row)=>{
|
|
433
739
|
if (expression == null) {
|
|
434
740
|
return UNRESOLVED;
|
|
435
741
|
}
|
|
436
742
|
if ((0,_assertions__rspack_import_0.isValueExpression)(expression)) {
|
|
437
|
-
return
|
|
743
|
+
return expression.value;
|
|
438
744
|
}
|
|
439
745
|
if ((0,_assertions__rspack_import_0.isPropertyExpression)(expression)) {
|
|
440
746
|
// Through the PropertyInfo, so a nested path and a `from`-renamed segment resolve the same
|
|
441
747
|
// way every other consumer of the tree resolves them.
|
|
442
|
-
return
|
|
748
|
+
return expression.property.getValue(row);
|
|
749
|
+
}
|
|
750
|
+
if ((0,_assertions__rspack_import_0.isCallExpression)(expression)) {
|
|
751
|
+
/**
|
|
752
|
+
* `??` and `? :` are the two calls whose whole job is to answer when something is absent, so
|
|
753
|
+
* they run before the guard that refuses an absent operand.
|
|
754
|
+
*/ if (expression.call === "coalesce") {
|
|
755
|
+
const left = operandValue(expression.expression, row);
|
|
756
|
+
return left === UNRESOLVED || left == null ? operandValue(expression.arguments[0], row) : left;
|
|
757
|
+
}
|
|
758
|
+
if (expression.call === "conditional") {
|
|
759
|
+
const condition = evaluate(expression.expression, row);
|
|
760
|
+
if (condition === undefined) {
|
|
761
|
+
return UNRESOLVED;
|
|
762
|
+
}
|
|
763
|
+
return operandValue(expression.arguments[condition === true ? 0 : 1], row);
|
|
764
|
+
}
|
|
765
|
+
const inner = operandValue(expression.expression, row);
|
|
766
|
+
if (inner === UNRESOLVED) {
|
|
767
|
+
return UNRESOLVED;
|
|
768
|
+
}
|
|
769
|
+
const args = [];
|
|
770
|
+
for (const argument of expression.arguments){
|
|
771
|
+
const resolved = operandValue(argument, row);
|
|
772
|
+
if (resolved === UNRESOLVED) {
|
|
773
|
+
return UNRESOLVED;
|
|
774
|
+
}
|
|
775
|
+
args.push(resolved);
|
|
776
|
+
}
|
|
777
|
+
return applyCall(expression.call, inner, args);
|
|
443
778
|
}
|
|
444
779
|
return UNRESOLVED;
|
|
445
780
|
};
|
|
@@ -524,8 +859,8 @@ const evaluateComparator = (comparator, left, right, strict)=>{
|
|
|
524
859
|
return left === false && right === false ? false : undefined;
|
|
525
860
|
}
|
|
526
861
|
if ((0,_assertions__rspack_import_0.isComparatorExpression)(expression)) {
|
|
527
|
-
const left =
|
|
528
|
-
const right =
|
|
862
|
+
const left = operandValue(expression.left, row);
|
|
863
|
+
const right = operandValue(expression.right, row);
|
|
529
864
|
if (left === UNRESOLVED || right === UNRESOLVED) {
|
|
530
865
|
return undefined;
|
|
531
866
|
}
|
|
@@ -563,19 +898,129 @@ const evaluateComparator = (comparator, left, right, strict)=>{
|
|
|
563
898
|
};
|
|
564
899
|
|
|
565
900
|
|
|
901
|
+
},
|
|
902
|
+
43(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
903
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
904
|
+
F5: () => (foldConstantCalls)
|
|
905
|
+
});
|
|
906
|
+
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
907
|
+
/* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
|
|
908
|
+
/* import */ var _types__rspack_import_2 = __webpack_require__(27);
|
|
909
|
+
/* import */ var _utils__rspack_import_1 = __webpack_require__(63);
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
/** Calls fold may compute. Absent means a plugin declines it, so a new call is opt-in. */ const FOLDABLE = new Set([
|
|
915
|
+
"to-lower-case",
|
|
916
|
+
"to-upper-case",
|
|
917
|
+
"length",
|
|
918
|
+
"bit-not",
|
|
919
|
+
"matches",
|
|
920
|
+
"to-string",
|
|
921
|
+
"concat",
|
|
922
|
+
"add",
|
|
923
|
+
"subtract",
|
|
924
|
+
"multiply",
|
|
925
|
+
"divide",
|
|
926
|
+
"modulo",
|
|
927
|
+
"power",
|
|
928
|
+
"bit-and",
|
|
929
|
+
"bit-or",
|
|
930
|
+
"bit-xor",
|
|
931
|
+
"shift-left",
|
|
932
|
+
"shift-right",
|
|
933
|
+
"shift-right-unsigned",
|
|
934
|
+
"coalesce",
|
|
935
|
+
"conditional"
|
|
936
|
+
]);
|
|
937
|
+
/** `String(value)` on an object is the host's rendering — a Date carries its timezone. */ const COERCES_TO_TEXT = new Set([
|
|
938
|
+
"to-string",
|
|
939
|
+
"concat"
|
|
940
|
+
]);
|
|
941
|
+
const isFrozenPrimitive = (value)=>value == null || typeof value !== "object";
|
|
942
|
+
const readsAProperty = (expression)=>{
|
|
943
|
+
if ((0,_assertions__rspack_import_0.isPropertyExpression)(expression)) {
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
return (0,_utils__rspack_import_1/* .childrenOf */.LU)(expression).some(readsAProperty);
|
|
947
|
+
};
|
|
948
|
+
/** A `conditional` holds a condition where every other call holds a value. */ const isConstant = (call)=>{
|
|
949
|
+
if (!FOLDABLE.has(call.call) || !call.arguments.every(_assertions__rspack_import_0.isValueExpression)) {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
if (COERCES_TO_TEXT.has(call.call) && [
|
|
953
|
+
call.expression,
|
|
954
|
+
...call.arguments
|
|
955
|
+
].some((operand)=>(0,_assertions__rspack_import_0.isValueExpression)(operand) && !isFrozenPrimitive(operand.value))) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
return call.call === "conditional" ? !readsAProperty(call.expression) : (0,_assertions__rspack_import_0.isValueExpression)(call.expression);
|
|
959
|
+
};
|
|
960
|
+
/** Computes every call whose operand and arguments are all literals. Runs after `bindExpression`. */ const foldConstantCalls = (expression)=>{
|
|
961
|
+
if ((0,_assertions__rspack_import_0.isCallExpression)(expression)) {
|
|
962
|
+
const folded = new _types__rspack_import_2/* .CallExpression */.DG({
|
|
963
|
+
call: expression.call,
|
|
964
|
+
expression: foldConstantCalls(expression.expression),
|
|
965
|
+
arguments: expression.arguments.map(foldConstantCalls)
|
|
966
|
+
});
|
|
967
|
+
if (!isConstant(folded)) {
|
|
968
|
+
return folded;
|
|
969
|
+
}
|
|
970
|
+
const value = (0,_evaluate__rspack_import_3/* .operandValue */.Vv)(folded, {});
|
|
971
|
+
return value === _evaluate__rspack_import_3/* .UNRESOLVED */.gm ? folded : new _types__rspack_import_2/* .ValueExpression */.Ko({
|
|
972
|
+
value
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
if ((0,_assertions__rspack_import_0.isComparatorExpression)(expression)) {
|
|
976
|
+
return new _types__rspack_import_2/* .ComparatorExpression */.bQ({
|
|
977
|
+
comparator: expression.comparator,
|
|
978
|
+
negated: expression.negated,
|
|
979
|
+
strict: expression.strict,
|
|
980
|
+
left: expression.left == null ? undefined : foldConstantCalls(expression.left),
|
|
981
|
+
right: expression.right == null ? undefined : foldConstantCalls(expression.right)
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
if ((0,_assertions__rspack_import_0.isOperatorExpression)(expression)) {
|
|
985
|
+
return new _types__rspack_import_2/* .OperatorExpression */.fw({
|
|
986
|
+
operator: expression.operator,
|
|
987
|
+
left: expression.left == null ? undefined : foldConstantCalls(expression.left),
|
|
988
|
+
right: expression.right == null ? undefined : foldConstantCalls(expression.right)
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
return expression;
|
|
992
|
+
};
|
|
993
|
+
/** The value a literal operand binds as once the calls on it are computed. Throws if it cannot. */ const foldedOperandValue = (operand, calls)=>{
|
|
994
|
+
if (calls.length === 0) {
|
|
995
|
+
return operand.value;
|
|
996
|
+
}
|
|
997
|
+
// `peelCalls` returns calls innermost first, so the last one evaluates the whole chain.
|
|
998
|
+
const outermost = calls[calls.length - 1];
|
|
999
|
+
const value = readsAProperty(outermost) ? UNRESOLVED : operandValue(outermost, {});
|
|
1000
|
+
if (value === UNRESOLVED) {
|
|
1001
|
+
throw new Error(`'${calls.map((call)=>call.call).join("', '")}' cannot be computed on the literal ` + `'${String(operand.value)}'.`);
|
|
1002
|
+
}
|
|
1003
|
+
return value;
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
|
|
566
1007
|
},
|
|
567
1008
|
91(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
568
1009
|
__webpack_require__.d(__webpack_exports__, {
|
|
569
1010
|
MY: () => (toExpression)
|
|
570
1011
|
});
|
|
571
|
-
/* import */ var
|
|
1012
|
+
/* import */ var _utilities__rspack_import_4 = __webpack_require__(581);
|
|
572
1013
|
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
573
1014
|
/* import */ var _schema__rspack_import_2 = __webpack_require__(537);
|
|
1015
|
+
/* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
|
|
1016
|
+
/* import */ var _fold__rspack_import_5 = __webpack_require__(43);
|
|
574
1017
|
/* import */ var _types__rspack_import_1 = __webpack_require__(27);
|
|
575
1018
|
|
|
576
1019
|
|
|
577
1020
|
|
|
578
1021
|
|
|
1022
|
+
|
|
1023
|
+
|
|
579
1024
|
// Error message constants
|
|
580
1025
|
const ERROR_MESSAGES = {
|
|
581
1026
|
PROPERTY_NOT_FOUND: (path)=>`Error parsing query, could not find PropertyInfo for path: ${path}`,
|
|
@@ -621,8 +1066,13 @@ const converters = {
|
|
|
621
1066
|
};
|
|
622
1067
|
// Longest first so multi-character punctuation wins over its prefixes
|
|
623
1068
|
const MULTI_CHARACTER_PUNCTUATION = [
|
|
1069
|
+
">>>",
|
|
624
1070
|
"===",
|
|
625
1071
|
"!==",
|
|
1072
|
+
"**",
|
|
1073
|
+
"<<",
|
|
1074
|
+
">>",
|
|
1075
|
+
"??",
|
|
626
1076
|
"?.",
|
|
627
1077
|
"&&",
|
|
628
1078
|
"||",
|
|
@@ -659,9 +1109,17 @@ const SINGLE_CHARACTER_PUNCTUATION = new Set([
|
|
|
659
1109
|
"?",
|
|
660
1110
|
":",
|
|
661
1111
|
"&",
|
|
662
|
-
"|"
|
|
1112
|
+
"|",
|
|
1113
|
+
"^",
|
|
1114
|
+
"~"
|
|
663
1115
|
]);
|
|
664
|
-
|
|
1116
|
+
/**
|
|
1117
|
+
* A lookup table keyed by source text.
|
|
1118
|
+
*
|
|
1119
|
+
* Null-prototype: `TRANSFORM_METHODS["toString"]` otherwise returns `Object.prototype.toString`,
|
|
1120
|
+
* which is truthy, and the parser reads a method it does not support as one it does.
|
|
1121
|
+
*/ const sourceKeyed = (entries)=>Object.assign(Object.create(null), entries);
|
|
1122
|
+
const STRING_ESCAPES = sourceKeyed({
|
|
665
1123
|
"n": "\n",
|
|
666
1124
|
"r": "\r",
|
|
667
1125
|
"t": "\t",
|
|
@@ -669,6 +1127,24 @@ const STRING_ESCAPES = {
|
|
|
669
1127
|
"f": "\f",
|
|
670
1128
|
"v": "\v",
|
|
671
1129
|
"0": "\0"
|
|
1130
|
+
});
|
|
1131
|
+
/**
|
|
1132
|
+
* Whether a `/` here opens a regex rather than dividing.
|
|
1133
|
+
*
|
|
1134
|
+
* A regex cannot follow a value. Everything else — the start of the source, an operator, an opening
|
|
1135
|
+
* bracket, a comma — is a position where only a regex makes sense.
|
|
1136
|
+
*/ const regexCanStartHere = (tokens)=>{
|
|
1137
|
+
const previous = tokens[tokens.length - 1];
|
|
1138
|
+
if (previous == null) {
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
if (previous.kind === "number" || previous.kind === "string" || previous.kind === "bigint" || previous.kind === "regex") {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
if (previous.kind === "identifier") {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
return previous.value !== ")" && previous.value !== "]";
|
|
672
1148
|
};
|
|
673
1149
|
const isIdentifierStart = (char)=>/[a-zA-Z_$]/.test(char);
|
|
674
1150
|
const isIdentifierPart = (char)=>/[a-zA-Z0-9_$]/.test(char);
|
|
@@ -718,6 +1194,51 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
718
1194
|
i++;
|
|
719
1195
|
continue;
|
|
720
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* A regex literal, told from division by what came before it.
|
|
1199
|
+
*
|
|
1200
|
+
* `/` after a value — a number, string, identifier, `)` or `]` — is division. Anywhere else
|
|
1201
|
+
* it opens a regex. That is the same rule a JavaScript lexer uses, and it is why `x.a / 2`
|
|
1202
|
+
* and `/^a/.test(x.a)` can share a character.
|
|
1203
|
+
*/ if (char === "/" && source[i + 1] !== "/" && source[i + 1] !== "*" && regexCanStartHere(tokens)) {
|
|
1204
|
+
let value = "";
|
|
1205
|
+
let inClass = false;
|
|
1206
|
+
let j = i + 1;
|
|
1207
|
+
while(j < source.length){
|
|
1208
|
+
const current = source[j];
|
|
1209
|
+
if (current === "\\") {
|
|
1210
|
+
value += current + (source[j + 1] ?? "");
|
|
1211
|
+
j += 2;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (current === "[") {
|
|
1215
|
+
inClass = true;
|
|
1216
|
+
} else if (current === "]") {
|
|
1217
|
+
inClass = false;
|
|
1218
|
+
} else if (current === "/" && inClass === false) {
|
|
1219
|
+
break;
|
|
1220
|
+
} else if (current === "\n") {
|
|
1221
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
|
|
1222
|
+
}
|
|
1223
|
+
value += current;
|
|
1224
|
+
j++;
|
|
1225
|
+
}
|
|
1226
|
+
if (j >= source.length) {
|
|
1227
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
|
|
1228
|
+
}
|
|
1229
|
+
j++;
|
|
1230
|
+
let flags = "";
|
|
1231
|
+
while(j < source.length && isIdentifierPart(source[j])){
|
|
1232
|
+
flags += source[j];
|
|
1233
|
+
j++;
|
|
1234
|
+
}
|
|
1235
|
+
i = j;
|
|
1236
|
+
tokens.push({
|
|
1237
|
+
kind: "regex",
|
|
1238
|
+
value: `${value}\u0000${flags}`
|
|
1239
|
+
});
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
721
1242
|
// Comments
|
|
722
1243
|
if (char === "/" && source[i + 1] === "/") {
|
|
723
1244
|
while(i < source.length && source[i] !== "\n"){
|
|
@@ -737,6 +1258,8 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
737
1258
|
if (char === "'" || char === "\"" || char === "`") {
|
|
738
1259
|
const quote = char;
|
|
739
1260
|
let value = "";
|
|
1261
|
+
const chunks = [];
|
|
1262
|
+
const expressions = [];
|
|
740
1263
|
i++;
|
|
741
1264
|
while(i < source.length && source[i] !== quote){
|
|
742
1265
|
if (source[i] === "\\") {
|
|
@@ -751,8 +1274,43 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
751
1274
|
i += 2;
|
|
752
1275
|
continue;
|
|
753
1276
|
}
|
|
754
|
-
|
|
755
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* An interpolation. The literal so far becomes a chunk and the expression source is
|
|
1279
|
+
* kept whole, to be parsed by its own stream — nesting means the inner source can
|
|
1280
|
+
* hold anything, including another template.
|
|
1281
|
+
*/ if (quote === "`" && source[i] === "$" && source[i + 1] === "{") {
|
|
1282
|
+
let depth = 1;
|
|
1283
|
+
let expression = "";
|
|
1284
|
+
let at = i + 2;
|
|
1285
|
+
while(at < source.length && depth > 0){
|
|
1286
|
+
const current = source[at];
|
|
1287
|
+
if (current === "{") {
|
|
1288
|
+
depth++;
|
|
1289
|
+
} else if (current === "}") {
|
|
1290
|
+
depth--;
|
|
1291
|
+
if (depth === 0) {
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
} else if (current === "'" || current === '"' || current === "`") {
|
|
1295
|
+
const closing = current;
|
|
1296
|
+
expression += current;
|
|
1297
|
+
at++;
|
|
1298
|
+
while(at < source.length && source[at] !== closing){
|
|
1299
|
+
expression += source[at] === "\\" ? source[at] + (source[at + 1] ?? "") : source[at];
|
|
1300
|
+
at += source[at] === "\\" ? 2 : 1;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
expression += source[at];
|
|
1304
|
+
at++;
|
|
1305
|
+
}
|
|
1306
|
+
if (depth > 0) {
|
|
1307
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated template interpolation"));
|
|
1308
|
+
}
|
|
1309
|
+
chunks.push(value);
|
|
1310
|
+
expressions.push(expression);
|
|
1311
|
+
value = "";
|
|
1312
|
+
i = at + 1;
|
|
1313
|
+
continue;
|
|
756
1314
|
}
|
|
757
1315
|
value += source[i];
|
|
758
1316
|
i++;
|
|
@@ -761,6 +1319,17 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
761
1319
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated string literal"));
|
|
762
1320
|
}
|
|
763
1321
|
i++; // consume closing quote
|
|
1322
|
+
if (expressions.length > 0) {
|
|
1323
|
+
chunks.push(value);
|
|
1324
|
+
tokens.push({
|
|
1325
|
+
kind: "template",
|
|
1326
|
+
value: JSON.stringify({
|
|
1327
|
+
chunks,
|
|
1328
|
+
expressions
|
|
1329
|
+
})
|
|
1330
|
+
});
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
764
1333
|
tokens.push({
|
|
765
1334
|
kind: "string",
|
|
766
1335
|
value
|
|
@@ -804,6 +1373,14 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
804
1373
|
}
|
|
805
1374
|
}
|
|
806
1375
|
}
|
|
1376
|
+
if (source[i] === "n") {
|
|
1377
|
+
i++;
|
|
1378
|
+
tokens.push({
|
|
1379
|
+
kind: "bigint",
|
|
1380
|
+
value: value.replace(/_/g, "")
|
|
1381
|
+
});
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
807
1384
|
tokens.push({
|
|
808
1385
|
kind: "number",
|
|
809
1386
|
value: value.replace(/_/g, "")
|
|
@@ -854,6 +1431,24 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
854
1431
|
constructor(tokens){
|
|
855
1432
|
this.tokens = tokens;
|
|
856
1433
|
}
|
|
1434
|
+
/** Inserts tokens at the cursor, bracketed so they keep their own precedence. */ splice(tokens) {
|
|
1435
|
+
const bracketed = [
|
|
1436
|
+
{
|
|
1437
|
+
kind: "punctuation",
|
|
1438
|
+
value: "("
|
|
1439
|
+
},
|
|
1440
|
+
...tokens,
|
|
1441
|
+
{
|
|
1442
|
+
kind: "punctuation",
|
|
1443
|
+
value: ")"
|
|
1444
|
+
}
|
|
1445
|
+
];
|
|
1446
|
+
this.tokens = [
|
|
1447
|
+
...this.tokens.slice(0, this.index),
|
|
1448
|
+
...bracketed,
|
|
1449
|
+
...this.tokens.slice(this.index)
|
|
1450
|
+
];
|
|
1451
|
+
}
|
|
857
1452
|
get isAtEnd() {
|
|
858
1453
|
return this.index >= this.tokens.length;
|
|
859
1454
|
}
|
|
@@ -868,10 +1463,108 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
868
1463
|
this.index++;
|
|
869
1464
|
return token;
|
|
870
1465
|
}
|
|
1466
|
+
/** The tokens of one statement's value, through the `;` or block end that closes it. */ takeStatementTokens() {
|
|
1467
|
+
const tokens = [];
|
|
1468
|
+
let depth = 0;
|
|
1469
|
+
while(!this.isAtEnd){
|
|
1470
|
+
const token = this.peek();
|
|
1471
|
+
if (token.kind === "punctuation") {
|
|
1472
|
+
if (token.value === "(" || token.value === "[" || token.value === "{") {
|
|
1473
|
+
depth++;
|
|
1474
|
+
} else if (token.value === ")" || token.value === "]" || token.value === "}") {
|
|
1475
|
+
if (depth === 0) {
|
|
1476
|
+
break;
|
|
1477
|
+
}
|
|
1478
|
+
depth--;
|
|
1479
|
+
} else if (token.value === ";" && depth === 0) {
|
|
1480
|
+
this.next();
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
tokens.push(this.next());
|
|
1485
|
+
}
|
|
1486
|
+
if (tokens.length === 0) {
|
|
1487
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a declaration with no value"));
|
|
1488
|
+
}
|
|
1489
|
+
return tokens;
|
|
1490
|
+
}
|
|
871
1491
|
isPunctuation(value, offset = 0) {
|
|
872
1492
|
const token = this.peek(offset);
|
|
873
1493
|
return token != null && token.kind === "punctuation" && token.value === value;
|
|
874
1494
|
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Whether the group starting here holds a value rather than a condition.
|
|
1497
|
+
*
|
|
1498
|
+
* `(a && b)` is a boolean sub-expression; `(x.name ?? '') === 'ada'` and `(x.name).length` are
|
|
1499
|
+
* values. Only the token after the matching bracket tells them apart, so the decision is made by
|
|
1500
|
+
* looking ahead rather than by parsing one way and catching the failure — a rewind on exception
|
|
1501
|
+
* would swallow a genuine syntax error inside the group and report it as something else.
|
|
1502
|
+
*/ groupIsValue() {
|
|
1503
|
+
let depth = 0;
|
|
1504
|
+
let at = this.index;
|
|
1505
|
+
for(; at < this.tokens.length; at++){
|
|
1506
|
+
const token = this.tokens[at];
|
|
1507
|
+
if (token.kind !== "punctuation") {
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
if (token.value === "(") {
|
|
1511
|
+
depth++;
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
if (token.value === ")") {
|
|
1515
|
+
depth--;
|
|
1516
|
+
if (depth === 0) {
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
const after = this.tokens[at + 1];
|
|
1522
|
+
if (after == null || after.kind !== "punctuation") {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
return COMPARISON_OPERATORS[after.value] != null || after.value === "." || after.value === "?.";
|
|
1526
|
+
}
|
|
1527
|
+
/** Whether a `?` sits at the top level of what is left, so this is a conditional. */ holdsConditional() {
|
|
1528
|
+
let depth = 0;
|
|
1529
|
+
for(let at = this.index; at < this.tokens.length; at++){
|
|
1530
|
+
const token = this.tokens[at];
|
|
1531
|
+
if (token.kind !== "punctuation") {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (token.value === "(" || token.value === "[") {
|
|
1535
|
+
depth++;
|
|
1536
|
+
} else if (token.value === ")" || token.value === "]") {
|
|
1537
|
+
depth--;
|
|
1538
|
+
} else if (token.value === "?" && depth === 0) {
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return false;
|
|
1543
|
+
}
|
|
1544
|
+
/** Whether the group starting here is `( … ? … : … )` rather than a plain value. */ groupHoldsConditional() {
|
|
1545
|
+
let depth = 0;
|
|
1546
|
+
for(let at = this.index; at < this.tokens.length; at++){
|
|
1547
|
+
const token = this.tokens[at];
|
|
1548
|
+
if (token.kind !== "punctuation") {
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
if (token.value === "(") {
|
|
1552
|
+
depth++;
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
if (token.value === ")") {
|
|
1556
|
+
depth--;
|
|
1557
|
+
if (depth === 0) {
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (token.value === "?" && depth === 1) {
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
875
1568
|
matchPunctuation(value) {
|
|
876
1569
|
if (this.isPunctuation(value)) {
|
|
877
1570
|
this.index++;
|
|
@@ -885,12 +1578,101 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
885
1578
|
}
|
|
886
1579
|
}
|
|
887
1580
|
}
|
|
888
|
-
|
|
1581
|
+
/**
|
|
1582
|
+
* Calls JavaScript binds LOOSER than a comparison.
|
|
1583
|
+
*
|
|
1584
|
+
* This grammar reads a comparison's operands as values, which puts these tighter than they belong:
|
|
1585
|
+
* `x.flags & 6 === 2` is `x.flags & (6 === 2)` in JavaScript and would be read here as
|
|
1586
|
+
* `(x.flags & 6) === 2`. The two answer differently, so an ungrouped one is refused rather than
|
|
1587
|
+
* reinterpreted — the filter then runs in memory against the caller's own function, which is right by
|
|
1588
|
+
* construction. Brackets say which was meant, and JavaScript itself makes an unbracketed `??` mix a
|
|
1589
|
+
* syntax error for the same reason.
|
|
1590
|
+
*/ const LOOSER_THAN_COMPARISON = [
|
|
1591
|
+
"bit-and",
|
|
1592
|
+
"bit-or",
|
|
1593
|
+
"bit-xor",
|
|
1594
|
+
"coalesce"
|
|
1595
|
+
];
|
|
1596
|
+
const needsBrackets = (operand)=>operand.kind === "arithmetic" && operand.grouped !== true && LOOSER_THAN_COMPARISON.includes(operand.call);
|
|
1597
|
+
/** JavaScript precedence: `*`, `/`, `%` bind tighter than `+` and `-`. */ const MULTIPLICATIVE_OPERATORS = sourceKeyed({
|
|
1598
|
+
"*": "multiply",
|
|
1599
|
+
"/": "divide",
|
|
1600
|
+
"%": "modulo"
|
|
1601
|
+
});
|
|
1602
|
+
const ADDITIVE_OPERATORS = sourceKeyed({
|
|
1603
|
+
"+": "add",
|
|
1604
|
+
"-": "subtract"
|
|
1605
|
+
});
|
|
1606
|
+
const SHIFT_OPERATORS = sourceKeyed({
|
|
1607
|
+
"<<": "shift-left",
|
|
1608
|
+
">>": "shift-right",
|
|
1609
|
+
">>>": "shift-right-unsigned"
|
|
1610
|
+
});
|
|
1611
|
+
const BITWISE_AND_OPERATORS = sourceKeyed({
|
|
1612
|
+
"&": "bit-and"
|
|
1613
|
+
});
|
|
1614
|
+
const BITWISE_XOR_OPERATORS = sourceKeyed({
|
|
1615
|
+
"^": "bit-xor"
|
|
1616
|
+
});
|
|
1617
|
+
const BITWISE_OR_OPERATORS = sourceKeyed({
|
|
1618
|
+
"|": "bit-or"
|
|
1619
|
+
});
|
|
1620
|
+
const COALESCE_OPERATORS = sourceKeyed({
|
|
1621
|
+
"??": "coalesce"
|
|
1622
|
+
});
|
|
1623
|
+
/** Whether a schema property is reachable in here, which decides which side of a comparison it is. */ const containsProperty = (operand)=>{
|
|
1624
|
+
if (operand.kind === "property") {
|
|
1625
|
+
return true;
|
|
1626
|
+
}
|
|
1627
|
+
if (operand.kind === "conditional") {
|
|
1628
|
+
// A comparison always names a schema property, so the condition alone settles it
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
return operand.kind === "arithmetic" && (containsProperty(operand.left) || containsProperty(operand.right) || operand.extra != null && containsProperty(operand.extra));
|
|
1632
|
+
};
|
|
1633
|
+
const DECLARATION_KEYWORDS = new Set([
|
|
1634
|
+
"const",
|
|
1635
|
+
"let",
|
|
1636
|
+
"var"
|
|
1637
|
+
]);
|
|
1638
|
+
/** An operand whose value only a row can supply. */ const UNKNOWN_UNTIL_ROW = Symbol("unknown until row");
|
|
1639
|
+
/** The empty argument slot of a unary call. Compared by identity, so a real `undefined` still counts. */ const NO_ARGUMENT = Object.freeze({
|
|
1640
|
+
kind: "value",
|
|
1641
|
+
value: undefined,
|
|
1642
|
+
transformer: null,
|
|
1643
|
+
locale: null
|
|
1644
|
+
});
|
|
1645
|
+
const noArgument = ()=>NO_ARGUMENT;
|
|
1646
|
+
/** A predicate no row satisfies. Never reaches a tree: no expression node means "match nothing". */ const NEVER = "never";
|
|
1647
|
+
const and = (left, right)=>{
|
|
1648
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left)) {
|
|
1649
|
+
return right;
|
|
1650
|
+
}
|
|
1651
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
|
|
1652
|
+
return left;
|
|
1653
|
+
}
|
|
1654
|
+
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
1655
|
+
operator: "&&",
|
|
1656
|
+
left,
|
|
1657
|
+
right
|
|
1658
|
+
});
|
|
1659
|
+
};
|
|
1660
|
+
const or = (left, right)=>{
|
|
1661
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left) || _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
|
|
1662
|
+
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
1663
|
+
}
|
|
1664
|
+
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
1665
|
+
operator: "||",
|
|
1666
|
+
left,
|
|
1667
|
+
right
|
|
1668
|
+
});
|
|
1669
|
+
};
|
|
1670
|
+
const COMPARATOR_METHODS = sourceKeyed({
|
|
889
1671
|
startsWith: "starts-with",
|
|
890
1672
|
endsWith: "ends-with",
|
|
891
1673
|
includes: "includes"
|
|
892
|
-
};
|
|
893
|
-
const TRANSFORM_METHODS = {
|
|
1674
|
+
});
|
|
1675
|
+
const TRANSFORM_METHODS = sourceKeyed({
|
|
894
1676
|
toLowerCase: {
|
|
895
1677
|
transformer: "to-lower-case",
|
|
896
1678
|
locale: null
|
|
@@ -907,8 +1689,8 @@ const TRANSFORM_METHODS = {
|
|
|
907
1689
|
transformer: "to-upper-case",
|
|
908
1690
|
locale: "en-US"
|
|
909
1691
|
}
|
|
910
|
-
};
|
|
911
|
-
const COMPARISON_OPERATORS = {
|
|
1692
|
+
});
|
|
1693
|
+
const COMPARISON_OPERATORS = sourceKeyed({
|
|
912
1694
|
"==": {
|
|
913
1695
|
comparator: "equals",
|
|
914
1696
|
negated: false,
|
|
@@ -949,7 +1731,7 @@ const COMPARISON_OPERATORS = {
|
|
|
949
1731
|
negated: false,
|
|
950
1732
|
strict: false
|
|
951
1733
|
}
|
|
952
|
-
};
|
|
1734
|
+
});
|
|
953
1735
|
const SWAPPED_COMPARATORS = {
|
|
954
1736
|
"equals": "equals",
|
|
955
1737
|
"greater-than": "less-than",
|
|
@@ -1020,14 +1802,14 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1020
1802
|
*/ class ExpressionParser {
|
|
1021
1803
|
schema;
|
|
1022
1804
|
stream;
|
|
1023
|
-
|
|
1805
|
+
scope;
|
|
1024
1806
|
paramsName;
|
|
1025
1807
|
params;
|
|
1026
1808
|
/** Set when a param value shaped the tree itself (e.g. x[p.name]) — such templates cannot be cached. */ structurallyDependsOnParams = false;
|
|
1027
|
-
constructor(schema, stream,
|
|
1809
|
+
constructor(schema, stream, scope, paramsName, params){
|
|
1028
1810
|
this.schema = schema;
|
|
1029
1811
|
this.stream = stream;
|
|
1030
|
-
this.
|
|
1812
|
+
this.scope = scope;
|
|
1031
1813
|
this.paramsName = paramsName;
|
|
1032
1814
|
this.params = params;
|
|
1033
1815
|
}
|
|
@@ -1038,6 +1820,180 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1038
1820
|
}
|
|
1039
1821
|
return expression;
|
|
1040
1822
|
}
|
|
1823
|
+
parseBody() {
|
|
1824
|
+
if (!this.stream.isPunctuation("{")) {
|
|
1825
|
+
return this.parse();
|
|
1826
|
+
}
|
|
1827
|
+
const answer = this.parseBlock();
|
|
1828
|
+
if (!this.stream.isAtEnd) {
|
|
1829
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`unexpected token '${this.stream.peek()?.value}'`));
|
|
1830
|
+
}
|
|
1831
|
+
if (answer === NEVER) {
|
|
1832
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a predicate no row can satisfy"));
|
|
1833
|
+
}
|
|
1834
|
+
return answer;
|
|
1835
|
+
}
|
|
1836
|
+
/** The expression a `{ … }` block answers with. */ parseBlock() {
|
|
1837
|
+
this.stream.expectPunctuation("{");
|
|
1838
|
+
const answer = this.parseStatements();
|
|
1839
|
+
this.stream.expectPunctuation("}");
|
|
1840
|
+
return answer;
|
|
1841
|
+
}
|
|
1842
|
+
/** Statements up to the one that returns. What follows a `return` is never read, as in JavaScript. */ parseStatements() {
|
|
1843
|
+
if (this.stream.isPunctuation("}") || this.stream.isAtEnd) {
|
|
1844
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a block body that returns nothing"));
|
|
1845
|
+
}
|
|
1846
|
+
const keyword = this.stream.peek();
|
|
1847
|
+
if (keyword == null || keyword.kind !== "identifier") {
|
|
1848
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`a statement starting '${keyword?.value}'`));
|
|
1849
|
+
}
|
|
1850
|
+
if (DECLARATION_KEYWORDS.has(keyword.value)) {
|
|
1851
|
+
this.declare();
|
|
1852
|
+
return this.parseStatements();
|
|
1853
|
+
}
|
|
1854
|
+
if (keyword.value === "return") {
|
|
1855
|
+
this.stream.next();
|
|
1856
|
+
const answer = this.parseReturnedCondition();
|
|
1857
|
+
this.stream.matchPunctuation(";");
|
|
1858
|
+
return answer;
|
|
1859
|
+
}
|
|
1860
|
+
if (keyword.value === "if") {
|
|
1861
|
+
return this.parseIfStatement();
|
|
1862
|
+
}
|
|
1863
|
+
if (keyword.value === "switch") {
|
|
1864
|
+
return this.parseSwitchStatement();
|
|
1865
|
+
}
|
|
1866
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the statement '${keyword.value}'`));
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Binds a `const`/`let`/`var` name to the tokens of its initializer — tokens rather than a parsed
|
|
1870
|
+
* expression, so the name works as an operand, an argument, or a call receiver alike.
|
|
1871
|
+
*/ declare() {
|
|
1872
|
+
this.stream.next();
|
|
1873
|
+
const name = this.stream.next();
|
|
1874
|
+
if (name.kind !== "identifier") {
|
|
1875
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the declaration '${name.value}'`));
|
|
1876
|
+
}
|
|
1877
|
+
this.stream.expectPunctuation("=");
|
|
1878
|
+
this.scope.set(name.value, {
|
|
1879
|
+
kind: "inlined",
|
|
1880
|
+
tokens: this.stream.takeStatementTokens()
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
/** `return false` on its own, which no row satisfies, and every other returned condition. */ parseReturnedCondition() {
|
|
1884
|
+
const next = this.stream.peek();
|
|
1885
|
+
const after = this.stream.peek(1);
|
|
1886
|
+
const endsHere = after == null || after.kind === "punctuation" && (after.value === ";" || after.value === "}");
|
|
1887
|
+
if (next != null && next.kind === "identifier" && next.value === "false" && endsHere) {
|
|
1888
|
+
this.stream.next();
|
|
1889
|
+
return NEVER;
|
|
1890
|
+
}
|
|
1891
|
+
return this.parseOr();
|
|
1892
|
+
}
|
|
1893
|
+
parseIfStatement() {
|
|
1894
|
+
this.stream.next();
|
|
1895
|
+
this.stream.expectPunctuation("(");
|
|
1896
|
+
const condition = this.parseOr();
|
|
1897
|
+
this.stream.expectPunctuation(")");
|
|
1898
|
+
const whenTrue = this.parseBranch();
|
|
1899
|
+
if (this.stream.peek()?.value === "else") {
|
|
1900
|
+
this.stream.next();
|
|
1901
|
+
return this.either(condition, whenTrue, this.parseBranch());
|
|
1902
|
+
}
|
|
1903
|
+
// Without an `else`, the statements after the `if` are the other branch
|
|
1904
|
+
return this.either(condition, whenTrue, this.parseStatements());
|
|
1905
|
+
}
|
|
1906
|
+
/** One arm of an `if`: a block, or a single statement. */ parseBranch() {
|
|
1907
|
+
return this.stream.isPunctuation("{") ? this.parseBlock() : this.parseStatements();
|
|
1908
|
+
}
|
|
1909
|
+
/** A `switch` over one subject, as the disjunction of its cases. */ parseSwitchStatement() {
|
|
1910
|
+
this.stream.next();
|
|
1911
|
+
this.stream.expectPunctuation("(");
|
|
1912
|
+
const subject = this.parseValue();
|
|
1913
|
+
this.stream.expectPunctuation(")");
|
|
1914
|
+
this.stream.expectPunctuation("{");
|
|
1915
|
+
let matching = null;
|
|
1916
|
+
let pending = [];
|
|
1917
|
+
let everyLabel = [];
|
|
1918
|
+
let byDefault = null;
|
|
1919
|
+
let anyCaseBroke = false;
|
|
1920
|
+
while(!this.stream.matchPunctuation("}")){
|
|
1921
|
+
const label = this.stream.next();
|
|
1922
|
+
if (label.kind !== "identifier" || label.value !== "case" && label.value !== "default") {
|
|
1923
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'${label.value}' inside a switch`));
|
|
1924
|
+
}
|
|
1925
|
+
if (label.value === "case") {
|
|
1926
|
+
const test = this.buildComparison(subject, COMPARISON_OPERATORS["==="], this.parseValue());
|
|
1927
|
+
pending.push(test);
|
|
1928
|
+
everyLabel.push(test);
|
|
1929
|
+
}
|
|
1930
|
+
this.stream.expectPunctuation(":");
|
|
1931
|
+
// `case 'a':` with no body of its own runs the next case's body
|
|
1932
|
+
if (this.stream.peek()?.value === "case" || this.stream.peek()?.value === "default") {
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
if (this.stream.peek()?.value === "break") {
|
|
1936
|
+
this.stream.next();
|
|
1937
|
+
this.stream.matchPunctuation(";");
|
|
1938
|
+
anyCaseBroke = true;
|
|
1939
|
+
pending = [];
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
const body = this.parseCaseBody();
|
|
1943
|
+
if (label.value === "default") {
|
|
1944
|
+
byDefault = body === NEVER ? null : body;
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (body !== NEVER && pending.length > 0) {
|
|
1948
|
+
const reached = pending.reduce((left, right)=>or(left, right));
|
|
1949
|
+
const term = _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(body) ? reached : and(reached, body);
|
|
1950
|
+
matching = matching == null ? term : or(matching, term);
|
|
1951
|
+
}
|
|
1952
|
+
pending = [];
|
|
1953
|
+
}
|
|
1954
|
+
// Falling out of the switch continues after it, so the statements there are the default too
|
|
1955
|
+
const afterSwitch = byDefault == null && !this.stream.isPunctuation("}") && !this.stream.isAtEnd ? this.parseStatements() : NEVER;
|
|
1956
|
+
if (afterSwitch !== NEVER) {
|
|
1957
|
+
// A `break` also continues after the switch, so its case would take that answer rather
|
|
1958
|
+
// than none — a distinction this rewrite cannot carry
|
|
1959
|
+
if (anyCaseBroke) {
|
|
1960
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a switch that breaks and then falls into more statements"));
|
|
1961
|
+
}
|
|
1962
|
+
byDefault = afterSwitch;
|
|
1963
|
+
}
|
|
1964
|
+
// A `default` runs only when every case failed, wherever it was written
|
|
1965
|
+
if (byDefault != null) {
|
|
1966
|
+
const noCaseMatched = everyLabel.length === 0 ? byDefault : and(this.negateExpression(everyLabel.reduce((left, right)=>or(left, right))), byDefault);
|
|
1967
|
+
matching = matching == null ? noCaseMatched : or(matching, noCaseMatched);
|
|
1968
|
+
}
|
|
1969
|
+
return matching ?? NEVER;
|
|
1970
|
+
}
|
|
1971
|
+
/** One case body, and the `break` that may follow its `return`. */ parseCaseBody() {
|
|
1972
|
+
const answer = this.parseStatements();
|
|
1973
|
+
if (this.stream.peek()?.value === "break") {
|
|
1974
|
+
this.stream.next();
|
|
1975
|
+
this.stream.matchPunctuation(";");
|
|
1976
|
+
}
|
|
1977
|
+
return answer;
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* The predicate an `if`/`else` answers: `(condition && whenTrue) || (!condition && whenFalse)`,
|
|
1981
|
+
* with each case below that form after a constant branch cancels out.
|
|
1982
|
+
*/ either(condition, whenTrue, whenFalse) {
|
|
1983
|
+
if (whenTrue === NEVER) {
|
|
1984
|
+
return whenFalse === NEVER ? NEVER : and(this.negateExpression(condition), whenFalse);
|
|
1985
|
+
}
|
|
1986
|
+
if (whenFalse === NEVER) {
|
|
1987
|
+
return and(condition, whenTrue);
|
|
1988
|
+
}
|
|
1989
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenTrue)) {
|
|
1990
|
+
return or(condition, whenFalse);
|
|
1991
|
+
}
|
|
1992
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenFalse)) {
|
|
1993
|
+
return or(this.negateExpression(condition), whenTrue);
|
|
1994
|
+
}
|
|
1995
|
+
return or(and(condition, whenTrue), and(this.negateExpression(condition), whenFalse));
|
|
1996
|
+
}
|
|
1041
1997
|
// || binds loosest, so it sits at the root of the parse
|
|
1042
1998
|
parseOr() {
|
|
1043
1999
|
let left = this.parseAnd();
|
|
@@ -1085,10 +2041,18 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1085
2041
|
/**
|
|
1086
2042
|
* Applies `!` to an already-parsed expression: comparators flip their
|
|
1087
2043
|
* negated flag, compound expressions distribute via De Morgan's laws.
|
|
2044
|
+
*
|
|
2045
|
+
* Builds a new tree rather than flipping the flag in place, because an `if` uses its condition
|
|
2046
|
+
* twice — once negated — and a shared node would carry the flip into both branches.
|
|
1088
2047
|
*/ negateExpression(expression) {
|
|
1089
2048
|
if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
|
|
1090
|
-
|
|
1091
|
-
|
|
2049
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2050
|
+
comparator: expression.comparator,
|
|
2051
|
+
negated: !expression.negated,
|
|
2052
|
+
strict: expression.strict,
|
|
2053
|
+
left: expression.left,
|
|
2054
|
+
right: expression.right
|
|
2055
|
+
});
|
|
1092
2056
|
}
|
|
1093
2057
|
if (expression instanceof _types__rspack_import_1/* .OperatorExpression */.fw && expression.left != null && expression.right != null) {
|
|
1094
2058
|
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
@@ -1100,25 +2064,125 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1100
2064
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("'!' on this expression"));
|
|
1101
2065
|
}
|
|
1102
2066
|
parseComparison() {
|
|
1103
|
-
|
|
1104
|
-
|
|
2067
|
+
/**
|
|
2068
|
+
* A parenthesised group is either a boolean sub-expression or a VALUE — `(a && b)` against
|
|
2069
|
+
* `(x.name ?? '') === 'ada'` — and which one it is is only known at the closing bracket, by
|
|
2070
|
+
* what follows. So the boolean reading is tried first and rewound if a comparator turns up.
|
|
2071
|
+
*/ if (this.stream.isPunctuation("(") && this.stream.groupIsValue() === false) {
|
|
2072
|
+
this.stream.next();
|
|
1105
2073
|
const expression = this.parseOr();
|
|
1106
2074
|
this.stream.expectPunctuation(")");
|
|
1107
|
-
const trailing = this.stream.peek();
|
|
1108
|
-
if (trailing != null && trailing.kind === "punctuation" && COMPARISON_OPERATORS[trailing.value] != null) {
|
|
1109
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison against a parenthesized expression"));
|
|
1110
|
-
}
|
|
1111
2075
|
return expression;
|
|
1112
2076
|
}
|
|
1113
|
-
const left = this.
|
|
2077
|
+
const left = this.parseValue();
|
|
1114
2078
|
const operatorToken = this.stream.peek();
|
|
1115
2079
|
if (operatorToken != null && operatorToken.kind === "punctuation" && COMPARISON_OPERATORS[operatorToken.value] != null) {
|
|
1116
2080
|
this.stream.next();
|
|
1117
|
-
const right = this.
|
|
2081
|
+
const right = this.parseValue();
|
|
1118
2082
|
return this.buildComparison(left, COMPARISON_OPERATORS[operatorToken.value], right);
|
|
1119
2083
|
}
|
|
1120
2084
|
return this.buildStandalone(left);
|
|
1121
2085
|
}
|
|
2086
|
+
/**
|
|
2087
|
+
* A value, at JavaScript's precedence.
|
|
2088
|
+
*
|
|
2089
|
+
* Lowest first: the conditional operator, then nullish coalescing, then the bitwise levels, then
|
|
2090
|
+
* the shifts, then the arithmetic. Comparison sits between the shifts and the bitwise levels in
|
|
2091
|
+
* JavaScript, but a comparison is a boolean and is handled by `parseComparison` above, so this
|
|
2092
|
+
* chain skips it — a bitwise operand here is always a value.
|
|
2093
|
+
*/ /**
|
|
2094
|
+
* An operand from its own source, sharing this parser's schema and parameter names.
|
|
2095
|
+
*
|
|
2096
|
+
* A structural dependence found inside propagates outward: the template it belongs to cannot be
|
|
2097
|
+
* cached either.
|
|
2098
|
+
*/ parseNested(source) {
|
|
2099
|
+
const nested = new ExpressionParser(this.schema, new TokenStream(tokenize(source)), this.scope, this.paramsName, this.params);
|
|
2100
|
+
const operand = nested.parseInterpolation();
|
|
2101
|
+
// Leftover tokens mean the interpolation held something this reads only part of. Silently
|
|
2102
|
+
// keeping the part it understood is the worst outcome available: `${x.age > 5 ? "a" : "b"}`
|
|
2103
|
+
// would become `x.age`, and the filter would answer a question nobody asked.
|
|
2104
|
+
if (nested.stream.isAtEnd === false) {
|
|
2105
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("an interpolation this parser reads only part of"));
|
|
2106
|
+
}
|
|
2107
|
+
if (nested.structurallyDependsOnParams === true) {
|
|
2108
|
+
this.structurallyDependsOnParams = true;
|
|
2109
|
+
}
|
|
2110
|
+
return operand;
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* The whole of one `${…}`.
|
|
2114
|
+
*
|
|
2115
|
+
* A conditional is read here rather than in `parseValue`, because an interpolation is the one
|
|
2116
|
+
* place a conditional appears without brackets around it.
|
|
2117
|
+
*/ parseInterpolation() {
|
|
2118
|
+
if (this.stream.holdsConditional()) {
|
|
2119
|
+
const condition = this.parseOr();
|
|
2120
|
+
this.stream.expectPunctuation("?");
|
|
2121
|
+
const whenTrue = this.parseValue();
|
|
2122
|
+
this.stream.expectPunctuation(":");
|
|
2123
|
+
const whenFalse = this.parseValue();
|
|
2124
|
+
return {
|
|
2125
|
+
kind: "conditional",
|
|
2126
|
+
condition,
|
|
2127
|
+
whenTrue,
|
|
2128
|
+
whenFalse
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
return this.parseValue();
|
|
2132
|
+
}
|
|
2133
|
+
parseValue() {
|
|
2134
|
+
return this.parseCoalesce();
|
|
2135
|
+
}
|
|
2136
|
+
parseCoalesce() {
|
|
2137
|
+
return this.parseBinary(COALESCE_OPERATORS, ()=>this.parseBitwiseOr());
|
|
2138
|
+
}
|
|
2139
|
+
parseBitwiseOr() {
|
|
2140
|
+
return this.parseBinary(BITWISE_OR_OPERATORS, ()=>this.parseBitwiseXor());
|
|
2141
|
+
}
|
|
2142
|
+
parseBitwiseXor() {
|
|
2143
|
+
return this.parseBinary(BITWISE_XOR_OPERATORS, ()=>this.parseBitwiseAnd());
|
|
2144
|
+
}
|
|
2145
|
+
parseBitwiseAnd() {
|
|
2146
|
+
return this.parseBinary(BITWISE_AND_OPERATORS, ()=>this.parseShift());
|
|
2147
|
+
}
|
|
2148
|
+
parseShift() {
|
|
2149
|
+
return this.parseBinary(SHIFT_OPERATORS, ()=>this.parseAdditive());
|
|
2150
|
+
}
|
|
2151
|
+
parseAdditive() {
|
|
2152
|
+
return this.parseBinary(ADDITIVE_OPERATORS, ()=>this.parseMultiplicative());
|
|
2153
|
+
}
|
|
2154
|
+
parseMultiplicative() {
|
|
2155
|
+
return this.parseBinary(MULTIPLICATIVE_OPERATORS, ()=>this.parseExponent());
|
|
2156
|
+
}
|
|
2157
|
+
/** `**` is RIGHT-associative: `2 ** 3 ** 2` is 2 ** 9, not 8 ** 2. */ parseExponent() {
|
|
2158
|
+
const left = this.parseOperand();
|
|
2159
|
+
if (this.stream.isPunctuation("**") === false) {
|
|
2160
|
+
return left;
|
|
2161
|
+
}
|
|
2162
|
+
this.stream.next();
|
|
2163
|
+
return {
|
|
2164
|
+
kind: "arithmetic",
|
|
2165
|
+
call: "power",
|
|
2166
|
+
left,
|
|
2167
|
+
right: this.parseExponent()
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
/** Left-associative, so `a - b - c` is `(a - b) - c` rather than `a - (b - c)`. */ parseBinary(operators, next) {
|
|
2171
|
+
let left = next();
|
|
2172
|
+
for(;;){
|
|
2173
|
+
const token = this.stream.peek();
|
|
2174
|
+
if (token == null || token.kind !== "punctuation" || operators[token.value] == null) {
|
|
2175
|
+
return left;
|
|
2176
|
+
}
|
|
2177
|
+
this.stream.next();
|
|
2178
|
+
left = {
|
|
2179
|
+
kind: "arithmetic",
|
|
2180
|
+
call: operators[token.value],
|
|
2181
|
+
left,
|
|
2182
|
+
right: next()
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
1122
2186
|
parseOperand() {
|
|
1123
2187
|
const token = this.stream.peek();
|
|
1124
2188
|
if (token == null) {
|
|
@@ -1142,6 +2206,131 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1142
2206
|
locale: null
|
|
1143
2207
|
};
|
|
1144
2208
|
}
|
|
2209
|
+
// A parenthesised VALUE — `(x.price & 1)`, `(x.name ?? '')`. The boolean reading of a group
|
|
2210
|
+
// is handled in parseComparison; by the time an operand sees one it is arithmetic.
|
|
2211
|
+
if (token.kind === "punctuation" && token.value === "(") {
|
|
2212
|
+
const conditional = this.stream.groupHoldsConditional();
|
|
2213
|
+
this.stream.next();
|
|
2214
|
+
if (conditional === true) {
|
|
2215
|
+
const condition = this.parseOr();
|
|
2216
|
+
this.stream.expectPunctuation("?");
|
|
2217
|
+
const whenTrue = this.parseValue();
|
|
2218
|
+
this.stream.expectPunctuation(":");
|
|
2219
|
+
const whenFalse = this.parseValue();
|
|
2220
|
+
this.stream.expectPunctuation(")");
|
|
2221
|
+
return {
|
|
2222
|
+
kind: "conditional",
|
|
2223
|
+
condition,
|
|
2224
|
+
whenTrue,
|
|
2225
|
+
whenFalse
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
const inner = this.parseValue();
|
|
2229
|
+
this.stream.expectPunctuation(")");
|
|
2230
|
+
const grouped = inner.kind === "arithmetic" ? {
|
|
2231
|
+
...inner,
|
|
2232
|
+
grouped: true
|
|
2233
|
+
} : inner;
|
|
2234
|
+
return this.withGroupCall(grouped);
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* A template with interpolation, folded into `concat`.
|
|
2238
|
+
*
|
|
2239
|
+
* Each `${…}` was kept as source by the tokenizer and is parsed by its own stream, so it can
|
|
2240
|
+
* hold anything an operand can — a property, a param, arithmetic, another template. Empty
|
|
2241
|
+
* chunks are dropped: `${a}${b}` is two operands, not two operands and three empty strings.
|
|
2242
|
+
*/ if (token.kind === "template") {
|
|
2243
|
+
this.stream.next();
|
|
2244
|
+
const { chunks, expressions } = JSON.parse(token.value);
|
|
2245
|
+
const pieces = [];
|
|
2246
|
+
for(let at = 0; at < chunks.length; at++){
|
|
2247
|
+
if (chunks[at].length > 0) {
|
|
2248
|
+
pieces.push({
|
|
2249
|
+
kind: "value",
|
|
2250
|
+
value: chunks[at],
|
|
2251
|
+
transformer: null,
|
|
2252
|
+
locale: null
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
if (at < expressions.length) {
|
|
2256
|
+
pieces.push(this.parseNested(expressions[at]));
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
if (pieces.length === 0) {
|
|
2260
|
+
return {
|
|
2261
|
+
kind: "value",
|
|
2262
|
+
value: "",
|
|
2263
|
+
transformer: null,
|
|
2264
|
+
locale: null
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
// One piece and no chunk means no concat to do the coercion, so the conversion has to be
|
|
2268
|
+
// explicit: `` `${x.age}` `` is the STRING "9", not the number 9.
|
|
2269
|
+
if (pieces.length === 1) {
|
|
2270
|
+
const only = pieces[0];
|
|
2271
|
+
const alreadyText = only.kind === "value" && typeof only.value === "string";
|
|
2272
|
+
return alreadyText ? only : {
|
|
2273
|
+
kind: "arithmetic",
|
|
2274
|
+
call: "to-string",
|
|
2275
|
+
left: only,
|
|
2276
|
+
right: noArgument()
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
return pieces.reduce((left, right)=>({
|
|
2280
|
+
kind: "arithmetic",
|
|
2281
|
+
call: "concat",
|
|
2282
|
+
left,
|
|
2283
|
+
right
|
|
2284
|
+
}));
|
|
2285
|
+
}
|
|
2286
|
+
if (token.kind === "bigint") {
|
|
2287
|
+
this.stream.next();
|
|
2288
|
+
return {
|
|
2289
|
+
kind: "value",
|
|
2290
|
+
value: BigInt(token.value),
|
|
2291
|
+
transformer: null,
|
|
2292
|
+
locale: null
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
if (token.kind === "regex") {
|
|
2296
|
+
this.stream.next();
|
|
2297
|
+
const [source, flags] = token.value.split("\u0000");
|
|
2298
|
+
const pattern = {
|
|
2299
|
+
kind: "value",
|
|
2300
|
+
value: new RegExp(source, flags),
|
|
2301
|
+
transformer: null,
|
|
2302
|
+
locale: null
|
|
2303
|
+
};
|
|
2304
|
+
// `/^a/.test(x.name)` — the pattern is the literal, the subject is the argument, and the
|
|
2305
|
+
// tree puts them the other way round: the property is what the call applies to.
|
|
2306
|
+
if (this.stream.isPunctuation(".")) {
|
|
2307
|
+
const method = this.stream.peek(1);
|
|
2308
|
+
if (method != null && method.kind === "identifier" && method.value === "test") {
|
|
2309
|
+
this.stream.next();
|
|
2310
|
+
this.stream.next();
|
|
2311
|
+
this.stream.expectPunctuation("(");
|
|
2312
|
+
const subject = this.parseValue();
|
|
2313
|
+
this.stream.expectPunctuation(")");
|
|
2314
|
+
return {
|
|
2315
|
+
kind: "arithmetic",
|
|
2316
|
+
call: "matches",
|
|
2317
|
+
left: subject,
|
|
2318
|
+
right: pattern
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return pattern;
|
|
2323
|
+
}
|
|
2324
|
+
if (token.kind === "punctuation" && token.value === "~") {
|
|
2325
|
+
this.stream.next();
|
|
2326
|
+
// Unary, so the tree carries the operand and no argument
|
|
2327
|
+
return {
|
|
2328
|
+
kind: "arithmetic",
|
|
2329
|
+
call: "bit-not",
|
|
2330
|
+
left: this.parseOperand(),
|
|
2331
|
+
right: noArgument()
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
1145
2334
|
if (token.kind === "punctuation" && token.value === "-") {
|
|
1146
2335
|
this.stream.next();
|
|
1147
2336
|
const numberToken = this.stream.next();
|
|
@@ -1199,6 +2388,9 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1199
2388
|
if (argument.kind === "method-call") {
|
|
1200
2389
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("nested method call inside .includes()"));
|
|
1201
2390
|
}
|
|
2391
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2392
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic inside .includes()"));
|
|
2393
|
+
}
|
|
1202
2394
|
return {
|
|
1203
2395
|
kind: "method-call",
|
|
1204
2396
|
target: array,
|
|
@@ -1244,16 +2436,17 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1244
2436
|
locale: null
|
|
1245
2437
|
};
|
|
1246
2438
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (this.paramsName != null && root === this.paramsName) {
|
|
2439
|
+
const binding = this.scope.get(root);
|
|
2440
|
+
if (binding != null) {
|
|
2441
|
+
if (binding.kind === "inlined") {
|
|
2442
|
+
this.stream.splice(binding.tokens);
|
|
2443
|
+
return this.parseOperand();
|
|
2444
|
+
}
|
|
1254
2445
|
return this.parseChain({
|
|
1255
|
-
kind:
|
|
1256
|
-
|
|
2446
|
+
kind: binding.kind,
|
|
2447
|
+
path: [
|
|
2448
|
+
...binding.path
|
|
2449
|
+
]
|
|
1257
2450
|
});
|
|
1258
2451
|
}
|
|
1259
2452
|
// A bare variable from the outer scope — its value cannot be derived from source text
|
|
@@ -1263,7 +2456,7 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1263
2456
|
* Parses the segments after an entity/params root: dot access, bracket
|
|
1264
2457
|
* access, transform methods and comparator methods.
|
|
1265
2458
|
*/ parseChain(options) {
|
|
1266
|
-
const path =
|
|
2459
|
+
const path = options.path;
|
|
1267
2460
|
let transformer = null;
|
|
1268
2461
|
let locale = null;
|
|
1269
2462
|
while(true){
|
|
@@ -1289,6 +2482,9 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1289
2482
|
if (argument.kind === "method-call") {
|
|
1290
2483
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`nested method call inside .${method}()`));
|
|
1291
2484
|
}
|
|
2485
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2486
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`arithmetic inside .${method}()`));
|
|
2487
|
+
}
|
|
1292
2488
|
return {
|
|
1293
2489
|
kind: "method-call",
|
|
1294
2490
|
target: this.resolveChain(options.kind, path, transformer, locale),
|
|
@@ -1325,12 +2521,15 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1325
2521
|
// docs/mutation-backlog.md) — every mutation of this four-conjunct guard reroutes
|
|
1326
2522
|
// bracket access between two paths that both collapse to NOT_PARSABLE; the
|
|
1327
2523
|
// experiment recorded there aimed 30 tests at this line and killed none.
|
|
1328
|
-
|
|
1329
|
-
|
|
2524
|
+
const binding = token.kind === "identifier" ? this.scope.get(token.value) : undefined;
|
|
2525
|
+
if (kind === "property" && binding != null && binding.kind === "param") {
|
|
2526
|
+
const paramPath = [
|
|
2527
|
+
...binding.path
|
|
2528
|
+
];
|
|
1330
2529
|
while(this.stream.matchPunctuation(".") || this.stream.matchPunctuation("?.")){
|
|
1331
2530
|
paramPath.push(this.stream.next().value);
|
|
1332
2531
|
}
|
|
1333
|
-
const resolved = resolveParamPath(this.paramsName, paramPath, this.params);
|
|
2532
|
+
const resolved = resolveParamPath(this.paramsName ?? token.value, paramPath, this.params);
|
|
1334
2533
|
if (typeof resolved !== "string") {
|
|
1335
2534
|
throw new ParamDependentParseError(ERROR_MESSAGES.PROPERTY_NOT_FOUND(paramPath.join(".")));
|
|
1336
2535
|
}
|
|
@@ -1383,6 +2582,67 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1383
2582
|
locale
|
|
1384
2583
|
};
|
|
1385
2584
|
}
|
|
2585
|
+
/**
|
|
2586
|
+
* A call on a parenthesised value: `(x.name).toLowerCase()`, `(x.age + 1).length`. Any operand can
|
|
2587
|
+
* receive one here, unlike a property chain, which carries at most one transform.
|
|
2588
|
+
*/ withGroupCall(operand) {
|
|
2589
|
+
let receiver = operand;
|
|
2590
|
+
while(this.stream.isPunctuation(".") || this.stream.isPunctuation("?.")){
|
|
2591
|
+
const segment = this.stream.peek(1);
|
|
2592
|
+
if (segment == null || segment.kind !== "identifier") {
|
|
2593
|
+
break;
|
|
2594
|
+
}
|
|
2595
|
+
if (segment.value === "length" && !this.stream.isPunctuation("(", 2)) {
|
|
2596
|
+
this.stream.next();
|
|
2597
|
+
this.stream.next();
|
|
2598
|
+
receiver = {
|
|
2599
|
+
kind: "arithmetic",
|
|
2600
|
+
call: "length",
|
|
2601
|
+
left: receiver,
|
|
2602
|
+
right: noArgument()
|
|
2603
|
+
};
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
const transform = TRANSFORM_METHODS[segment.value];
|
|
2607
|
+
if (transform != null) {
|
|
2608
|
+
this.stream.next();
|
|
2609
|
+
this.stream.next();
|
|
2610
|
+
this.stream.expectPunctuation("(");
|
|
2611
|
+
this.stream.expectPunctuation(")");
|
|
2612
|
+
receiver = {
|
|
2613
|
+
kind: "arithmetic",
|
|
2614
|
+
call: transform.transformer,
|
|
2615
|
+
left: receiver,
|
|
2616
|
+
right: transform.locale == null ? noArgument() : {
|
|
2617
|
+
kind: "value",
|
|
2618
|
+
value: transform.locale,
|
|
2619
|
+
transformer: null,
|
|
2620
|
+
locale: null
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
// A comparator method needs a property target, which only an ungrouped chain produces
|
|
2626
|
+
if (COMPARATOR_METHODS[segment.value] != null && receiver.kind === "property") {
|
|
2627
|
+
this.stream.next();
|
|
2628
|
+
this.stream.next();
|
|
2629
|
+
this.stream.expectPunctuation("(");
|
|
2630
|
+
const argument = this.parseOperand();
|
|
2631
|
+
this.stream.expectPunctuation(")");
|
|
2632
|
+
if (argument.kind !== "property" && argument.kind !== "value" && argument.kind !== "param") {
|
|
2633
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'.${segment.value}()' on that argument`));
|
|
2634
|
+
}
|
|
2635
|
+
return {
|
|
2636
|
+
kind: "method-call",
|
|
2637
|
+
target: receiver,
|
|
2638
|
+
method: segment.value,
|
|
2639
|
+
argument
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
break;
|
|
2643
|
+
}
|
|
2644
|
+
return receiver;
|
|
2645
|
+
}
|
|
1386
2646
|
withValueTransformer(operand) {
|
|
1387
2647
|
if (this.stream.isPunctuation(".")) {
|
|
1388
2648
|
const method = this.stream.peek(1);
|
|
@@ -1416,12 +2676,22 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1416
2676
|
if (right.kind === "method-call") {
|
|
1417
2677
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("method call on the right side of a comparison"));
|
|
1418
2678
|
}
|
|
1419
|
-
if (left
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
2679
|
+
if (needsBrackets(left) || needsBrackets(right)) {
|
|
2680
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a bitwise or nullish operator compared without brackets, which JavaScript reads the other way round"));
|
|
2681
|
+
}
|
|
2682
|
+
if (left.kind === "arithmetic" || right.kind === "arithmetic" || left.kind === "conditional" || right.kind === "conditional") {
|
|
2683
|
+
if (containsProperty(left) === false && containsProperty(right) === false) {
|
|
2684
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic that references no schema property"));
|
|
1424
2685
|
}
|
|
2686
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2687
|
+
comparator: operator.comparator,
|
|
2688
|
+
negated: operator.negated,
|
|
2689
|
+
strict: operator.strict,
|
|
2690
|
+
left: this.createOperandExpression(left),
|
|
2691
|
+
right: this.createOperandExpression(right)
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
if (left.kind === "property" && right.kind === "property") {
|
|
1425
2695
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
1426
2696
|
comparator: operator.comparator,
|
|
1427
2697
|
negated: operator.negated,
|
|
@@ -1430,18 +2700,63 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1430
2700
|
right: this.createPropertyExpression(right)
|
|
1431
2701
|
});
|
|
1432
2702
|
}
|
|
2703
|
+
// Only a loose comparison coerces. `===` records `strict` and honouring it is the point.
|
|
1433
2704
|
if (left.kind === "property" && right.kind !== "property") {
|
|
1434
|
-
return this.buildPropertyComparator(left, operator, right, /* applyConverter */
|
|
2705
|
+
return this.buildPropertyComparator(left, operator, right, /* applyConverter */ !operator.strict);
|
|
1435
2706
|
}
|
|
1436
2707
|
if (right.kind === "property" && left.kind !== "property") {
|
|
1437
2708
|
const swapped = {
|
|
1438
2709
|
...operator,
|
|
1439
2710
|
comparator: SWAPPED_COMPARATORS[operator.comparator]
|
|
1440
2711
|
};
|
|
1441
|
-
return this.buildPropertyComparator(right, swapped, left, /* applyConverter */
|
|
2712
|
+
return this.buildPropertyComparator(right, swapped, left, /* applyConverter */ !operator.strict);
|
|
2713
|
+
}
|
|
2714
|
+
const settled = this.settleConstantComparison(left, operator, right);
|
|
2715
|
+
if (settled != null) {
|
|
2716
|
+
return settled;
|
|
1442
2717
|
}
|
|
1443
2718
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison requires a schema property on at least one side"));
|
|
1444
2719
|
}
|
|
2720
|
+
/**
|
|
2721
|
+
* The answer a comparison of two constants gives, when that answer is `true`. The other answer
|
|
2722
|
+
* excludes every row, which has no expression node.
|
|
2723
|
+
*/ settleConstantComparison(left, operator, right) {
|
|
2724
|
+
const leftValue = this.constantOf(left);
|
|
2725
|
+
const rightValue = this.constantOf(right);
|
|
2726
|
+
if (leftValue === UNKNOWN_UNTIL_ROW || rightValue === UNKNOWN_UNTIL_ROW) {
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
const answer = (0,_evaluate__rspack_import_3/* .evaluate */._3)(new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2730
|
+
comparator: operator.comparator,
|
|
2731
|
+
negated: operator.negated,
|
|
2732
|
+
strict: operator.strict,
|
|
2733
|
+
left: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2734
|
+
value: leftValue
|
|
2735
|
+
}),
|
|
2736
|
+
right: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2737
|
+
value: rightValue
|
|
2738
|
+
})
|
|
2739
|
+
}), {});
|
|
2740
|
+
if (answer === true) {
|
|
2741
|
+
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
2742
|
+
}
|
|
2743
|
+
// Params decided this, so the refusal must not be cached against the source: the same filter
|
|
2744
|
+
// with other params can be a tautology.
|
|
2745
|
+
if (left.kind === "param" || right.kind === "param") {
|
|
2746
|
+
throw new ParamDependentParseError(ERROR_MESSAGES.UNSUPPORTED("a params comparison no row satisfies"));
|
|
2747
|
+
}
|
|
2748
|
+
return null;
|
|
2749
|
+
}
|
|
2750
|
+
/** The value an operand holds already, for the operands that do not depend on a row. */ constantOf(operand) {
|
|
2751
|
+
if (operand.kind === "value" && operand.transformer == null) {
|
|
2752
|
+
return operand.value;
|
|
2753
|
+
}
|
|
2754
|
+
if (operand.kind === "param" && operand.transformer == null) {
|
|
2755
|
+
this.structurallyDependsOnParams = true;
|
|
2756
|
+
return resolveParamPath(this.paramsName ?? "params", operand.path, this.params);
|
|
2757
|
+
}
|
|
2758
|
+
return UNKNOWN_UNTIL_ROW;
|
|
2759
|
+
}
|
|
1445
2760
|
buildStandalone(operand) {
|
|
1446
2761
|
if (operand.kind === "method-call") {
|
|
1447
2762
|
return this.buildMethodComparator(operand);
|
|
@@ -1464,6 +2779,21 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1464
2779
|
locale: null
|
|
1465
2780
|
}, /* applyConverter */ true);
|
|
1466
2781
|
}
|
|
2782
|
+
// A boolean-valued call standing alone IS the predicate
|
|
2783
|
+
if (operand.kind === "arithmetic" && operand.call === "matches") {
|
|
2784
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2785
|
+
comparator: "equals",
|
|
2786
|
+
negated: false,
|
|
2787
|
+
strict: false,
|
|
2788
|
+
left: this.createOperandExpression(operand),
|
|
2789
|
+
right: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2790
|
+
value: true
|
|
2791
|
+
})
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
if (operand.kind === "arithmetic" || operand.kind === "conditional") {
|
|
2795
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic used as a condition rather than compared"));
|
|
2796
|
+
}
|
|
1467
2797
|
// Constant `true` — a tautology, which parseAnd/parseOr simplify away
|
|
1468
2798
|
if (operand.kind === "value" && operand.value === true && operand.transformer == null) {
|
|
1469
2799
|
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
@@ -1516,12 +2846,6 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1516
2846
|
right: this.createValueExpression(value, null, /* applyConverter */ false)
|
|
1517
2847
|
});
|
|
1518
2848
|
}
|
|
1519
|
-
// Casing transformers on a property are only meaningful with string-matching
|
|
1520
|
-
// comparators; on relational comparators the plugins would silently
|
|
1521
|
-
// ignore them and return wrong data
|
|
1522
|
-
if (property.transformer != null && !isStringMatch) {
|
|
1523
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("transform method outside of startsWith/endsWith/includes"));
|
|
1524
|
-
}
|
|
1525
2849
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
1526
2850
|
comparator: operator.comparator,
|
|
1527
2851
|
negated: operator.negated,
|
|
@@ -1530,31 +2854,60 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1530
2854
|
right: this.createValueExpression(value, property.property, applyConverter)
|
|
1531
2855
|
});
|
|
1532
2856
|
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Any operand as an expression.
|
|
2859
|
+
*
|
|
2860
|
+
* Values inside arithmetic take no paired property: the result is a computed number, so the
|
|
2861
|
+
* property's serializer and type converter do not describe it — the same reason `.length` skips
|
|
2862
|
+
* them.
|
|
2863
|
+
*/ createOperandExpression(operand) {
|
|
2864
|
+
if (operand.kind === "conditional") {
|
|
2865
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2866
|
+
call: "conditional",
|
|
2867
|
+
expression: operand.condition,
|
|
2868
|
+
arguments: [
|
|
2869
|
+
this.createOperandExpression(operand.whenTrue),
|
|
2870
|
+
this.createOperandExpression(operand.whenFalse)
|
|
2871
|
+
]
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
if (operand.kind === "arithmetic") {
|
|
2875
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2876
|
+
call: operand.call,
|
|
2877
|
+
expression: this.createOperandExpression(operand.left),
|
|
2878
|
+
arguments: operand.right === NO_ARGUMENT ? [] : operand.extra == null ? [
|
|
2879
|
+
this.createOperandExpression(operand.right)
|
|
2880
|
+
] : [
|
|
2881
|
+
this.createOperandExpression(operand.right),
|
|
2882
|
+
this.createOperandExpression(operand.extra)
|
|
2883
|
+
]
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
if (operand.kind === "property") {
|
|
2887
|
+
return this.createPropertyExpression(operand);
|
|
2888
|
+
}
|
|
2889
|
+
if (operand.kind === "method-call") {
|
|
2890
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a method call inside arithmetic"));
|
|
2891
|
+
}
|
|
2892
|
+
return this.createValueExpression(operand, null, /* applyConverter */ false);
|
|
2893
|
+
}
|
|
1533
2894
|
createPropertyExpression(operand) {
|
|
1534
|
-
|
|
2895
|
+
return asCall(new _types__rspack_import_1/* .PropertyExpression */.ep({
|
|
1535
2896
|
property: operand.property
|
|
1536
|
-
});
|
|
1537
|
-
expression.transformer = operand.transformer;
|
|
1538
|
-
expression.locale = operand.locale;
|
|
1539
|
-
return expression;
|
|
2897
|
+
}), operand.transformer, operand.locale);
|
|
1540
2898
|
}
|
|
1541
2899
|
createValueExpression(operand, pairedProperty, applyConverter) {
|
|
1542
2900
|
if (operand.kind === "param") {
|
|
1543
|
-
|
|
2901
|
+
return asCall(new ParamReferenceExpression({
|
|
1544
2902
|
paramPath: operand.path,
|
|
1545
2903
|
pairedProperty,
|
|
1546
2904
|
applyConverter
|
|
1547
|
-
});
|
|
1548
|
-
expression.transformer = operand.transformer;
|
|
1549
|
-
expression.locale = operand.locale;
|
|
1550
|
-
return expression;
|
|
2905
|
+
}), operand.transformer, operand.locale);
|
|
1551
2906
|
}
|
|
1552
2907
|
const expression = new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1553
2908
|
value: resolvePairedValue(operand.value, pairedProperty, applyConverter)
|
|
1554
2909
|
});
|
|
1555
|
-
expression.transformer
|
|
1556
|
-
expression.locale = operand.locale;
|
|
1557
|
-
return expression;
|
|
2910
|
+
return asCall(expression, operand.transformer, operand.locale);
|
|
1558
2911
|
}
|
|
1559
2912
|
}
|
|
1560
2913
|
// #endregion
|
|
@@ -1566,28 +2919,19 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1566
2919
|
*/ const bindExpression = (expression, paramsName, params)=>{
|
|
1567
2920
|
if (expression instanceof ParamReferenceExpression) {
|
|
1568
2921
|
const raw = resolveParamPath(paramsName ?? "params", expression.paramPath, params);
|
|
1569
|
-
|
|
2922
|
+
return new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1570
2923
|
value: resolvePairedValue(raw, expression.pairedProperty, expression.applyConverter)
|
|
1571
2924
|
});
|
|
1572
|
-
bound.transformer = expression.transformer;
|
|
1573
|
-
bound.locale = expression.locale;
|
|
1574
|
-
return bound;
|
|
1575
2925
|
}
|
|
1576
2926
|
if (expression instanceof _types__rspack_import_1/* .ValueExpression */.Ko) {
|
|
1577
|
-
|
|
2927
|
+
return new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1578
2928
|
value: expression.value
|
|
1579
2929
|
});
|
|
1580
|
-
clone.transformer = expression.transformer;
|
|
1581
|
-
clone.locale = expression.locale;
|
|
1582
|
-
return clone;
|
|
1583
2930
|
}
|
|
1584
2931
|
if (expression instanceof _types__rspack_import_1/* .PropertyExpression */.ep) {
|
|
1585
|
-
|
|
2932
|
+
return new _types__rspack_import_1/* .PropertyExpression */.ep({
|
|
1586
2933
|
property: expression.property
|
|
1587
2934
|
});
|
|
1588
|
-
clone.transformer = expression.transformer;
|
|
1589
|
-
clone.locale = expression.locale;
|
|
1590
|
-
return clone;
|
|
1591
2935
|
}
|
|
1592
2936
|
if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
|
|
1593
2937
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
@@ -1605,8 +2949,99 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1605
2949
|
right: expression.right ? bindExpression(expression.right, paramsName, params) : undefined
|
|
1606
2950
|
});
|
|
1607
2951
|
}
|
|
2952
|
+
if (expression instanceof _types__rspack_import_1/* .CallExpression */.DG) {
|
|
2953
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2954
|
+
call: expression.call,
|
|
2955
|
+
expression: bindExpression(expression.expression, paramsName, params),
|
|
2956
|
+
arguments: expression.arguments.map((argument)=>bindExpression(argument, paramsName, params))
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
1608
2959
|
return expression;
|
|
1609
2960
|
};
|
|
2961
|
+
/**
|
|
2962
|
+
* Wraps an operand in the call a transform method named, if there was one.
|
|
2963
|
+
*
|
|
2964
|
+
* `Transformer` and `Call` share these three names, so the transform IS the call name. A locale
|
|
2965
|
+
* becomes the call's first argument, which is where it belongs — it qualifies the casing, not the
|
|
2966
|
+
* property.
|
|
2967
|
+
*/ const asCall = (inner, transformer, locale)=>{
|
|
2968
|
+
if (transformer == null) {
|
|
2969
|
+
return inner;
|
|
2970
|
+
}
|
|
2971
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2972
|
+
call: transformer,
|
|
2973
|
+
expression: inner,
|
|
2974
|
+
arguments: locale == null ? [] : [
|
|
2975
|
+
new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2976
|
+
value: locale
|
|
2977
|
+
})
|
|
2978
|
+
]
|
|
2979
|
+
});
|
|
2980
|
+
};
|
|
2981
|
+
/** Binds every name a destructuring pattern introduces to the path it reads. */ const bindPattern = (stream, kind, path, scope)=>{
|
|
2982
|
+
if (!stream.matchPunctuation("{")) {
|
|
2983
|
+
const name = stream.next();
|
|
2984
|
+
if (name.kind !== "identifier") {
|
|
2985
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`parameter '${name.value}'`));
|
|
2986
|
+
}
|
|
2987
|
+
scope.set(name.value, {
|
|
2988
|
+
kind,
|
|
2989
|
+
path
|
|
2990
|
+
});
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
while(!stream.matchPunctuation("}")){
|
|
2994
|
+
const key = stream.next();
|
|
2995
|
+
if (key.kind !== "identifier") {
|
|
2996
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`destructured key '${key.value}'`));
|
|
2997
|
+
}
|
|
2998
|
+
if (stream.matchPunctuation(":")) {
|
|
2999
|
+
bindPattern(stream, kind, [
|
|
3000
|
+
...path,
|
|
3001
|
+
key.value
|
|
3002
|
+
], scope);
|
|
3003
|
+
} else {
|
|
3004
|
+
scope.set(key.value, {
|
|
3005
|
+
kind,
|
|
3006
|
+
path: [
|
|
3007
|
+
...path,
|
|
3008
|
+
key.value
|
|
3009
|
+
]
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
if (!stream.matchPunctuation(",")) {
|
|
3013
|
+
stream.expectPunctuation("}");
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
/** Reads a filter's parameter list — the entity alone, or the `[entity, params]` pair — into a scope. */ const buildScope = (parameterNames, hasParams)=>{
|
|
3019
|
+
const stream = new TokenStream(tokenize(parameterNames));
|
|
3020
|
+
const scope = new Map();
|
|
3021
|
+
if (!stream.matchPunctuation("[")) {
|
|
3022
|
+
bindPattern(stream, "property", [], scope);
|
|
3023
|
+
return {
|
|
3024
|
+
scope,
|
|
3025
|
+
paramsName: null
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
bindPattern(stream, "property", [], scope);
|
|
3029
|
+
if (hasParams && stream.matchPunctuation(",") && !stream.isPunctuation("]")) {
|
|
3030
|
+
bindPattern(stream, "param", [], scope);
|
|
3031
|
+
}
|
|
3032
|
+
return {
|
|
3033
|
+
scope,
|
|
3034
|
+
paramsName: wholeParamsName(scope)
|
|
3035
|
+
};
|
|
3036
|
+
};
|
|
3037
|
+
/** The name the whole params object was given, when it was not destructured. Error messages only. */ const wholeParamsName = (scope)=>{
|
|
3038
|
+
for (const [name, binding] of scope){
|
|
3039
|
+
if (binding.kind === "param" && binding.path.length === 0) {
|
|
3040
|
+
return name;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return null;
|
|
3044
|
+
};
|
|
1610
3045
|
/**
|
|
1611
3046
|
* Splits stringified filter source into parameter names and the expression
|
|
1612
3047
|
* body, unwrapping single-return block bodies.
|
|
@@ -1636,33 +3071,12 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1636
3071
|
parameterNames = parameterNames.slice(1, -1).trim();
|
|
1637
3072
|
}
|
|
1638
3073
|
}
|
|
1639
|
-
|
|
1640
|
-
let paramsName = null;
|
|
1641
|
-
if (parameterNames.startsWith("[") && parameterNames.endsWith("]")) {
|
|
1642
|
-
const destructured = parameterNames.slice(1, -1).split(",").map((w)=>w.trim());
|
|
1643
|
-
entityName = destructured[0];
|
|
1644
|
-
if (hasParams) {
|
|
1645
|
-
paramsName = destructured[1] ?? null;
|
|
1646
|
-
}
|
|
1647
|
-
} else {
|
|
1648
|
-
entityName = parameterNames;
|
|
1649
|
-
}
|
|
1650
|
-
if (entityName == null || entityName.length === 0) {
|
|
3074
|
+
if (parameterNames.length === 0) {
|
|
1651
3075
|
throw new Error("Invalid Function");
|
|
1652
3076
|
}
|
|
1653
|
-
|
|
1654
|
-
if (body.startsWith("{")) {
|
|
1655
|
-
const inner = body.slice(1, body.lastIndexOf("}")).trim();
|
|
1656
|
-
if (!inner.startsWith("return")) {
|
|
1657
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("block body without a single return statement"));
|
|
1658
|
-
}
|
|
1659
|
-
body = inner.slice("return".length).trim();
|
|
1660
|
-
if (body.endsWith(";")) {
|
|
1661
|
-
body = body.slice(0, -1).trim();
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
3077
|
+
const { scope, paramsName } = buildScope(parameterNames, hasParams);
|
|
1664
3078
|
return {
|
|
1665
|
-
|
|
3079
|
+
scope,
|
|
1666
3080
|
paramsName,
|
|
1667
3081
|
body
|
|
1668
3082
|
};
|
|
@@ -1730,17 +3144,27 @@ const combineExpressions = (...expressions)=>{
|
|
|
1730
3144
|
*/ const parseFragment = (schema, body, rootName)=>{
|
|
1731
3145
|
try {
|
|
1732
3146
|
const stream = new TokenStream(tokenize(body));
|
|
1733
|
-
const
|
|
1734
|
-
|
|
3147
|
+
const scope = new Map([
|
|
3148
|
+
[
|
|
3149
|
+
rootName,
|
|
3150
|
+
{
|
|
3151
|
+
kind: "property",
|
|
3152
|
+
path: []
|
|
3153
|
+
}
|
|
3154
|
+
]
|
|
3155
|
+
]);
|
|
3156
|
+
const parser = new ExpressionParser(schema, stream, scope, null, undefined);
|
|
3157
|
+
return foldConstantCalls(parser.parse());
|
|
1735
3158
|
} catch {
|
|
1736
3159
|
// The failure is expected and informative — see above — so it is not logged. A caller that
|
|
1737
3160
|
// parses one conjunct against two schemas would otherwise warn on every successful split.
|
|
1738
3161
|
return Expression.NOT_PARSABLE;
|
|
1739
3162
|
}
|
|
1740
3163
|
};
|
|
3164
|
+
/** What the parser refused, from a throw that may not be an `Error`. */ const refusalOf = (error)=>error instanceof Error ? error.message : String(error);
|
|
1741
3165
|
const toExpression = (schema, fn, params)=>{
|
|
1742
3166
|
const stringifiedFunction = fn.toString();
|
|
1743
|
-
const warn = (error)=>
|
|
3167
|
+
const warn = (error)=>_utilities__rspack_import_4/* .logger.warn */.vF.warn("Error parsing expression", {
|
|
1744
3168
|
error,
|
|
1745
3169
|
collectionName: schema.collectionName,
|
|
1746
3170
|
params,
|
|
@@ -1748,16 +3172,17 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1748
3172
|
});
|
|
1749
3173
|
const cached = getCachedTemplate(schema, stringifiedFunction);
|
|
1750
3174
|
if (cached != null) {
|
|
1751
|
-
// A cached failure — the warning was already logged when it was discovered
|
|
3175
|
+
// A cached failure — the warning was already logged when it was discovered. The template
|
|
3176
|
+
// carries what was refused, and `.explain()` is usually called once the cache is warm.
|
|
1752
3177
|
if (_types__rspack_import_1/* .Expression.isNotParsable */.r4.isNotParsable(cached.template)) {
|
|
1753
|
-
return
|
|
3178
|
+
return cached.template;
|
|
1754
3179
|
}
|
|
1755
3180
|
try {
|
|
1756
|
-
return bindExpression(cached.template, cached.paramsName, params);
|
|
3181
|
+
return (0,_fold__rspack_import_5/* .foldConstantCalls */.F5)(bindExpression(cached.template, cached.paramsName, params));
|
|
1757
3182
|
} catch (error) {
|
|
1758
3183
|
// Binding failures are param-dependent by nature — never cached
|
|
1759
3184
|
warn(error);
|
|
1760
|
-
return _types__rspack_import_1/* .Expression.
|
|
3185
|
+
return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1761
3186
|
}
|
|
1762
3187
|
}
|
|
1763
3188
|
let paramsName = null;
|
|
@@ -1766,22 +3191,23 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1766
3191
|
try {
|
|
1767
3192
|
const shape = resolveFunctionShape(stringifiedFunction, params != null);
|
|
1768
3193
|
const stream = new TokenStream(tokenize(shape.body));
|
|
1769
|
-
const parser = new ExpressionParser(schema, stream, shape.
|
|
3194
|
+
const parser = new ExpressionParser(schema, stream, shape.scope, shape.paramsName, params);
|
|
1770
3195
|
paramsName = shape.paramsName;
|
|
1771
|
-
template = parser.
|
|
3196
|
+
template = parser.parseBody();
|
|
1772
3197
|
structurallyDependsOnParams = parser.structurallyDependsOnParams;
|
|
1773
3198
|
} catch (error) {
|
|
1774
3199
|
// Cache the failure so a hot query on an unsupported filter doesn't
|
|
1775
3200
|
// re-parse and re-warn on every execution. Param-dependent failures are
|
|
1776
3201
|
// exempt: the same source can succeed with different params.
|
|
3202
|
+
const refused = _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1777
3203
|
if (!(error instanceof ParamDependentParseError)) {
|
|
1778
3204
|
setCachedTemplate(schema, stringifiedFunction, {
|
|
1779
|
-
template:
|
|
3205
|
+
template: refused,
|
|
1780
3206
|
paramsName: null
|
|
1781
3207
|
});
|
|
1782
3208
|
}
|
|
1783
3209
|
warn(error);
|
|
1784
|
-
return
|
|
3210
|
+
return refused;
|
|
1785
3211
|
}
|
|
1786
3212
|
// Templates whose structure was resolved from param values are only
|
|
1787
3213
|
// valid for this exact params object — parse those fresh every time
|
|
@@ -1792,10 +3218,10 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1792
3218
|
});
|
|
1793
3219
|
}
|
|
1794
3220
|
try {
|
|
1795
|
-
return bindExpression(template, paramsName, params);
|
|
3221
|
+
return (0,_fold__rspack_import_5/* .foldConstantCalls */.F5)(bindExpression(template, paramsName, params));
|
|
1796
3222
|
} catch (error) {
|
|
1797
3223
|
warn(error);
|
|
1798
|
-
return _types__rspack_import_1/* .Expression.
|
|
3224
|
+
return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1799
3225
|
}
|
|
1800
3226
|
};
|
|
1801
3227
|
|
|
@@ -1803,6 +3229,7 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1803
3229
|
},
|
|
1804
3230
|
27(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1805
3231
|
__webpack_require__.d(__webpack_exports__, {
|
|
3232
|
+
DG: () => (CallExpression),
|
|
1806
3233
|
Ko: () => (ValueExpression),
|
|
1807
3234
|
bQ: () => (ComparatorExpression),
|
|
1808
3235
|
ep: () => (PropertyExpression),
|
|
@@ -1812,59 +3239,69 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
1812
3239
|
const valueToJson = (value)=>{
|
|
1813
3240
|
if (value === undefined) {
|
|
1814
3241
|
return {
|
|
1815
|
-
|
|
3242
|
+
undefined: true
|
|
1816
3243
|
};
|
|
1817
3244
|
}
|
|
1818
3245
|
if (value === null) {
|
|
1819
|
-
return
|
|
1820
|
-
k: "raw",
|
|
1821
|
-
v: null
|
|
1822
|
-
};
|
|
3246
|
+
return null;
|
|
1823
3247
|
}
|
|
1824
3248
|
if (value instanceof Date) {
|
|
1825
3249
|
// ISO rather than epoch millis: it survives a human reading the payload, and an invalid
|
|
1826
3250
|
// Date has no ISO form — so it is caught here rather than becoming a silent `null`.
|
|
1827
3251
|
return {
|
|
1828
|
-
|
|
1829
|
-
v: value.toISOString()
|
|
3252
|
+
date: value.toISOString()
|
|
1830
3253
|
};
|
|
1831
3254
|
}
|
|
1832
3255
|
if (Array.isArray(value)) {
|
|
1833
|
-
return
|
|
1834
|
-
k: "array",
|
|
1835
|
-
v: value.map(valueToJson)
|
|
1836
|
-
};
|
|
3256
|
+
return value.map(valueToJson);
|
|
1837
3257
|
}
|
|
1838
3258
|
if (typeof value === "number" && Number.isFinite(value) === false) {
|
|
1839
3259
|
// `JSON.stringify` turns all three of these into `null`, which would compare as a different
|
|
1840
3260
|
// value entirely rather than failing.
|
|
1841
3261
|
return {
|
|
1842
|
-
|
|
1843
|
-
v: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
|
|
3262
|
+
number: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
|
|
1844
3263
|
};
|
|
1845
3264
|
}
|
|
1846
3265
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
3266
|
+
return value;
|
|
3267
|
+
}
|
|
3268
|
+
if (value instanceof RegExp) {
|
|
3269
|
+
return {
|
|
3270
|
+
regex: {
|
|
3271
|
+
source: value.source,
|
|
3272
|
+
flags: value.flags
|
|
3273
|
+
}
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
// `JSON.stringify` throws outright on a bigint rather than losing it quietly, so this is the one
|
|
3277
|
+
// tag that turns a crash into a value
|
|
3278
|
+
if (typeof value === "bigint") {
|
|
1847
3279
|
return {
|
|
1848
|
-
|
|
1849
|
-
v: value
|
|
3280
|
+
bigint: value.toString()
|
|
1850
3281
|
};
|
|
1851
3282
|
}
|
|
1852
3283
|
throw new Error(`Cannot serialize this filter value: only strings, numbers, booleans, null, undefined, Dates and arrays of those can cross a wire. ` + `Received: ${Object.prototype.toString.call(value)}`);
|
|
1853
3284
|
};
|
|
1854
3285
|
const valueFromJson = (value)=>{
|
|
1855
|
-
if (value
|
|
1856
|
-
return
|
|
3286
|
+
if (value === null || typeof value !== "object") {
|
|
3287
|
+
return value;
|
|
1857
3288
|
}
|
|
1858
|
-
if (value
|
|
1859
|
-
return
|
|
3289
|
+
if (Array.isArray(value)) {
|
|
3290
|
+
return value.map(valueFromJson);
|
|
3291
|
+
}
|
|
3292
|
+
if ("date" in value) {
|
|
3293
|
+
return new Date(value.date);
|
|
3294
|
+
}
|
|
3295
|
+
if ("undefined" in value) {
|
|
3296
|
+
return undefined;
|
|
1860
3297
|
}
|
|
1861
|
-
if (
|
|
1862
|
-
return value.
|
|
3298
|
+
if ("regex" in value) {
|
|
3299
|
+
return new RegExp(value.regex.source, value.regex.flags);
|
|
1863
3300
|
}
|
|
1864
|
-
if (
|
|
1865
|
-
return value.
|
|
3301
|
+
if ("bigint" in value) {
|
|
3302
|
+
return BigInt(value.bigint);
|
|
1866
3303
|
}
|
|
1867
|
-
return value.
|
|
3304
|
+
return value.number === "NaN" ? Number.NaN : value.number === "Infinity" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
1868
3305
|
};
|
|
1869
3306
|
/**
|
|
1870
3307
|
* The base class for all expression types.
|
|
@@ -1881,6 +3318,9 @@ const valueFromJson = (value)=>{
|
|
|
1881
3318
|
static get NOT_PARSABLE() {
|
|
1882
3319
|
return new NotParsableExpression();
|
|
1883
3320
|
}
|
|
3321
|
+
/** `NOT_PARSABLE`, carrying what the parser refused. */ static notParsable(reason) {
|
|
3322
|
+
return new NotParsableExpression(reason);
|
|
3323
|
+
}
|
|
1884
3324
|
static isEmpty(expression) {
|
|
1885
3325
|
return expression.type === "empty" || expression instanceof EmptyExpression;
|
|
1886
3326
|
}
|
|
@@ -1897,7 +3337,7 @@ const valueFromJson = (value)=>{
|
|
|
1897
3337
|
*
|
|
1898
3338
|
* ## Why it is this small
|
|
1899
3339
|
*
|
|
1900
|
-
* Of the
|
|
3340
|
+
* Of the seven node types a bound tree can contain, exactly one holds anything JSON cannot carry:
|
|
1901
3341
|
* `PropertyExpression`, whose live `PropertyInfo` has functions, a parent chain and caches. It
|
|
1902
3342
|
* reduces to a property PATH — `PropertyInfo.id` IS the dotted path, and `getProperty` is keyed by
|
|
1903
3343
|
* exactly that — so rebinding is one lookup.
|
|
@@ -1912,7 +3352,7 @@ const valueFromJson = (value)=>{
|
|
|
1912
3352
|
if (expression.type === "operator") {
|
|
1913
3353
|
const operator = expression;
|
|
1914
3354
|
return {
|
|
1915
|
-
|
|
3355
|
+
type: "operator",
|
|
1916
3356
|
operator: operator.operator,
|
|
1917
3357
|
...operator.left != null && {
|
|
1918
3358
|
left: Expression.toJson(operator.left)
|
|
@@ -1925,7 +3365,7 @@ const valueFromJson = (value)=>{
|
|
|
1925
3365
|
if (expression.type === "comparator") {
|
|
1926
3366
|
const comparator = expression;
|
|
1927
3367
|
return {
|
|
1928
|
-
|
|
3368
|
+
type: "comparator",
|
|
1929
3369
|
comparator: comparator.comparator,
|
|
1930
3370
|
negated: comparator.negated,
|
|
1931
3371
|
strict: comparator.strict,
|
|
@@ -1937,29 +3377,41 @@ const valueFromJson = (value)=>{
|
|
|
1937
3377
|
}
|
|
1938
3378
|
};
|
|
1939
3379
|
}
|
|
3380
|
+
if (expression.type === "call") {
|
|
3381
|
+
const call = expression;
|
|
3382
|
+
return {
|
|
3383
|
+
type: "call",
|
|
3384
|
+
call: call.call,
|
|
3385
|
+
expression: Expression.toJson(call.expression),
|
|
3386
|
+
arguments: call.arguments.map(Expression.toJson)
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
1940
3389
|
if (expression.type === "property") {
|
|
1941
3390
|
const property = expression;
|
|
1942
3391
|
return {
|
|
1943
|
-
|
|
3392
|
+
type: "property",
|
|
1944
3393
|
// The dotted path, which is exactly the key `getProperty` is looking up
|
|
1945
|
-
path: property.property.id
|
|
1946
|
-
transformer: property.transformer,
|
|
1947
|
-
locale: property.locale
|
|
3394
|
+
path: property.property.id
|
|
1948
3395
|
};
|
|
1949
3396
|
}
|
|
1950
3397
|
if (expression.type === "value") {
|
|
1951
3398
|
const value = expression;
|
|
1952
3399
|
return {
|
|
1953
|
-
|
|
1954
|
-
value: valueToJson(value.value)
|
|
1955
|
-
|
|
1956
|
-
|
|
3400
|
+
type: "value",
|
|
3401
|
+
value: valueToJson(value.value)
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
if (expression.type === "empty") {
|
|
3405
|
+
return {
|
|
3406
|
+
type: "empty"
|
|
1957
3407
|
};
|
|
1958
3408
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
3409
|
+
const reason = expression.reason;
|
|
3410
|
+
return reason == null ? {
|
|
3411
|
+
type: "not-parsable"
|
|
1961
3412
|
} : {
|
|
1962
|
-
|
|
3413
|
+
type: "not-parsable",
|
|
3414
|
+
reason
|
|
1963
3415
|
};
|
|
1964
3416
|
}
|
|
1965
3417
|
/**
|
|
@@ -1975,14 +3427,14 @@ const valueFromJson = (value)=>{
|
|
|
1975
3427
|
* failure here worse than an error.
|
|
1976
3428
|
*/ static fromJson(json, schema) {
|
|
1977
3429
|
const child = (node)=>node == null ? undefined : Expression.fromJson(node, schema);
|
|
1978
|
-
if (json.
|
|
3430
|
+
if (json.type === "operator") {
|
|
1979
3431
|
return new OperatorExpression({
|
|
1980
3432
|
operator: json.operator,
|
|
1981
3433
|
left: child(json.left),
|
|
1982
3434
|
right: child(json.right)
|
|
1983
3435
|
});
|
|
1984
3436
|
}
|
|
1985
|
-
if (json.
|
|
3437
|
+
if (json.type === "comparator") {
|
|
1986
3438
|
return new ComparatorExpression({
|
|
1987
3439
|
comparator: json.comparator,
|
|
1988
3440
|
negated: json.negated,
|
|
@@ -1991,27 +3443,34 @@ const valueFromJson = (value)=>{
|
|
|
1991
3443
|
right: child(json.right)
|
|
1992
3444
|
});
|
|
1993
3445
|
}
|
|
1994
|
-
if (json.
|
|
3446
|
+
if (json.type === "call") {
|
|
3447
|
+
if (json.expression == null) {
|
|
3448
|
+
throw new Error(`Cannot deserialize a filter: a '${json.call}' call carries no operand. ` + `Collection: ${schema.collectionName}.`);
|
|
3449
|
+
}
|
|
3450
|
+
return new CallExpression({
|
|
3451
|
+
call: json.call,
|
|
3452
|
+
expression: Expression.fromJson(json.expression, schema),
|
|
3453
|
+
arguments: (json.arguments ?? []).map((argument)=>Expression.fromJson(argument, schema))
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3456
|
+
if (json.type === "property") {
|
|
1995
3457
|
const property = schema.getProperty(json.path);
|
|
1996
3458
|
if (property == null) {
|
|
1997
3459
|
throw new Error(`Cannot deserialize a filter: this schema does not declare the property it names. ` + `Property: ${json.path}, Collection: ${schema.collectionName}. ` + `The two sides disagree about the shape of the data, so the filter cannot be applied.`);
|
|
1998
3460
|
}
|
|
1999
|
-
|
|
3461
|
+
return new PropertyExpression({
|
|
2000
3462
|
property
|
|
2001
3463
|
});
|
|
2002
|
-
rebuilt.transformer = json.transformer;
|
|
2003
|
-
rebuilt.locale = json.locale;
|
|
2004
|
-
return rebuilt;
|
|
2005
3464
|
}
|
|
2006
|
-
if (json.
|
|
2007
|
-
|
|
3465
|
+
if (json.type === "value") {
|
|
3466
|
+
return new ValueExpression({
|
|
2008
3467
|
value: valueFromJson(json.value)
|
|
2009
3468
|
});
|
|
2010
|
-
rebuilt.transformer = json.transformer;
|
|
2011
|
-
rebuilt.locale = json.locale;
|
|
2012
|
-
return rebuilt;
|
|
2013
3469
|
}
|
|
2014
|
-
|
|
3470
|
+
if (json.type === "empty") {
|
|
3471
|
+
return Expression.EMPTY;
|
|
3472
|
+
}
|
|
3473
|
+
return json.reason == null ? Expression.NOT_PARSABLE : Expression.notParsable(json.reason);
|
|
2015
3474
|
}
|
|
2016
3475
|
}
|
|
2017
3476
|
class EmptyExpression extends Expression {
|
|
@@ -2019,6 +3478,11 @@ class EmptyExpression extends Expression {
|
|
|
2019
3478
|
}
|
|
2020
3479
|
class NotParsableExpression extends Expression {
|
|
2021
3480
|
type = "not-parsable";
|
|
3481
|
+
/** What the parser refused, when it knows. `.explain()` prints it beside the source. */ reason;
|
|
3482
|
+
constructor(reason){
|
|
3483
|
+
super();
|
|
3484
|
+
this.reason = reason;
|
|
3485
|
+
}
|
|
2022
3486
|
}
|
|
2023
3487
|
/**
|
|
2024
3488
|
* A class representing a comparison operation (e.g., equals, greater-than).
|
|
@@ -2049,20 +3513,28 @@ class NotParsableExpression extends Expression {
|
|
|
2049
3513
|
*/ class PropertyExpression extends Expression {
|
|
2050
3514
|
/** The type of the expression (always 'property'). */ type = "property";
|
|
2051
3515
|
/** The property info for the path. */ property;
|
|
2052
|
-
transformer = null;
|
|
2053
|
-
locale = null;
|
|
2054
3516
|
constructor(options){
|
|
2055
3517
|
super();
|
|
2056
3518
|
this.property = options.property;
|
|
2057
3519
|
}
|
|
2058
3520
|
}
|
|
3521
|
+
class CallExpression extends Expression {
|
|
3522
|
+
type = "call";
|
|
3523
|
+
call;
|
|
3524
|
+
expression;
|
|
3525
|
+
/** Empty for a unary call. */ arguments;
|
|
3526
|
+
constructor(options){
|
|
3527
|
+
super();
|
|
3528
|
+
this.call = options.call;
|
|
3529
|
+
this.expression = options.expression;
|
|
3530
|
+
this.arguments = options.arguments ?? [];
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
2059
3533
|
/**
|
|
2060
3534
|
* A class representing a literal value.
|
|
2061
3535
|
*/ class ValueExpression extends Expression {
|
|
2062
3536
|
/** The type of the expression (always 'value'). */ type = "value";
|
|
2063
3537
|
/** The literal value. */ value;
|
|
2064
|
-
transformer = null;
|
|
2065
|
-
locale = null;
|
|
2066
3538
|
constructor(options){
|
|
2067
3539
|
super();
|
|
2068
3540
|
this.value = options.value;
|
|
@@ -2073,8 +3545,43 @@ class NotParsableExpression extends Expression {
|
|
|
2073
3545
|
},
|
|
2074
3546
|
63(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
2075
3547
|
__webpack_require__.d(__webpack_exports__, {
|
|
2076
|
-
|
|
3548
|
+
LU: () => (childrenOf),
|
|
3549
|
+
jJ: () => (forEach)
|
|
2077
3550
|
});
|
|
3551
|
+
/**
|
|
3552
|
+
* Separates an operand from the calls applied to it.
|
|
3553
|
+
*
|
|
3554
|
+
* `null` when there is no operand beneath the calls. Every consumer needs this to decide whether a
|
|
3555
|
+
* comparator side is a property or a value, so it lives here rather than in each translator.
|
|
3556
|
+
*/ function peelCalls(expression) {
|
|
3557
|
+
const calls = [];
|
|
3558
|
+
let current = expression;
|
|
3559
|
+
while(current != null && current.type === "call"){
|
|
3560
|
+
calls.unshift(current);
|
|
3561
|
+
current = current.expression;
|
|
3562
|
+
}
|
|
3563
|
+
return current == null ? null : {
|
|
3564
|
+
operand: current,
|
|
3565
|
+
calls
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
function childrenOf(expression) {
|
|
3569
|
+
if (expression.type === "call") {
|
|
3570
|
+
const call = expression;
|
|
3571
|
+
return [
|
|
3572
|
+
call.expression,
|
|
3573
|
+
...call.arguments ?? []
|
|
3574
|
+
].filter((child)=>child != null);
|
|
3575
|
+
}
|
|
3576
|
+
const children = [];
|
|
3577
|
+
if (expression.left != null) {
|
|
3578
|
+
children.push(expression.left);
|
|
3579
|
+
}
|
|
3580
|
+
if (expression.right != null) {
|
|
3581
|
+
children.push(expression.right);
|
|
3582
|
+
}
|
|
3583
|
+
return children;
|
|
3584
|
+
}
|
|
2078
3585
|
/**
|
|
2079
3586
|
* Extracts all properties referenced in an expression
|
|
2080
3587
|
* @param expression The expression to analyze
|
|
@@ -2086,12 +3593,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2086
3593
|
if (expr.type === "property") {
|
|
2087
3594
|
properties.push(expr.property);
|
|
2088
3595
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
traverse(expr.left);
|
|
2092
|
-
}
|
|
2093
|
-
if (expr.right) {
|
|
2094
|
-
traverse(expr.right);
|
|
3596
|
+
for (const child of childrenOf(expr)){
|
|
3597
|
+
traverse(child);
|
|
2095
3598
|
}
|
|
2096
3599
|
}
|
|
2097
3600
|
traverse(expression);
|
|
@@ -2104,14 +3607,8 @@ function forEach(expression, callback) {
|
|
|
2104
3607
|
if (!callback(expr)) {
|
|
2105
3608
|
return false;
|
|
2106
3609
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
if (!traverse(expr.left)) {
|
|
2110
|
-
return false;
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
if (expr.right) {
|
|
2114
|
-
if (!traverse(expr.right)) {
|
|
3610
|
+
for (const child of childrenOf(expr)){
|
|
3611
|
+
if (!traverse(child)) {
|
|
2115
3612
|
return false;
|
|
2116
3613
|
}
|
|
2117
3614
|
}
|
|
@@ -2392,32 +3889,62 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2392
3889
|
H: () => (QueryOptionsCollection)
|
|
2393
3890
|
});
|
|
2394
3891
|
/* import */ var _assertions__rspack_import_1 = __webpack_require__(126);
|
|
2395
|
-
/* import */ var
|
|
3892
|
+
/* import */ var _expressions_utils__rspack_import_2 = __webpack_require__(63);
|
|
3893
|
+
/* import */ var _schema_types__rspack_import_0 = __webpack_require__(537);
|
|
3894
|
+
/* import */ var _utilities__rspack_import_3 = __webpack_require__(581);
|
|
3895
|
+
|
|
2396
3896
|
|
|
2397
3897
|
|
|
3898
|
+
|
|
3899
|
+
/** What a schema type is called in JavaScript, where one exists. A value of any other type cannot equal it. */ const JAVASCRIPT_TYPE_OF = {
|
|
3900
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Number */.L.Number]: "number",
|
|
3901
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.String */.L.String]: "string",
|
|
3902
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Boolean */.L.Boolean]: "boolean",
|
|
3903
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Date */.L.Date]: "object"
|
|
3904
|
+
};
|
|
3905
|
+
const mismatchedSide = (property, value)=>{
|
|
3906
|
+
if (property == null || value == null || !(0,_assertions__rspack_import_1.isPropertyExpression)(property) || !(0,_assertions__rspack_import_1.isValueExpression)(value)) {
|
|
3907
|
+
return null;
|
|
3908
|
+
}
|
|
3909
|
+
const expected = JAVASCRIPT_TYPE_OF[property.property.type];
|
|
3910
|
+
if (expected == null || value.value == null || typeof value.value === expected) {
|
|
3911
|
+
return null;
|
|
3912
|
+
}
|
|
3913
|
+
return {
|
|
3914
|
+
property,
|
|
3915
|
+
value,
|
|
3916
|
+
expected
|
|
3917
|
+
};
|
|
3918
|
+
};
|
|
3919
|
+
/** A strict comparison whose answer is the same for every row, because the types cannot be equal. */ const comparesTypesThatCannotMatch = (expression)=>{
|
|
3920
|
+
if (!(0,_assertions__rspack_import_1.isComparatorExpression)(expression) || expression.strict !== true) {
|
|
3921
|
+
return false;
|
|
3922
|
+
}
|
|
3923
|
+
if (expression.comparator !== "equals") {
|
|
3924
|
+
return false;
|
|
3925
|
+
}
|
|
3926
|
+
return mismatchedSide(expression.left, expression.right) != null || mismatchedSide(expression.right, expression.left) != null;
|
|
3927
|
+
};
|
|
3928
|
+
/** `JSON.stringify` throws on a BigInt, and this runs inside the guard that exists to catch one. */ const describeLiteral = (value)=>typeof value === "string" ? `"${value}"` : String(value);
|
|
3929
|
+
const mismatchWarning = (expression)=>{
|
|
3930
|
+
const side = mismatchedSide(expression.left, expression.right) ?? mismatchedSide(expression.right, expression.left);
|
|
3931
|
+
const outcome = expression.negated ? "every row matches" : "no row matches";
|
|
3932
|
+
return `Routier: '${side.property.property.getAssignmentPath()}' is a ${side.expected}, and this filter ` + `compares it against ${describeLiteral(side.value.value)}, which is a ${typeof side.value.value}. ` + `A strict comparison between them is the same answer for every row, so ${outcome} and the filter ` + `runs in memory. https://routier.dev/guides/strict-comparison-types`;
|
|
3933
|
+
};
|
|
2398
3934
|
class QueryOptionsCollection {
|
|
2399
3935
|
options = new Map();
|
|
2400
3936
|
nextExecutionTarget = "database";
|
|
2401
3937
|
nextExecutionReason = null;
|
|
2402
3938
|
nextIndex = 0;
|
|
2403
3939
|
enumeratedItems = [];
|
|
3940
|
+
dirty = true;
|
|
3941
|
+
/** The collection a `splitAt`/`split` half came from. A capability report belongs to it. */ origin = null;
|
|
2404
3942
|
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
2405
3943
|
this.nextExecutionTarget = "memory";
|
|
2406
3944
|
if (this.nextExecutionReason == null) {
|
|
2407
3945
|
this.nextExecutionReason = reason;
|
|
2408
3946
|
}
|
|
2409
3947
|
}
|
|
2410
|
-
/**
|
|
2411
|
-
* True when `split()` or `splitAt()` produced this collection.
|
|
2412
|
-
*
|
|
2413
|
-
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
2414
|
-
* without the options that caused them — a post-join filter alone in the memory half
|
|
2415
|
-
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
2416
|
-
* has to reject a derived collection; see `explainQuery`.
|
|
2417
|
-
*/ derived = false;
|
|
2418
|
-
get isDerived() {
|
|
2419
|
-
return this.derived;
|
|
2420
|
-
}
|
|
2421
3948
|
get items() {
|
|
2422
3949
|
return this.options;
|
|
2423
3950
|
}
|
|
@@ -2452,7 +3979,7 @@ class QueryOptionsCollection {
|
|
|
2452
3979
|
if (filterValue.expression.type === "not-parsable") {
|
|
2453
3980
|
this.cutOverToMemory("not-parsable");
|
|
2454
3981
|
} else {
|
|
2455
|
-
(0,
|
|
3982
|
+
(0,_expressions_utils__rspack_import_2/* .forEach */.jJ)(filterValue.expression, (expression)=>{
|
|
2456
3983
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
|
|
2457
3984
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
2458
3985
|
// cannot be queried
|
|
@@ -2467,6 +3994,11 @@ class QueryOptionsCollection {
|
|
|
2467
3994
|
this.cutOverToMemory("renamed-property");
|
|
2468
3995
|
return false;
|
|
2469
3996
|
}
|
|
3997
|
+
if (comparesTypesThatCannotMatch(expression)) {
|
|
3998
|
+
_utilities__rspack_import_3/* .logger.warn */.vF.warn(mismatchWarning(expression));
|
|
3999
|
+
this.cutOverToMemory("predicate-error");
|
|
4000
|
+
return false;
|
|
4001
|
+
}
|
|
2470
4002
|
return true;
|
|
2471
4003
|
});
|
|
2472
4004
|
}
|
|
@@ -2495,6 +4027,11 @@ class QueryOptionsCollection {
|
|
|
2495
4027
|
this.cutOverToMemory("renamed-property");
|
|
2496
4028
|
}
|
|
2497
4029
|
}
|
|
4030
|
+
if ((name === "filter" || name === "sort") && (this.options.has("skip") || this.options.has("take"))) {
|
|
4031
|
+
// SQL emits WHERE before LIMIT and Mongo's find() filters before skipping, so an option
|
|
4032
|
+
// written after a window can only see the windowed rows if it runs after it.
|
|
4033
|
+
this.cutOverToMemory("after-window");
|
|
4034
|
+
}
|
|
2498
4035
|
if (name === "join") {
|
|
2499
4036
|
const joinValue = value;
|
|
2500
4037
|
// A join whose two sides live on different plugins cannot be sent to EITHER of
|
|
@@ -2507,18 +4044,24 @@ class QueryOptionsCollection {
|
|
|
2507
4044
|
this.cutOverToMemory("cross-plugin-join");
|
|
2508
4045
|
}
|
|
2509
4046
|
}
|
|
4047
|
+
// `executed` is the plan, not a record: nothing has run when an option is added. Every
|
|
4048
|
+
// consumer reads it after the plugin returned, so the optimistic window is never observed.
|
|
2510
4049
|
const item = {
|
|
2511
4050
|
index: this.nextIndex,
|
|
2512
|
-
option: {
|
|
4051
|
+
option: this.nextExecutionTarget === "database" ? {
|
|
2513
4052
|
name,
|
|
2514
|
-
target: this.nextExecutionTarget,
|
|
2515
4053
|
value,
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
4054
|
+
target: "database",
|
|
4055
|
+
reason: "executed"
|
|
4056
|
+
} : {
|
|
4057
|
+
name,
|
|
4058
|
+
value,
|
|
4059
|
+
target: "memory",
|
|
4060
|
+
reason: this.nextExecutionReason ?? "not-parsable"
|
|
2519
4061
|
}
|
|
2520
4062
|
};
|
|
2521
4063
|
this.nextIndex++;
|
|
4064
|
+
this.dirty = true;
|
|
2522
4065
|
const found = this.options.get(name);
|
|
2523
4066
|
this.options.set(name, [
|
|
2524
4067
|
...found ?? [],
|
|
@@ -2563,8 +4106,6 @@ class QueryOptionsCollection {
|
|
|
2563
4106
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2564
4107
|
const before = new QueryOptionsCollection();
|
|
2565
4108
|
const after = new QueryOptionsCollection();
|
|
2566
|
-
before.derived = true;
|
|
2567
|
-
after.derived = true;
|
|
2568
4109
|
let at = null;
|
|
2569
4110
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2570
4111
|
const { option } = sortedItems[i];
|
|
@@ -2573,8 +4114,10 @@ class QueryOptionsCollection {
|
|
|
2573
4114
|
continue;
|
|
2574
4115
|
}
|
|
2575
4116
|
const destination = at == null ? before : after;
|
|
2576
|
-
destination.
|
|
4117
|
+
destination.adopt(sortedItems[i]);
|
|
2577
4118
|
}
|
|
4119
|
+
before.origin = this.origin ?? this;
|
|
4120
|
+
after.origin = this.origin ?? this;
|
|
2578
4121
|
return {
|
|
2579
4122
|
before,
|
|
2580
4123
|
at,
|
|
@@ -2606,23 +4149,99 @@ class QueryOptionsCollection {
|
|
|
2606
4149
|
this.nextExecutionReason = nextExecutionReason;
|
|
2607
4150
|
this.nextIndex = nextIndex;
|
|
2608
4151
|
this.enumeratedItems = [];
|
|
4152
|
+
// Clearing the list is not enough now that staleness is a flag rather than a count:
|
|
4153
|
+
// without this, `resolveEnumeration` believes the empty list is current and every read
|
|
4154
|
+
// of the collection sees no options at all.
|
|
4155
|
+
this.dirty = true;
|
|
2609
4156
|
};
|
|
2610
4157
|
}
|
|
4158
|
+
/** Takes an item as it stands — same object, same index, same target and reason. */ adopt(item) {
|
|
4159
|
+
const found = this.options.get(item.option.name);
|
|
4160
|
+
this.options.set(item.option.name, [
|
|
4161
|
+
...found ?? [],
|
|
4162
|
+
item
|
|
4163
|
+
]);
|
|
4164
|
+
this.nextIndex = Math.max(this.nextIndex, item.index + 1);
|
|
4165
|
+
this.dirty = true;
|
|
4166
|
+
}
|
|
4167
|
+
/**
|
|
4168
|
+
* A plugin reporting that its engine cannot express one option.
|
|
4169
|
+
*
|
|
4170
|
+
* Core marks the rest of the database phase `not-reached`, because the database has to stop
|
|
4171
|
+
* there — a window applied in front of a filter that was not applied returns the wrong rows.
|
|
4172
|
+
* Passing the cascade through core is what makes it impossible for a plugin to mark a
|
|
4173
|
+
* non-contiguous cut.
|
|
4174
|
+
*
|
|
4175
|
+
* A report names a culprit and never un-names one, so reports commute.
|
|
4176
|
+
*
|
|
4177
|
+
* The option is not moved to the memory arm. It stays where it was planned, which is what keeps
|
|
4178
|
+
* a redirect distinguishable from something core sent to memory in the first place.
|
|
4179
|
+
*/ reportMissingCapability(item) {
|
|
4180
|
+
this.report(item, "missing-capability");
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* A plugin reporting that its engine would answer one option differently from JavaScript.
|
|
4184
|
+
*
|
|
4185
|
+
* Same cascade as `reportMissingCapability`, and a separate reason because the caller can act on
|
|
4186
|
+
* one and not the other. See `DatabaseExecutionReason`.
|
|
4187
|
+
*/ reportEngineDivergence(item) {
|
|
4188
|
+
this.report(item, "engine-divergence");
|
|
4189
|
+
}
|
|
4190
|
+
report(item, reason) {
|
|
4191
|
+
// A half can only see its own slice, and the database has to stop for the whole dispatch.
|
|
4192
|
+
if (this.origin != null) {
|
|
4193
|
+
this.origin.report(item, reason);
|
|
4194
|
+
return;
|
|
4195
|
+
}
|
|
4196
|
+
this.resolveEnumeration();
|
|
4197
|
+
for (const candidate of this.enumeratedItems){
|
|
4198
|
+
if (candidate.option.target !== "database" || candidate.index < item.index) {
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
if (candidate.index === item.index) {
|
|
4202
|
+
candidate.option.reason = reason;
|
|
4203
|
+
continue;
|
|
4204
|
+
}
|
|
4205
|
+
if (candidate.option.reason === "executed") {
|
|
4206
|
+
candidate.option.reason = "not-reached";
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
/**
|
|
4211
|
+
* Forgets what any previous dispatch reported.
|
|
4212
|
+
*
|
|
4213
|
+
* Capability is answered per dispatch, so a report is only an answer for the execution that
|
|
4214
|
+
* produced it. The items are shared with any snapshot, so a report mutated in place otherwise
|
|
4215
|
+
* survives a restore and a second terminal on the same queryable replays options the plugin
|
|
4216
|
+
* did run — a `skip` applied twice, over rows already windowed.
|
|
4217
|
+
*/ forgetReports() {
|
|
4218
|
+
this.resolveEnumeration();
|
|
4219
|
+
for (const item of this.enumeratedItems){
|
|
4220
|
+
if (item.option.target === "database") {
|
|
4221
|
+
item.option.reason = "executed";
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
/** The options the database did not run, in the order they were written. */ notExecuted() {
|
|
4226
|
+
this.resolveEnumeration();
|
|
4227
|
+
return this.enumeratedItems.filter((item)=>item.option.target === "database" && item.option.reason !== "executed").toSorted((a, b)=>a.index - b.index);
|
|
4228
|
+
}
|
|
2611
4229
|
split() {
|
|
2612
4230
|
this.resolveEnumeration();
|
|
2613
4231
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2614
4232
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
2615
4233
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
2616
|
-
memoryQueryOptionsCollection.derived = true;
|
|
2617
|
-
databaseQueryOptionsCollection.derived = true;
|
|
2618
4234
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2619
4235
|
const sortedItem = sortedItems[i];
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
4236
|
+
const half = sortedItem.option.target === "database" ? databaseQueryOptionsCollection : memoryQueryOptionsCollection;
|
|
4237
|
+
// The ITEM, not its name and value. Re-adding would re-derive target and reason from a
|
|
4238
|
+
// fresh cascade, and a memory option re-added alone comes back out as `database` with no
|
|
4239
|
+
// reason at all. Sharing it also means a plugin's report on the database half is the
|
|
4240
|
+
// same object the explanation reads.
|
|
4241
|
+
half.adopt(sortedItem);
|
|
4242
|
+
}
|
|
4243
|
+
memoryQueryOptionsCollection.origin = this.origin ?? this;
|
|
4244
|
+
databaseQueryOptionsCollection.origin = this.origin ?? this;
|
|
2626
4245
|
return {
|
|
2627
4246
|
memory: memoryQueryOptionsCollection,
|
|
2628
4247
|
database: databaseQueryOptionsCollection
|
|
@@ -2668,8 +4287,11 @@ class QueryOptionsCollection {
|
|
|
2668
4287
|
].flat().toSorted((a, b)=>a.index - b.index);
|
|
2669
4288
|
}
|
|
2670
4289
|
resolveEnumeration() {
|
|
2671
|
-
|
|
4290
|
+
// A flag, not a count: adopting leaves gaps in the indexes, so `length !== nextIndex` is
|
|
4291
|
+
// true forever on a half and the enumeration rebuilds on every read.
|
|
4292
|
+
if (this.dirty === true) {
|
|
2672
4293
|
this.enumeratedItems = this.getEnumeration();
|
|
4294
|
+
this.dirty = false;
|
|
2673
4295
|
}
|
|
2674
4296
|
}
|
|
2675
4297
|
forEach(iterator) {
|
|
@@ -2889,12 +4511,14 @@ const isLogLevel = (value)=>typeof value === 'string' && LOG_LEVELS.includes(val
|
|
|
2889
4511
|
const debug = process.env.DEBUG;
|
|
2890
4512
|
if (debug === 'routier' || debug === '*') return 'debug';
|
|
2891
4513
|
const env = "production"?.toLowerCase();
|
|
2892
|
-
// `test` is deliberately absent. It used to be here, which meant no test suite anywhere
|
|
2893
|
-
// could run Routier quietly. Opt in with DEBUG=routier or ROUTIER_LOG_LEVEL when a test
|
|
2894
|
-
// needs the output.
|
|
2895
4514
|
if (env === 'dev' || env === 'development') return 'debug';
|
|
2896
4515
|
}
|
|
2897
|
-
|
|
4516
|
+
// Warnings are on unless something turns them off.
|
|
4517
|
+
//
|
|
4518
|
+
// Routier warns when a query returns correct rows a slower way than it could, or when a filter
|
|
4519
|
+
// compares types that can never match. Both are the caller's to act on, and a default of
|
|
4520
|
+
// `silent` meant the only people who ever saw them were the ones who already knew to look.
|
|
4521
|
+
return 'warn';
|
|
2898
4522
|
};
|
|
2899
4523
|
let level = resolveLevel();
|
|
2900
4524
|
let rank = RANK[level];
|
|
@@ -3057,6 +4681,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3057
4681
|
|
|
3058
4682
|
// EXPORTS
|
|
3059
4683
|
__webpack_require__.d(__webpack_exports__, {
|
|
4684
|
+
describeFilters: () => (/* reexport */ describeFilters),
|
|
3060
4685
|
DEFAULT_SEMI_JOIN_KEY_THRESHOLD: () => (/* reexport */ DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
|
|
3061
4686
|
TranslatedArrayValue: () => (/* reexport */ TranslatedArrayValue),
|
|
3062
4687
|
collectingSink: () => (/* reexport */ collectingSink),
|
|
@@ -3066,12 +4691,15 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3066
4691
|
splitSendableOptions: () => (/* reexport */ splitSendableOptions),
|
|
3067
4692
|
executeJoin: () => (/* reexport */ executeJoin),
|
|
3068
4693
|
formatExplanation: () => (/* reexport */ formatExplanation),
|
|
3069
|
-
|
|
4694
|
+
parameteriseDocument: () => (/* reexport */ parameteriseDocument),
|
|
4695
|
+
mappedResultColumns: () => (/* reexport */ mappedResultColumns),
|
|
3070
4696
|
explainQuery: () => (/* reexport */ explainQuery),
|
|
4697
|
+
parameter: () => (/* reexport */ parameter),
|
|
4698
|
+
serializePersistResult: () => (/* reexport */ serializePersistResult),
|
|
4699
|
+
applyInnerOptions: () => (/* reexport */ applyInnerOptions),
|
|
3071
4700
|
cosineDistance: () => (/* reexport */ cosineDistance),
|
|
3072
4701
|
RetryDbPlugin: () => (/* reexport */ RetryDbPlugin),
|
|
3073
4702
|
BatchingDbPlugin: () => (/* reexport */ BatchingDbPlugin),
|
|
3074
|
-
applyInnerOptions: () => (/* reexport */ applyInnerOptions),
|
|
3075
4703
|
MEMORY_EXECUTION_EXPLANATIONS: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
|
|
3076
4704
|
TupleTranslator: () => (/* reexport */ TupleTranslator),
|
|
3077
4705
|
TranslatedSingleValue: () => (/* reexport */ TranslatedSingleValue),
|
|
@@ -3079,29 +4707,47 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3079
4707
|
TranslatedGroupValue: () => (/* reexport */ TranslatedGroupValue),
|
|
3080
4708
|
deserializeBulkPersist: () => (/* reexport */ deserializeBulkPersist),
|
|
3081
4709
|
EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport */ EXECUTED_QUERIES_UNSUPPORTED),
|
|
3082
|
-
|
|
4710
|
+
executedQueriesOf: () => (/* reexport */ executedQueriesOf),
|
|
3083
4711
|
deserializePersistResult: () => (/* reexport */ deserializePersistResult),
|
|
4712
|
+
joinInPlugin: () => (/* reexport */ joinInPlugin),
|
|
3084
4713
|
loggerSink: () => (/* reexport */ loggerSink),
|
|
3085
4714
|
nearestBy: () => (/* reexport */ nearestBy),
|
|
3086
|
-
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3087
4715
|
hashJoin: () => (/* reexport */ hashJoin),
|
|
3088
4716
|
EphemeralDataPlugin: () => (/* reexport */ EphemeralDataPlugin),
|
|
3089
4717
|
QueryOptionsCollection: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
|
|
3090
|
-
|
|
3091
|
-
|
|
4718
|
+
describeUnparsableFilter: () => (/* reexport */ describeUnparsableFilter),
|
|
4719
|
+
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3092
4720
|
loadJoinInnerSide: () => (/* reexport */ loadJoinInnerSide),
|
|
3093
4721
|
DataTranslator: () => (/* reexport */ DataTranslator),
|
|
3094
|
-
|
|
4722
|
+
serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
|
|
4723
|
+
toEntityShape: () => (/* reexport */ toEntityShape),
|
|
3095
4724
|
TelemetryDbPlugin: () => (/* reexport */ TelemetryDbPlugin),
|
|
4725
|
+
withExecutedQueries: () => (/* reexport */ withExecutedQueries),
|
|
3096
4726
|
createRequestHandler: () => (/* reexport */ createRequestHandler),
|
|
3097
4727
|
SqlTranslator: () => (/* reexport */ SqlTranslator),
|
|
4728
|
+
withInnerSide: () => (/* reexport */ withInnerSide),
|
|
3098
4729
|
QueryOrdering: () => (/* reexport */ types_QueryOrdering),
|
|
4730
|
+
describeFilterAsJs: () => (/* reexport */ describeFilterAsJs),
|
|
3099
4731
|
serializeQueryOptions: () => (/* reexport */ serializeQueryOptions),
|
|
3100
|
-
distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
|
|
3101
4732
|
JsonTranslator: () => (/* reexport */ JsonTranslator),
|
|
3102
|
-
|
|
4733
|
+
DATABASE_EXECUTION_EXPLANATIONS: () => (/* reexport */ DATABASE_EXECUTION_EXPLANATIONS),
|
|
4734
|
+
distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
|
|
4735
|
+
deserializeQueryOptions: () => (/* reexport */ deserializeQueryOptions),
|
|
4736
|
+
isDatabaseStep: () => (/* reexport */ isDatabaseStep)
|
|
3103
4737
|
});
|
|
3104
4738
|
|
|
4739
|
+
;// CONCATENATED MODULE: ./src/plugins/resultShape.ts
|
|
4740
|
+
/**
|
|
4741
|
+
* The columns a `map` projection selects.
|
|
4742
|
+
*
|
|
4743
|
+
* Named by `sourceName`, which is what the statement actually emits; the rename to
|
|
4744
|
+
* `destinationName` happens in the translator, after the rows come back. A field with no
|
|
4745
|
+
* `property` is an expression.
|
|
4746
|
+
*/ const mappedResultColumns = (fields)=>fields.map((field)=>({
|
|
4747
|
+
name: field.sourceName,
|
|
4748
|
+
property: field.property ?? null
|
|
4749
|
+
}));
|
|
4750
|
+
|
|
3105
4751
|
;// CONCATENATED MODULE: ./src/plugins/translators/TranslatedArrayValue.ts
|
|
3106
4752
|
class TranslatedArrayValue {
|
|
3107
4753
|
value;
|
|
@@ -3196,6 +4842,10 @@ class DataTranslator {
|
|
|
3196
4842
|
translate(data) {
|
|
3197
4843
|
const isTransformed = this.query.options.hasTransformations();
|
|
3198
4844
|
this.query.options.forEach((item)=>{
|
|
4845
|
+
// The plugin reported it could not run this one, so the memory pass owns it now.
|
|
4846
|
+
if (item.target === "database" && item.reason !== "executed") {
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
3199
4849
|
data = this.functionMap[item.name](data, item);
|
|
3200
4850
|
});
|
|
3201
4851
|
if (Array.isArray(data)) {
|
|
@@ -3608,7 +5258,7 @@ class Query {
|
|
|
3608
5258
|
* Only a plugin that runs its outer query FIRST can supply these, and most run this loader
|
|
3609
5259
|
* before anything else — so it is optional, and its absence costs a wider inner read rather
|
|
3610
5260
|
* than a wrong one.
|
|
3611
|
-
*/ outerKeys)=>{
|
|
5261
|
+
*/ outerKeys, /** Where the inner read reports what it executed. Defaults to the outer read's own list. */ innerExecutedQueries)=>{
|
|
3612
5262
|
const joinOption = event.operation.options.getLast("join");
|
|
3613
5263
|
if (joinOption == null) {
|
|
3614
5264
|
done({
|
|
@@ -3636,9 +5286,10 @@ class Query {
|
|
|
3636
5286
|
action: "query",
|
|
3637
5287
|
reason: "join inner side",
|
|
3638
5288
|
explain: event.explain,
|
|
3639
|
-
// The
|
|
3640
|
-
//
|
|
3641
|
-
|
|
5289
|
+
// The caller decides where the inner read reports, because only it knows whether the inner
|
|
5290
|
+
// side is the SAME plugin — where both reads belong in one explanation — or a different one,
|
|
5291
|
+
// where a PouchDB scan filed under SqliteDbPlugin is a lie.
|
|
5292
|
+
executedQueries: innerExecutedQueries ?? event.executedQueries
|
|
3642
5293
|
};
|
|
3643
5294
|
query(innerEvent, (result)=>{
|
|
3644
5295
|
if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
|
|
@@ -3715,6 +5366,12 @@ class Query {
|
|
|
3715
5366
|
return;
|
|
3716
5367
|
}
|
|
3717
5368
|
const outerRows = outerResult.data.value ?? [];
|
|
5369
|
+
if (at.reason !== "executed") {
|
|
5370
|
+
// The outer read reported something, so the database phase stopped before the join.
|
|
5371
|
+
// The datastore's own join branch pairs these rows.
|
|
5372
|
+
done(Result/* .PluginEventResult.success */.D.success(event.id, new TranslatedArrayValue(outerRows, false)));
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
3718
5375
|
// Storage shape: the plugin returns rows as it holds them, and deserialization is what
|
|
3719
5376
|
// `executeJoin` does per side below.
|
|
3720
5377
|
const outerKeys = distinctJoinKeys(outerRows, at.value.outerKey, at.value.semiJoinKeyThreshold, {
|
|
@@ -3830,9 +5487,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
3830
5487
|
if (field.property != null) {
|
|
3831
5488
|
const value = field.property.getValue(data[i]);
|
|
3832
5489
|
if (value != null) {
|
|
3833
|
-
|
|
3834
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
3835
|
-
field.property.setValue(data[i], resolvedValue);
|
|
5490
|
+
field.property.setValue(data[i], field.property.deserialize(value));
|
|
3836
5491
|
}
|
|
3837
5492
|
}
|
|
3838
5493
|
}
|
|
@@ -3856,9 +5511,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
3856
5511
|
if (field.property != null) {
|
|
3857
5512
|
const value = field.property.getValue(data[i]);
|
|
3858
5513
|
if (value != null) {
|
|
3859
|
-
|
|
3860
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
3861
|
-
field.property.setValue(item, resolvedValue);
|
|
5514
|
+
field.property.setValue(item, field.property.deserialize(value));
|
|
3862
5515
|
continue;
|
|
3863
5516
|
}
|
|
3864
5517
|
// The property exists, lets set it to the value (null/undefined)
|
|
@@ -4199,9 +5852,12 @@ class SqlTranslator extends DataTranslator {
|
|
|
4199
5852
|
for(let j = 0, l = option.value.fields.length; j < l; j++){
|
|
4200
5853
|
const field = option.value.fields[j];
|
|
4201
5854
|
if (field.property != null) {
|
|
4202
|
-
const
|
|
5855
|
+
const row = data[i];
|
|
5856
|
+
// A nested field arrives FLAT, under the alias the statement emitted, because
|
|
5857
|
+
// the value was read out of a JSON column. `setValue` puts it back on its path.
|
|
5858
|
+
const value = Object.prototype.hasOwnProperty.call(row, field.sourceName) ? row[field.sourceName] : field.property.getValue(row);
|
|
4203
5859
|
if (value != null) {
|
|
4204
|
-
field.property.setValue(
|
|
5860
|
+
field.property.setValue(row, field.property.deserialize(value));
|
|
4205
5861
|
}
|
|
4206
5862
|
}
|
|
4207
5863
|
}
|
|
@@ -4338,6 +5994,183 @@ class SqlTranslator extends DataTranslator {
|
|
|
4338
5994
|
|
|
4339
5995
|
|
|
4340
5996
|
|
|
5997
|
+
// EXTERNAL MODULE: ./src/expressions/callSource.ts
|
|
5998
|
+
var callSource = __webpack_require__(429);
|
|
5999
|
+
;// CONCATENATED MODULE: ./src/plugins/query/describeFilter.ts
|
|
6000
|
+
|
|
6001
|
+
|
|
6002
|
+
const COMPARATOR_OPERATORS = {
|
|
6003
|
+
"equals": "===",
|
|
6004
|
+
"greater-than": ">",
|
|
6005
|
+
"greater-than-equals": ">=",
|
|
6006
|
+
"less-than": "<",
|
|
6007
|
+
"less-than-equals": "<="
|
|
6008
|
+
};
|
|
6009
|
+
/** The three comparators that read as a method call rather than an operator. */ const COMPARATOR_METHODS = {
|
|
6010
|
+
"starts-with": "startsWith",
|
|
6011
|
+
"includes": "includes",
|
|
6012
|
+
"ends-with": "endsWith"
|
|
6013
|
+
};
|
|
6014
|
+
const renderProperty = (property)=>property.property.getPathArray().join(".");
|
|
6015
|
+
/**
|
|
6016
|
+
* The predicate as JavaScript, with every value replaced by `?`.
|
|
6017
|
+
*
|
|
6018
|
+
* Rendered from the parsed tree rather than from the function's source. The tree is what the
|
|
6019
|
+
* backend was actually given, so this cannot drift from what ran; and a value reaching the tree
|
|
6020
|
+
* as a literal is indistinguishable from one arriving through a params object, which is what
|
|
6021
|
+
* makes both come out as `?` the way SQL treats them.
|
|
6022
|
+
*/ const describeFilterAsJs = (expression)=>{
|
|
6023
|
+
const parameters = [];
|
|
6024
|
+
const hold = (value)=>{
|
|
6025
|
+
parameters.push(value);
|
|
6026
|
+
return "?";
|
|
6027
|
+
};
|
|
6028
|
+
const side = (part)=>{
|
|
6029
|
+
if (part == null) {
|
|
6030
|
+
return "?";
|
|
6031
|
+
}
|
|
6032
|
+
if ((0,assertions.isPropertyExpression)(part)) {
|
|
6033
|
+
return renderProperty(part);
|
|
6034
|
+
}
|
|
6035
|
+
if ((0,assertions.isValueExpression)(part)) {
|
|
6036
|
+
return hold(part.value);
|
|
6037
|
+
}
|
|
6038
|
+
if ((0,assertions.isCallExpression)(part)) {
|
|
6039
|
+
return (0,callSource/* .renderCallAsJs */.a)(part.call, ()=>side(part.expression), ()=>part.arguments.map(side));
|
|
6040
|
+
}
|
|
6041
|
+
return walk(part);
|
|
6042
|
+
};
|
|
6043
|
+
const walk = (current)=>{
|
|
6044
|
+
if ((0,assertions.isOperatorExpression)(current)) {
|
|
6045
|
+
const operator = current.operator === "&&" ? "&&" : "||";
|
|
6046
|
+
return `(${side(current.left)} ${operator} ${side(current.right)})`;
|
|
6047
|
+
}
|
|
6048
|
+
if ((0,assertions.isComparatorExpression)(current)) {
|
|
6049
|
+
const method = COMPARATOR_METHODS[current.comparator];
|
|
6050
|
+
// Evaluated LEFT then RIGHT, always: the parameter order has to match the reading
|
|
6051
|
+
// order of the text, or the values line up against the wrong placeholders.
|
|
6052
|
+
const left = side(current.left);
|
|
6053
|
+
const right = side(current.right);
|
|
6054
|
+
if (method != null) {
|
|
6055
|
+
const call = `${left}.${method}(${right})`;
|
|
6056
|
+
return current.negated ? `${call} === false` : call;
|
|
6057
|
+
}
|
|
6058
|
+
const symbol = COMPARATOR_OPERATORS[current.comparator];
|
|
6059
|
+
if (symbol == null) {
|
|
6060
|
+
return `${left} ${current.comparator} ${right}`;
|
|
6061
|
+
}
|
|
6062
|
+
return `${left} ${current.negated ? negate(symbol) : symbol} ${right}`;
|
|
6063
|
+
}
|
|
6064
|
+
if ((0,assertions.isCallExpression)(current)) {
|
|
6065
|
+
return (0,callSource/* .renderCallAsJs */.a)(current.call, ()=>side(current.expression), ()=>current.arguments.map(side));
|
|
6066
|
+
}
|
|
6067
|
+
if (current.type === "empty") {
|
|
6068
|
+
return "(no filter)";
|
|
6069
|
+
}
|
|
6070
|
+
return current.type === "not-parsable" ? "(not parsable)" : `(unsupported: ${current.type})`;
|
|
6071
|
+
};
|
|
6072
|
+
return {
|
|
6073
|
+
text: walk(expression),
|
|
6074
|
+
parameters
|
|
6075
|
+
};
|
|
6076
|
+
};
|
|
6077
|
+
const negate = (symbol)=>{
|
|
6078
|
+
switch(symbol){
|
|
6079
|
+
case "===":
|
|
6080
|
+
return "!==";
|
|
6081
|
+
case ">":
|
|
6082
|
+
return "<=";
|
|
6083
|
+
case ">=":
|
|
6084
|
+
return "<";
|
|
6085
|
+
case "<":
|
|
6086
|
+
return ">=";
|
|
6087
|
+
case "<=":
|
|
6088
|
+
return ">";
|
|
6089
|
+
default:
|
|
6090
|
+
return `!${symbol}`;
|
|
6091
|
+
}
|
|
6092
|
+
};
|
|
6093
|
+
/**
|
|
6094
|
+
* Marks a value inside a query document so it is replaced by `?` rather than printed.
|
|
6095
|
+
*
|
|
6096
|
+
* A document language carries its values inline, so there is nothing in the shape itself to say
|
|
6097
|
+
* which parts are operators and which are data. A dialect wraps the data as it builds the
|
|
6098
|
+
* document, and `parameteriseDocument` reads the wrapper.
|
|
6099
|
+
*/ const PARAMETER = Symbol("routier.parameter");
|
|
6100
|
+
const parameter = (value)=>({
|
|
6101
|
+
[PARAMETER]: value
|
|
6102
|
+
});
|
|
6103
|
+
const isParameter = (value)=>typeof value === "object" && value !== null && PARAMETER in value;
|
|
6104
|
+
/**
|
|
6105
|
+
* Renders a query DOCUMENT with its values replaced by `?`.
|
|
6106
|
+
*
|
|
6107
|
+
* Language-agnostic on purpose: an MQL filter and a Mango selector are both plain objects, and so
|
|
6108
|
+
* is whatever a future document store wants reported. The dialect decides the shape; this only
|
|
6109
|
+
* decides how it is written down.
|
|
6110
|
+
*
|
|
6111
|
+
* A value not wrapped by `parameter` is structural — an operator name, a field path, a nesting
|
|
6112
|
+
* level — and is printed as it is. That is the whole distinction, and it has to be made where the
|
|
6113
|
+
* document is built, because by the time it is an object the two are the same kind of thing.
|
|
6114
|
+
*/ const parameteriseDocument = (document)=>{
|
|
6115
|
+
const parameters = [];
|
|
6116
|
+
const render = (value)=>{
|
|
6117
|
+
if (isParameter(value)) {
|
|
6118
|
+
parameters.push(value[PARAMETER]);
|
|
6119
|
+
return "?";
|
|
6120
|
+
}
|
|
6121
|
+
if (Array.isArray(value)) {
|
|
6122
|
+
return `[${value.map(render).join(", ")}]`;
|
|
6123
|
+
}
|
|
6124
|
+
if (typeof value === "object" && value !== null) {
|
|
6125
|
+
const entries = Object.entries(value).map(([key, nested])=>`${JSON.stringify(key)}: ${render(nested)}`);
|
|
6126
|
+
return `{ ${entries.join(", ")} }`;
|
|
6127
|
+
}
|
|
6128
|
+
return JSON.stringify(value) ?? String(value);
|
|
6129
|
+
};
|
|
6130
|
+
return {
|
|
6131
|
+
text: render(document),
|
|
6132
|
+
parameters
|
|
6133
|
+
};
|
|
6134
|
+
};
|
|
6135
|
+
/**
|
|
6136
|
+
* Every filter on a query, as one description.
|
|
6137
|
+
*
|
|
6138
|
+
* Filters accumulate — `.where(a).where(b)` is `a && b` — so they are reported as one predicate
|
|
6139
|
+
* rather than several, which is how the caller thinks of them and how a SQL plugin renders them
|
|
6140
|
+
* into one `WHERE`. Parameters run left to right across the whole thing, matching the text.
|
|
6141
|
+
*
|
|
6142
|
+
* A filter that could not be parsed falls back to its source. Mixing the two is deliberate: one
|
|
6143
|
+
* unparsable filter does not make the others unreadable, and seeing which one it was is the
|
|
6144
|
+
* point.
|
|
6145
|
+
*/ const describeFilters = (filters)=>{
|
|
6146
|
+
const parameters = [];
|
|
6147
|
+
const parts = filters.map((entry)=>{
|
|
6148
|
+
const described = entry.expression?.type === "not-parsable" ? describeUnparsableFilter(entry.filter, entry.expression.reason) : describeFilterAsJs(entry.expression);
|
|
6149
|
+
parameters.push(...described.parameters);
|
|
6150
|
+
return described.text;
|
|
6151
|
+
});
|
|
6152
|
+
if (parts.length === 0) {
|
|
6153
|
+
return {
|
|
6154
|
+
text: "(no filter)",
|
|
6155
|
+
parameters: []
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
6158
|
+
return {
|
|
6159
|
+
text: parts.length === 1 ? parts[0] : parts.join(" && "),
|
|
6160
|
+
parameters
|
|
6161
|
+
};
|
|
6162
|
+
};
|
|
6163
|
+
/**
|
|
6164
|
+
* A predicate core could not parse, shown as the caller wrote it.
|
|
6165
|
+
*
|
|
6166
|
+
* This is the case where the source matters most: an unparsable filter is why the query did not
|
|
6167
|
+
* push down, and the reason codes say that it happened without showing what it was. There are no
|
|
6168
|
+
* parameters — nothing was extracted, because nothing was understood.
|
|
6169
|
+
*/ const describeUnparsableFilter = (filter, reason)=>({
|
|
6170
|
+
text: typeof filter === "function" ? `${String(filter)} — ${reason ?? "could not be parsed"}, evaluated in memory` : "(not parsable)",
|
|
6171
|
+
parameters: []
|
|
6172
|
+
});
|
|
6173
|
+
|
|
4341
6174
|
;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
|
|
4342
6175
|
|
|
4343
6176
|
/**
|
|
@@ -4352,12 +6185,23 @@ class SqlTranslator extends DataTranslator {
|
|
|
4352
6185
|
"map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
|
|
4353
6186
|
"after-nearest": "A similarity search orders and limits rows, and the plugin cannot report whether it performed the search, so every option after it runs in memory.",
|
|
4354
6187
|
"after-join": "A join produces [outer, inner] tuples rather than entities, and the plugin cannot report how it joined, so every option after it runs in memory.",
|
|
4355
|
-
"cross-plugin-join": "The two sides of this join live on different plugins, so neither can read the other's rows and the join runs in the datastore."
|
|
6188
|
+
"cross-plugin-join": "The two sides of this join live on different plugins, so neither can read the other's rows and the join runs in the datastore.",
|
|
6189
|
+
"after-window": "A skip or take runs before this option, and SQL applies WHERE before LIMIT, so pushing it down would window rows this option had not seen yet. It runs in memory over the windowed rows instead.",
|
|
6190
|
+
"predicate-error": "A strict comparison compares a column against a value of a type it can never equal, so the answer is the same for every row and the filter runs in memory. Check the types in the filter."
|
|
4356
6191
|
};
|
|
4357
6192
|
const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
6193
|
+
/**
|
|
6194
|
+
* Why an option planned for the database did not run there. `executed` has no sentence: it needs no
|
|
6195
|
+
* explaining, and a step made of executed options is a database step like any other.
|
|
6196
|
+
*/ const DATABASE_EXECUTION_EXPLANATIONS = {
|
|
6197
|
+
"missing-capability": "The plugin's engine cannot express this option, so it runs in memory over the rows the plugin did return. Only the plugin can know this — an engine's capabilities are not visible from here.",
|
|
6198
|
+
"engine-divergence": "The plugin's engine would answer this option differently from JavaScript, so it runs in memory instead and the rows match what the predicate means. Nothing in the query needs changing.",
|
|
6199
|
+
"not-reached": "The database stopped at an option it could not express, so this one runs in memory too. Carrying on would apply it to rows the earlier option had not filtered."
|
|
6200
|
+
};
|
|
6201
|
+
/**
|
|
6202
|
+
* TypeScript does not narrow a union from a discriminant nested inside a property, so the two kinds
|
|
6203
|
+
* of step need a guard rather than an inline check.
|
|
6204
|
+
*/ const isDatabaseStep = (step)=>step.executedIn.kind === "database";
|
|
4361
6205
|
/**
|
|
4362
6206
|
* The reportable shape of one option's value.
|
|
4363
6207
|
*
|
|
@@ -4438,12 +6282,16 @@ const explainedOptionsOf = (options)=>{
|
|
|
4438
6282
|
options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
|
|
4439
6283
|
return explained;
|
|
4440
6284
|
};
|
|
6285
|
+
/** Every sentence, whoever decided — the summary reads the same either way. */ const EXPLANATIONS = {
|
|
6286
|
+
...MEMORY_EXECUTION_EXPLANATIONS,
|
|
6287
|
+
...DATABASE_EXECUTION_EXPLANATIONS
|
|
6288
|
+
};
|
|
4441
6289
|
const summarize = (steps)=>{
|
|
4442
6290
|
const reasons = [];
|
|
4443
6291
|
let database = 0;
|
|
4444
6292
|
let memory = 0;
|
|
4445
6293
|
for (const step of steps){
|
|
4446
|
-
if (step
|
|
6294
|
+
if (isDatabaseStep(step)) {
|
|
4447
6295
|
database += step.options.length;
|
|
4448
6296
|
continue;
|
|
4449
6297
|
}
|
|
@@ -4453,7 +6301,7 @@ const summarize = (steps)=>{
|
|
|
4453
6301
|
}
|
|
4454
6302
|
}
|
|
4455
6303
|
const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
|
|
4456
|
-
const causes = reasons.map((reason)=>
|
|
6304
|
+
const causes = reasons.map((reason)=>EXPLANATIONS[reason]).join(" ");
|
|
4457
6305
|
return {
|
|
4458
6306
|
database,
|
|
4459
6307
|
memory,
|
|
@@ -4461,50 +6309,87 @@ const summarize = (steps)=>{
|
|
|
4461
6309
|
explanation: causes.length === 0 ? counts : `${counts} ${causes}`
|
|
4462
6310
|
};
|
|
4463
6311
|
};
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
6312
|
+
const outcomeOf = (option)=>{
|
|
6313
|
+
if (option.target === "memory") {
|
|
6314
|
+
return {
|
|
6315
|
+
executedIn: "memory",
|
|
6316
|
+
reason: option.reason,
|
|
6317
|
+
explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
6320
|
+
if (option.reason === "executed") {
|
|
6321
|
+
return {
|
|
6322
|
+
executedIn: "database",
|
|
6323
|
+
reason: null,
|
|
6324
|
+
explanation: null
|
|
6325
|
+
};
|
|
6326
|
+
}
|
|
6327
|
+
return {
|
|
6328
|
+
executedIn: "memory",
|
|
6329
|
+
reason: option.reason,
|
|
6330
|
+
explanation: DATABASE_EXECUTION_EXPLANATIONS[option.reason]
|
|
6331
|
+
};
|
|
6332
|
+
};
|
|
6333
|
+
/** Whether an option belongs to the step already open, or starts a new one. */ const continuesStep = (current, outcome)=>{
|
|
6334
|
+
if (current == null) {
|
|
6335
|
+
return false;
|
|
6336
|
+
}
|
|
6337
|
+
if (current.executedIn.kind === "database") {
|
|
6338
|
+
return outcome.reason == null;
|
|
6339
|
+
}
|
|
6340
|
+
// `?? null` because a step with no reason omits the key, and `undefined === null` is false —
|
|
6341
|
+
// without it every option started a step of its own
|
|
6342
|
+
return outcome.reason != null && (current.reason ?? null) === outcome.reason;
|
|
6343
|
+
};
|
|
6344
|
+
const toExecutionSteps = (options, ranIn)=>{
|
|
4476
6345
|
const steps = [];
|
|
4477
6346
|
let index = 0;
|
|
4478
6347
|
options.forEach((option)=>{
|
|
4479
6348
|
const explained = explainedOptionOf(option, index++);
|
|
4480
6349
|
const current = steps[steps.length - 1];
|
|
4481
|
-
|
|
6350
|
+
const outcome = outcomeOf(option);
|
|
6351
|
+
// Grouped by outcome, not by target: an option the database could not express and one core
|
|
6352
|
+
// sent to memory both run in memory, for different reasons a reader needs told apart.
|
|
6353
|
+
if (continuesStep(current, outcome) === true) {
|
|
4482
6354
|
current.options.push(explained);
|
|
4483
6355
|
return;
|
|
4484
6356
|
}
|
|
4485
|
-
steps.push({
|
|
4486
|
-
step:
|
|
6357
|
+
steps.push(outcome.reason == null ? {
|
|
6358
|
+
step: 0,
|
|
4487
6359
|
of: 0,
|
|
4488
|
-
executedIn:
|
|
4489
|
-
description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
|
|
6360
|
+
executedIn: ranIn,
|
|
4490
6361
|
options: [
|
|
4491
6362
|
explained
|
|
4492
6363
|
],
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
6364
|
+
executedQueries: []
|
|
6365
|
+
} : {
|
|
6366
|
+
step: 0,
|
|
6367
|
+
of: 0,
|
|
6368
|
+
executedIn: {
|
|
6369
|
+
kind: "memory"
|
|
6370
|
+
},
|
|
6371
|
+
options: [
|
|
6372
|
+
explained
|
|
6373
|
+
],
|
|
6374
|
+
reason: outcome.reason,
|
|
6375
|
+
explanation: outcome.explanation ?? undefined
|
|
4497
6376
|
});
|
|
4498
6377
|
});
|
|
4499
|
-
|
|
6378
|
+
// A database step even when nothing pushed down: the plugin is dispatched either way, so
|
|
6379
|
+
// reporting "0 in the database" while the backend reads the whole table is the opposite of
|
|
6380
|
+
// the truth.
|
|
6381
|
+
if (steps[0]?.executedIn.kind !== "database") {
|
|
4500
6382
|
steps.unshift({
|
|
4501
6383
|
step: 0,
|
|
4502
6384
|
of: 0,
|
|
4503
|
-
executedIn:
|
|
4504
|
-
|
|
4505
|
-
|
|
6385
|
+
executedIn: ranIn,
|
|
6386
|
+
options: [],
|
|
6387
|
+
executedQueries: []
|
|
4506
6388
|
});
|
|
4507
6389
|
}
|
|
6390
|
+
return steps;
|
|
6391
|
+
};
|
|
6392
|
+
/** Numbers a finished list, so `step 1 of 3` reads as the shape of the whole query. */ const numbered = (steps)=>{
|
|
4508
6393
|
for(let i = 0; i < steps.length; i++){
|
|
4509
6394
|
steps[i].step = i + 1;
|
|
4510
6395
|
steps[i].of = steps.length;
|
|
@@ -4519,10 +6404,11 @@ const summarize = (steps)=>{
|
|
|
4519
6404
|
* post-join filter alone in the memory half derives back to `"database"`, and the document
|
|
4520
6405
|
* would report memory work as having run in the database.
|
|
4521
6406
|
*/ const explainQuery = (options, context)=>{
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
6407
|
+
const executionSteps = numbered(toExecutionSteps(options, {
|
|
6408
|
+
kind: "database",
|
|
6409
|
+
database: context.database,
|
|
6410
|
+
plugin: context.pluginKind
|
|
6411
|
+
}));
|
|
4526
6412
|
return {
|
|
4527
6413
|
collection: context.collection,
|
|
4528
6414
|
database: context.database,
|
|
@@ -4549,7 +6435,7 @@ const summarize = (steps)=>{
|
|
|
4549
6435
|
// Only the first database step: a plugin reports what IT ran, and everything it ran
|
|
4550
6436
|
// was sent as one dispatch. Stamping the same statements onto a second database step
|
|
4551
6437
|
// would claim they ran twice.
|
|
4552
|
-
if (step
|
|
6438
|
+
if (isDatabaseStep(step) === false || attached === true) {
|
|
4553
6439
|
return {
|
|
4554
6440
|
...step,
|
|
4555
6441
|
options: [
|
|
@@ -4582,8 +6468,47 @@ const summarize = (steps)=>{
|
|
|
4582
6468
|
executionSteps
|
|
4583
6469
|
};
|
|
4584
6470
|
};
|
|
6471
|
+
/**
|
|
6472
|
+
* Adds the step for a cross-plugin join's inner side.
|
|
6473
|
+
*
|
|
6474
|
+
* Appended by the executor rather than derived from the options, because the inner side's options
|
|
6475
|
+
* live on the join, in its own collection, and were never part of this query's chain. It goes before
|
|
6476
|
+
* the memory steps that consume it — the join cannot run until both sides are read.
|
|
6477
|
+
*/ /**
|
|
6478
|
+
* Every statement the query ran, across every database it touched, in execution order.
|
|
6479
|
+
*
|
|
6480
|
+
* A step is a place, so the statements live on the steps — this is for a caller that wants them all
|
|
6481
|
+
* without caring which plugin ran which.
|
|
6482
|
+
*/ const executedQueriesOf = (explanation)=>explanation.executionSteps.flatMap((step)=>isDatabaseStep(step) ? step.executedQueries : []);
|
|
6483
|
+
const withInnerSide = (explanation, innerSide)=>{
|
|
6484
|
+
const step = {
|
|
6485
|
+
step: 0,
|
|
6486
|
+
of: 0,
|
|
6487
|
+
executedIn: {
|
|
6488
|
+
kind: "database",
|
|
6489
|
+
database: innerSide.database,
|
|
6490
|
+
plugin: innerSide.plugin
|
|
6491
|
+
},
|
|
6492
|
+
options: [],
|
|
6493
|
+
executedQueries: innerSide.executedQueries
|
|
6494
|
+
};
|
|
6495
|
+
const firstMemory = explanation.executionSteps.findIndex((current)=>isDatabaseStep(current) === false);
|
|
6496
|
+
const at = firstMemory === -1 ? explanation.executionSteps.length : firstMemory;
|
|
6497
|
+
const executionSteps = numbered([
|
|
6498
|
+
...explanation.executionSteps.slice(0, at),
|
|
6499
|
+
step,
|
|
6500
|
+
...explanation.executionSteps.slice(at)
|
|
6501
|
+
]);
|
|
6502
|
+
return {
|
|
6503
|
+
...explanation,
|
|
6504
|
+
executionSteps,
|
|
6505
|
+
summary: summarize(executionSteps)
|
|
6506
|
+
};
|
|
6507
|
+
};
|
|
4585
6508
|
|
|
4586
6509
|
;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
|
|
6510
|
+
|
|
6511
|
+
|
|
4587
6512
|
const OPTION_LABEL_WIDTH = 8;
|
|
4588
6513
|
const WRAP_WIDTH = 68;
|
|
4589
6514
|
/** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
|
|
@@ -4609,29 +6534,41 @@ const COMPARATOR_SYMBOLS = {
|
|
|
4609
6534
|
"less-than": "<",
|
|
4610
6535
|
"less-than-equals": "<="
|
|
4611
6536
|
};
|
|
4612
|
-
const describeValue = (value)=>{
|
|
4613
|
-
if (value
|
|
6537
|
+
/** Typed against the union so a new OBJECT tag is a compile error here, not an "undefined" in output. */ const describeValue = (value)=>{
|
|
6538
|
+
if (value === null) {
|
|
6539
|
+
return "null";
|
|
6540
|
+
}
|
|
6541
|
+
if (value === undefined) {
|
|
4614
6542
|
return "?";
|
|
4615
6543
|
}
|
|
4616
|
-
if (value
|
|
4617
|
-
return
|
|
6544
|
+
if (Array.isArray(value)) {
|
|
6545
|
+
return `[${value.map(describeValue).join(", ")}]`;
|
|
6546
|
+
}
|
|
6547
|
+
if (typeof value !== "object") {
|
|
6548
|
+
return typeof value === "string" ? `"${value}"` : String(value);
|
|
6549
|
+
}
|
|
6550
|
+
if ("date" in value) {
|
|
6551
|
+
return value.date;
|
|
6552
|
+
}
|
|
6553
|
+
if ("undefined" in value) {
|
|
6554
|
+
return "undefined";
|
|
4618
6555
|
}
|
|
4619
|
-
if (
|
|
4620
|
-
return value.
|
|
6556
|
+
if ("regex" in value) {
|
|
6557
|
+
return `/${value.regex.source}/${value.regex.flags}`;
|
|
4621
6558
|
}
|
|
4622
|
-
if (
|
|
4623
|
-
return
|
|
6559
|
+
if ("bigint" in value) {
|
|
6560
|
+
return `${value.bigint}n`;
|
|
4624
6561
|
}
|
|
4625
|
-
return value.
|
|
6562
|
+
return value.number;
|
|
4626
6563
|
};
|
|
4627
6564
|
/** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
|
|
4628
6565
|
if (expression == null) {
|
|
4629
6566
|
return "?";
|
|
4630
6567
|
}
|
|
4631
|
-
if (expression.
|
|
6568
|
+
if (expression.type === "operator") {
|
|
4632
6569
|
return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
|
|
4633
6570
|
}
|
|
4634
|
-
if (expression.
|
|
6571
|
+
if (expression.type === "comparator") {
|
|
4635
6572
|
const left = describeExpression(expression.left);
|
|
4636
6573
|
const right = describeExpression(expression.right);
|
|
4637
6574
|
const symbol = COMPARATOR_SYMBOLS[expression.comparator];
|
|
@@ -4640,13 +6577,24 @@ const describeValue = (value)=>{
|
|
|
4640
6577
|
}
|
|
4641
6578
|
return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
|
|
4642
6579
|
}
|
|
4643
|
-
if (expression.
|
|
6580
|
+
if (expression.type === "property") {
|
|
4644
6581
|
return expression.path;
|
|
4645
6582
|
}
|
|
4646
|
-
if (expression.
|
|
6583
|
+
if (expression.type === "value") {
|
|
4647
6584
|
return describeValue(expression.value);
|
|
4648
6585
|
}
|
|
4649
|
-
|
|
6586
|
+
if (expression.type === "call") {
|
|
6587
|
+
return (0,callSource/* .renderCallAsJs */.a)(expression.call, ()=>describeExpression(expression.expression), ()=>(expression.arguments ?? []).map(describeExpression));
|
|
6588
|
+
}
|
|
6589
|
+
if (expression.type === "empty") {
|
|
6590
|
+
return "(no filter)";
|
|
6591
|
+
}
|
|
6592
|
+
// Distinguishable from "(not parsable)", which means the parser gave up and this runs in memory
|
|
6593
|
+
if (expression.type === "not-parsable") {
|
|
6594
|
+
return expression.reason == null ? "(not parsable)" : `(not parsable: ${expression.reason})`;
|
|
6595
|
+
}
|
|
6596
|
+
// Unreachable while the union is exhausted above; a payload from a newer sender is not.
|
|
6597
|
+
return `(unsupported: ${expression.type})`;
|
|
4650
6598
|
};
|
|
4651
6599
|
const describeOption = (option)=>{
|
|
4652
6600
|
const detail = option.detail;
|
|
@@ -4674,18 +6622,35 @@ const describeOption = (option)=>{
|
|
|
4674
6622
|
}
|
|
4675
6623
|
return "";
|
|
4676
6624
|
};
|
|
6625
|
+
/**
|
|
6626
|
+
* The sentence for a kind of step.
|
|
6627
|
+
*
|
|
6628
|
+
* Here rather than on the step: it is one of two constants keyed off `executedIn`, so carrying it in
|
|
6629
|
+
* the payload put prose beside the field it was derived from.
|
|
6630
|
+
*/ const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
|
|
6631
|
+
const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
|
|
6632
|
+
const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
|
|
6633
|
+
/** `database · orders.db · SqliteDbPlugin`, so a cross-plugin join says who ran what. */ const whereItRan = (step)=>isDatabaseStep(step) ? `database · ${step.executedIn.database} · ${step.executedIn.plugin}` : "memory";
|
|
4677
6634
|
const formatStep = (step, lines)=>{
|
|
4678
|
-
const reason = step.reason == null ? "" : ` [${step.reason}]`;
|
|
4679
|
-
lines.push(` STEP ${step.step} of ${step.of} — ${step
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
6635
|
+
const reason = isDatabaseStep(step) || step.reason == null ? "" : ` [${step.reason}]`;
|
|
6636
|
+
lines.push(` STEP ${step.step} of ${step.of} — ${whereItRan(step)}${reason}`);
|
|
6637
|
+
if (isDatabaseStep(step)) {
|
|
6638
|
+
lines.push(...wrap(step.options.length === 0 ? UNNARROWED_READ_DESCRIPTION : DATABASE_STEP_DESCRIPTION, " "));
|
|
6639
|
+
} else {
|
|
6640
|
+
lines.push(...wrap(MEMORY_STEP_DESCRIPTION, " "));
|
|
6641
|
+
if (step.explanation != null) {
|
|
6642
|
+
lines.push(...wrap(step.explanation, " "));
|
|
6643
|
+
}
|
|
4683
6644
|
}
|
|
4684
6645
|
lines.push("");
|
|
4685
6646
|
for (const option of step.options){
|
|
4686
6647
|
lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
|
|
4687
6648
|
}
|
|
4688
|
-
|
|
6649
|
+
if (isDatabaseStep(step) === false) {
|
|
6650
|
+
lines.push("");
|
|
6651
|
+
return;
|
|
6652
|
+
}
|
|
6653
|
+
for (const executed of step.executedQueries){
|
|
4689
6654
|
lines.push("");
|
|
4690
6655
|
lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
|
|
4691
6656
|
if (executed.parameters != null && executed.parameters.length > 0) {
|
|
@@ -4734,8 +6699,11 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
|
4734
6699
|
|
|
4735
6700
|
|
|
4736
6701
|
|
|
6702
|
+
|
|
4737
6703
|
// EXTERNAL MODULE: ./src/expressions/evaluate.ts
|
|
4738
6704
|
var evaluate = __webpack_require__(379);
|
|
6705
|
+
// EXTERNAL MODULE: ./src/expressions/fold.ts
|
|
6706
|
+
var fold = __webpack_require__(43);
|
|
4739
6707
|
;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
|
|
4740
6708
|
|
|
4741
6709
|
|
|
@@ -4936,7 +6904,7 @@ const serializeQueryOptions = (options)=>{
|
|
|
4936
6904
|
}
|
|
4937
6905
|
case "filter":
|
|
4938
6906
|
{
|
|
4939
|
-
const expression = types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema);
|
|
6907
|
+
const expression = (0,fold/* .foldConstantCalls */.F5)(types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema));
|
|
4940
6908
|
options.add("filter", {
|
|
4941
6909
|
filter: (0,evaluate/* .toStrictPredicate */.wS)(expression),
|
|
4942
6910
|
expression,
|
|
@@ -5620,7 +7588,8 @@ var TrampolinePipeline = __webpack_require__(416);
|
|
|
5620
7588
|
if (!(0,assertions.isPropertyExpression)(left) || !(0,assertions.isValueExpression)(right)) {
|
|
5621
7589
|
return null;
|
|
5622
7590
|
}
|
|
5623
|
-
|
|
7591
|
+
// A called property is a CallExpression, so it fails the isPropertyExpression check above
|
|
7592
|
+
if (left.property.isKey !== true || right.value == null) {
|
|
5624
7593
|
return null;
|
|
5625
7594
|
}
|
|
5626
7595
|
return {
|
|
@@ -5993,11 +7962,17 @@ class EphemeralDataPlugin {
|
|
|
5993
7962
|
* collection to pair it with three rows.
|
|
5994
7963
|
*
|
|
5995
7964
|
* `cloned` is in storage shape, so the keys are read by resolved column name.
|
|
5996
|
-
*/
|
|
5997
|
-
|
|
5998
|
-
|
|
7965
|
+
*/ /**
|
|
7966
|
+
* No statement to quote — an ephemeral store walks its own records — so the scan
|
|
7967
|
+
* is said plainly, and the PREDICATE is reported as JavaScript beside it. A count
|
|
7968
|
+
* alone leaves a reader unable to tell a filter that matched nothing from one
|
|
7969
|
+
* that was never applied.
|
|
7970
|
+
*
|
|
7971
|
+
* Before the inner side, to match execution order.
|
|
7972
|
+
*/ const described = describeFilters(operation.options.get("filter").map((entry)=>entry.option.value));
|
|
5999
7973
|
event.executedQueries.push({
|
|
6000
|
-
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory
|
|
7974
|
+
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ` + `${cloned.length === 1 ? "record" : "records"}, filter ${described.text}`,
|
|
7975
|
+
parameters: described.parameters.length > 0 ? described.parameters : undefined
|
|
6001
7976
|
});
|
|
6002
7977
|
const joinOption = operation.options.getLast("join");
|
|
6003
7978
|
const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
@@ -6172,6 +8147,29 @@ class TelemetryDbPlugin {
|
|
|
6172
8147
|
return JSON.stringify(option.value ?? null);
|
|
6173
8148
|
}
|
|
6174
8149
|
};
|
|
8150
|
+
/**
|
|
8151
|
+
* Restores a `Date` that `structuredClone` produced outside this realm.
|
|
8152
|
+
*
|
|
8153
|
+
* The clone is a real date and fails `instanceof Date`, which is what a caller checks. Mutated in
|
|
8154
|
+
* place because the clone is already private to this call.
|
|
8155
|
+
*/ const reviveDates = (value)=>{
|
|
8156
|
+
if (value == null || typeof value !== "object") {
|
|
8157
|
+
return value;
|
|
8158
|
+
}
|
|
8159
|
+
if (Object.prototype.toString.call(value) === "[object Date]") {
|
|
8160
|
+
return value instanceof Date ? value : new Date(value);
|
|
8161
|
+
}
|
|
8162
|
+
if (Array.isArray(value)) {
|
|
8163
|
+
for(let i = 0, length = value.length; i < length; i++){
|
|
8164
|
+
value[i] = reviveDates(value[i]);
|
|
8165
|
+
}
|
|
8166
|
+
return value;
|
|
8167
|
+
}
|
|
8168
|
+
for (const key of Object.keys(value)){
|
|
8169
|
+
value[key] = reviveDates(value[key]);
|
|
8170
|
+
}
|
|
8171
|
+
return value;
|
|
8172
|
+
};
|
|
6175
8173
|
class CacheDbPlugin {
|
|
6176
8174
|
plugin;
|
|
6177
8175
|
max;
|
|
@@ -6210,7 +8208,7 @@ class CacheDbPlugin {
|
|
|
6210
8208
|
* the next update would be written UNCHECKED with no error anywhere.
|
|
6211
8209
|
* Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
|
|
6212
8210
|
*/ rebuild(entry) {
|
|
6213
|
-
return new entry.construct(structuredClone(entry.value), entry.isTransformed);
|
|
8211
|
+
return new entry.construct(reviveDates(structuredClone(entry.value)), entry.isTransformed);
|
|
6214
8212
|
}
|
|
6215
8213
|
query(event, done) {
|
|
6216
8214
|
const key = this.keyFor(event);
|
|
@@ -6233,6 +8231,14 @@ class CacheDbPlugin {
|
|
|
6233
8231
|
done(result);
|
|
6234
8232
|
return;
|
|
6235
8233
|
}
|
|
8234
|
+
// A partial answer must never be cached. When the plugin reports an option it cannot
|
|
8235
|
+
// express, these rows are what came back BEFORE the datastore finished the query — and a
|
|
8236
|
+
// later hit skips the plugin entirely, so nothing would report and the rows would be
|
|
8237
|
+
// returned as if they were the whole answer. Unfiltered, silently.
|
|
8238
|
+
if (event.operation.options.notExecuted().length > 0) {
|
|
8239
|
+
done(Result/* .PluginEventResult.success */.D.success(event.id, result.data));
|
|
8240
|
+
return;
|
|
8241
|
+
}
|
|
6236
8242
|
this.store(key, result.data);
|
|
6237
8243
|
// The caller gets a rebuilt value too, not the one just stored, so that mutating
|
|
6238
8244
|
// the result of a MISS cannot corrupt what the next hit returns.
|
|
@@ -6610,6 +8616,7 @@ const DEFAULT_MAX_BATCH_SIZE = 100;
|
|
|
6610
8616
|
|
|
6611
8617
|
|
|
6612
8618
|
|
|
8619
|
+
|
|
6613
8620
|
})();
|
|
6614
8621
|
|
|
6615
8622
|
module.exports = __webpack_exports__;
|