@quereus/quereus 4.8.0 → 4.10.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/dist/src/core/database.d.ts +6 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +45 -5
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +74 -15
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +88 -35
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/conversion.d.ts +5 -0
- package/dist/src/func/builtins/conversion.d.ts.map +1 -1
- package/dist/src/func/builtins/conversion.js +16 -2
- package/dist/src/func/builtins/conversion.js.map +1 -1
- package/dist/src/func/builtins/explain.d.ts.map +1 -1
- package/dist/src/func/builtins/explain.js +9 -2
- package/dist/src/func/builtins/explain.js.map +1 -1
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +5 -2
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/func/builtins/scalar.d.ts.map +1 -1
- package/dist/src/func/builtins/scalar.js +4 -2
- package/dist/src/func/builtins/scalar.js.map +1 -1
- package/dist/src/func/builtins/string.d.ts +2 -0
- package/dist/src/func/builtins/string.d.ts.map +1 -1
- package/dist/src/func/builtins/string.js +60 -4
- package/dist/src/func/builtins/string.js.map +1 -1
- package/dist/src/func/registration.d.ts +6 -0
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +1 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +1 -1
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +13 -1
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +121 -51
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.d.ts +14 -4
- package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
- package/dist/src/planner/rules/shared/index-style-context.js +14 -4
- package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
- package/dist/src/runtime/emission-context.d.ts +14 -1
- package/dist/src/runtime/emission-context.d.ts.map +1 -1
- package/dist/src/runtime/emission-context.js +17 -5
- package/dist/src/runtime/emission-context.js.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.d.ts +44 -8
- package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.js +79 -15
- package/dist/src/runtime/emit/aggregate-setup.js.map +1 -1
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +21 -106
- package/dist/src/runtime/emit/aggregate.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +212 -155
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +1 -1
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/assertion-drop-guard.d.ts +26 -12
- package/dist/src/runtime/emit/assertion-drop-guard.d.ts.map +1 -1
- package/dist/src/runtime/emit/assertion-drop-guard.js +45 -26
- package/dist/src/runtime/emit/assertion-drop-guard.js.map +1 -1
- package/dist/src/runtime/emit/assertion-rename-helpers.d.ts +6 -6
- package/dist/src/runtime/emit/assertion-rename-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/assertion-rename-helpers.js +18 -20
- package/dist/src/runtime/emit/assertion-rename-helpers.js.map +1 -1
- package/dist/src/runtime/emit/between.d.ts +2 -0
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +7 -8
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts +27 -5
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +278 -187
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/case.d.ts +34 -0
- package/dist/src/runtime/emit/case.d.ts.map +1 -1
- package/dist/src/runtime/emit/case.js +18 -11
- package/dist/src/runtime/emit/case.js.map +1 -1
- package/dist/src/runtime/emit/cast.d.ts +2 -0
- package/dist/src/runtime/emit/cast.d.ts.map +1 -1
- package/dist/src/runtime/emit/cast.js +7 -5
- package/dist/src/runtime/emit/cast.js.map +1 -1
- package/dist/src/runtime/emit/column-reference.d.ts +3 -1
- package/dist/src/runtime/emit/column-reference.d.ts.map +1 -1
- package/dist/src/runtime/emit/column-reference.js +6 -2
- package/dist/src/runtime/emit/column-reference.js.map +1 -1
- package/dist/src/runtime/emit/column-rename-cascade.d.ts +84 -0
- package/dist/src/runtime/emit/column-rename-cascade.d.ts.map +1 -0
- package/dist/src/runtime/emit/column-rename-cascade.js +134 -0
- package/dist/src/runtime/emit/column-rename-cascade.js.map +1 -0
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +21 -0
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/drop-column-guards.d.ts +82 -53
- package/dist/src/runtime/emit/drop-column-guards.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-column-guards.js +155 -90
- package/dist/src/runtime/emit/drop-column-guards.js.map +1 -1
- package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-table.js +5 -0
- package/dist/src/runtime/emit/drop-table.js.map +1 -1
- package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/drop-view.js +5 -0
- package/dist/src/runtime/emit/drop-view.js.map +1 -1
- package/dist/src/runtime/emit/expression-drop-guard.d.ts +57 -0
- package/dist/src/runtime/emit/expression-drop-guard.d.ts.map +1 -0
- package/dist/src/runtime/emit/expression-drop-guard.js +82 -0
- package/dist/src/runtime/emit/expression-drop-guard.js.map +1 -0
- package/dist/src/runtime/emit/filter.d.ts +1 -1
- package/dist/src/runtime/emit/filter.js +1 -1
- package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.js +18 -88
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/literal.d.ts +14 -1
- package/dist/src/runtime/emit/literal.d.ts.map +1 -1
- package/dist/src/runtime/emit/literal.js +33 -7
- package/dist/src/runtime/emit/literal.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +33 -10
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +158 -49
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +3 -0
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/parameter.d.ts +3 -1
- package/dist/src/runtime/emit/parameter.d.ts.map +1 -1
- package/dist/src/runtime/emit/parameter.js +7 -2
- package/dist/src/runtime/emit/parameter.js.map +1 -1
- package/dist/src/runtime/emit/scalar-function.d.ts +29 -1
- package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
- package/dist/src/runtime/emit/scalar-function.js +66 -16
- package/dist/src/runtime/emit/scalar-function.js.map +1 -1
- package/dist/src/runtime/emit/scalar-op.d.ts +63 -0
- package/dist/src/runtime/emit/scalar-op.d.ts.map +1 -0
- package/dist/src/runtime/emit/scalar-op.js +37 -0
- package/dist/src/runtime/emit/scalar-op.js.map +1 -0
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +19 -1
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts +18 -8
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js +29 -311
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
- package/dist/src/runtime/emit/unary.d.ts +2 -0
- package/dist/src/runtime/emit/unary.d.ts.map +1 -1
- package/dist/src/runtime/emit/unary.js +125 -53
- package/dist/src/runtime/emit/unary.js.map +1 -1
- package/dist/src/runtime/emitters.d.ts +22 -0
- package/dist/src/runtime/emitters.d.ts.map +1 -1
- package/dist/src/runtime/emitters.js +29 -0
- package/dist/src/runtime/emitters.js.map +1 -1
- package/dist/src/runtime/scalar-fusion.d.ts +47 -0
- package/dist/src/runtime/scalar-fusion.d.ts.map +1 -0
- package/dist/src/runtime/scalar-fusion.js +275 -0
- package/dist/src/runtime/scalar-fusion.js.map +1 -0
- package/dist/src/runtime/strict-flags.d.ts +9 -0
- package/dist/src/runtime/strict-flags.d.ts.map +1 -1
- package/dist/src/runtime/strict-flags.js +9 -0
- package/dist/src/runtime/strict-flags.js.map +1 -1
- package/dist/src/runtime/strict-fork.js +2 -2
- package/dist/src/runtime/strict-fork.js.map +1 -1
- package/dist/src/runtime/strict-representation.d.ts +78 -0
- package/dist/src/runtime/strict-representation.d.ts.map +1 -0
- package/dist/src/runtime/strict-representation.js +159 -0
- package/dist/src/runtime/strict-representation.js.map +1 -0
- package/dist/src/schema/assertion.d.ts +58 -0
- package/dist/src/schema/assertion.d.ts.map +1 -1
- package/dist/src/schema/assertion.js +78 -0
- package/dist/src/schema/assertion.js.map +1 -1
- package/dist/src/schema/catalog-persistability.d.ts +11 -3
- package/dist/src/schema/catalog-persistability.d.ts.map +1 -1
- package/dist/src/schema/catalog-persistability.js +48 -29
- package/dist/src/schema/catalog-persistability.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +22 -0
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +4 -1
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/column-republication.d.ts +126 -0
- package/dist/src/schema/column-republication.d.ts.map +1 -0
- package/dist/src/schema/column-republication.js +121 -0
- package/dist/src/schema/column-republication.js.map +1 -0
- package/dist/src/schema/column-source-resolver.d.ts +29 -6
- package/dist/src/schema/column-source-resolver.d.ts.map +1 -1
- package/dist/src/schema/column-source-resolver.js +78 -9
- package/dist/src/schema/column-source-resolver.js.map +1 -1
- package/dist/src/schema/expression-dependents.d.ts +160 -0
- package/dist/src/schema/expression-dependents.d.ts.map +1 -0
- package/dist/src/schema/expression-dependents.js +228 -0
- package/dist/src/schema/expression-dependents.js.map +1 -0
- package/dist/src/schema/function.d.ts +15 -0
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/object-dependency-closure.d.ts +127 -0
- package/dist/src/schema/object-dependency-closure.d.ts.map +1 -0
- package/dist/src/schema/object-dependency-closure.js +117 -0
- package/dist/src/schema/object-dependency-closure.js.map +1 -0
- package/dist/src/schema/object-ref-resolver.d.ts +83 -0
- package/dist/src/schema/object-ref-resolver.d.ts.map +1 -0
- package/dist/src/schema/object-ref-resolver.js +135 -0
- package/dist/src/schema/object-ref-resolver.js.map +1 -0
- package/dist/src/schema/rename/column-rename.d.ts +228 -0
- package/dist/src/schema/rename/column-rename.d.ts.map +1 -0
- package/dist/src/schema/rename/column-rename.js +1225 -0
- package/dist/src/schema/rename/column-rename.js.map +1 -0
- package/dist/src/schema/rename/self-qualifier-strip.d.ts +29 -0
- package/dist/src/schema/rename/self-qualifier-strip.d.ts.map +1 -0
- package/dist/src/schema/rename/self-qualifier-strip.js +267 -0
- package/dist/src/schema/rename/self-qualifier-strip.js.map +1 -0
- package/dist/src/schema/rename/shared.d.ts +108 -0
- package/dist/src/schema/rename/shared.d.ts.map +1 -0
- package/dist/src/schema/rename/shared.js +49 -0
- package/dist/src/schema/rename/shared.js.map +1 -0
- package/dist/src/schema/rename/table-rename.d.ts +263 -0
- package/dist/src/schema/rename/table-rename.d.ts.map +1 -0
- package/dist/src/schema/rename/table-rename.js +860 -0
- package/dist/src/schema/rename/table-rename.js.map +1 -0
- package/dist/src/schema/rename-rewriter.d.ts +18 -278
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +17 -1506
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +228 -21
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/types/builtin-types.d.ts.map +1 -1
- package/dist/src/types/builtin-types.js +29 -36
- package/dist/src/types/builtin-types.js.map +1 -1
- package/dist/src/types/cast-semantics.d.ts +7 -5
- package/dist/src/types/cast-semantics.d.ts.map +1 -1
- package/dist/src/types/cast-semantics.js +20 -9
- package/dist/src/types/cast-semantics.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/registry.d.ts.map +1 -1
- package/dist/src/types/registry.js +2 -1
- package/dist/src/types/registry.js.map +1 -1
- package/dist/src/types/temporal-ops.d.ts +119 -0
- package/dist/src/types/temporal-ops.d.ts.map +1 -0
- package/dist/src/types/temporal-ops.js +348 -0
- package/dist/src/types/temporal-ops.js.map +1 -0
- package/dist/src/types/temporal-types.d.ts +11 -0
- package/dist/src/types/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +87 -0
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/util/affinity.d.ts +2 -1
- package/dist/src/util/affinity.d.ts.map +1 -1
- package/dist/src/util/affinity.js +16 -5
- package/dist/src/util/affinity.js.map +1 -1
- package/dist/src/util/ast-spine-clone.d.ts +2 -2
- package/dist/src/util/ast-spine-clone.js +2 -2
- package/dist/src/util/coercion.d.ts +22 -7
- package/dist/src/util/coercion.d.ts.map +1 -1
- package/dist/src/util/coercion.js +28 -15
- package/dist/src/util/coercion.js.map +1 -1
- package/dist/src/util/comparison.d.ts +25 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +25 -0
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/util/key-tuple-codec.d.ts +12 -3
- package/dist/src/util/key-tuple-codec.d.ts.map +1 -1
- package/dist/src/util/key-tuple-codec.js.map +1 -1
- package/dist/src/util/numeric-canonical.d.ts +26 -0
- package/dist/src/util/numeric-canonical.d.ts.map +1 -0
- package/dist/src/util/numeric-canonical.js +66 -0
- package/dist/src/util/numeric-canonical.js.map +1 -0
- package/dist/src/util/value-text.d.ts +48 -0
- package/dist/src/util/value-text.d.ts.map +1 -0
- package/dist/src/util/value-text.js +35 -0
- package/dist/src/util/value-text.js.map +1 -0
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +14 -5
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/module.d.ts +25 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -6,10 +6,25 @@ import { hasRelationalDescendant } from "../../planner/analysis/scalar-subquerie
|
|
|
6
6
|
import { emitPlanNode, emitCallFromPlan } from "../emitters.js";
|
|
7
7
|
import { compareSqlValuesFast, createTypedComparator, hasSemanticOrdering, isTruthy } from "../../util/comparison.js";
|
|
8
8
|
import { coerceToNumberForArithmetic } from "../../util/coercion.js";
|
|
9
|
+
import { canonicalizeInteger } from "../../util/numeric-canonical.js";
|
|
10
|
+
import { valueToText } from "../../util/value-text.js";
|
|
9
11
|
import { simpleLike, compileLikeMatcher } from "../../util/patterns.js";
|
|
10
12
|
import { tryTemporalArithmetic, tryTemporalComparison } from "./temporal-arithmetic.js";
|
|
13
|
+
import { runTemporalCase, temporalOpCaseForTypes, unsupportedTemporalOp } from "../../types/temporal-ops.js";
|
|
11
14
|
import { effectiveComparisonCollation } from "../../planner/analysis/comparison-collation.js";
|
|
12
|
-
|
|
15
|
+
import { emitScalarOp } from "./scalar-op.js";
|
|
16
|
+
/**
|
|
17
|
+
* Operator dispatch for a binary node — the single entry point both consumers share, so
|
|
18
|
+
* neither has to restate which operator routes to which body. `undefined` means the node
|
|
19
|
+
* has no {@link ScalarOpSpec}: today that is exactly the AND/OR short-circuit form, whose
|
|
20
|
+
* right operand is a deferred sub-program rather than a value (see
|
|
21
|
+
* {@link buildLogicalOpSpec}). A fusion consumer treats `undefined` as "do not fuse";
|
|
22
|
+
* {@link emitBinaryOp} falls back to {@link emitShortCircuitLogicalOp}.
|
|
23
|
+
*
|
|
24
|
+
* An unsupported operator throws here rather than returning `undefined` — that is an
|
|
25
|
+
* unimplemented operator, not a node that declines to fuse.
|
|
26
|
+
*/
|
|
27
|
+
export function buildBinaryOpSpec(plan, ctx) {
|
|
13
28
|
// Normalize operator to uppercase for case-insensitive matching of keywords
|
|
14
29
|
const operator = plan.expression.operator.toUpperCase();
|
|
15
30
|
switch (operator) {
|
|
@@ -18,7 +33,7 @@ export function emitBinaryOp(plan, ctx) {
|
|
|
18
33
|
case '*':
|
|
19
34
|
case '/':
|
|
20
35
|
case '%':
|
|
21
|
-
return
|
|
36
|
+
return buildNumericOpSpec(plan);
|
|
22
37
|
case '=':
|
|
23
38
|
case '==':
|
|
24
39
|
case '!=':
|
|
@@ -27,20 +42,24 @@ export function emitBinaryOp(plan, ctx) {
|
|
|
27
42
|
case '<=':
|
|
28
43
|
case '>':
|
|
29
44
|
case '>=':
|
|
30
|
-
return
|
|
45
|
+
return buildComparisonOpSpec(plan, ctx);
|
|
31
46
|
case '||':
|
|
32
|
-
return
|
|
47
|
+
return buildConcatOpSpec(plan);
|
|
33
48
|
case 'AND':
|
|
34
49
|
case 'OR':
|
|
35
50
|
case 'XOR':
|
|
36
|
-
return
|
|
51
|
+
return buildLogicalOpSpec(plan);
|
|
37
52
|
case 'LIKE':
|
|
38
|
-
return
|
|
39
|
-
// TODO:
|
|
53
|
+
return buildLikeOpSpec(plan);
|
|
54
|
+
// TODO: bitwise operators
|
|
40
55
|
default:
|
|
41
56
|
quereusError(`Unsupported binary operator: ${plan.expression.operator}`, StatusCode.UNSUPPORTED, undefined, plan.expression);
|
|
42
57
|
}
|
|
43
58
|
}
|
|
59
|
+
export function emitBinaryOp(plan, ctx) {
|
|
60
|
+
const spec = buildBinaryOpSpec(plan, ctx);
|
|
61
|
+
return spec ? emitScalarOp(spec, ctx) : emitShortCircuitLogicalOp(plan, ctx);
|
|
62
|
+
}
|
|
44
63
|
/** Handle arithmetic when at least one operand is bigint.
|
|
45
64
|
* Both bigint → use bigint arithmetic directly.
|
|
46
65
|
* Mixed (one bigint, one non-bigint):
|
|
@@ -50,7 +69,11 @@ export function emitBinaryOp(plan, ctx) {
|
|
|
50
69
|
function mixedBigIntArithmetic(v1, v2, inner, innerBigInt) {
|
|
51
70
|
if (typeof v1 === 'bigint' && typeof v2 === 'bigint') {
|
|
52
71
|
try {
|
|
53
|
-
|
|
72
|
+
// Narrow a result that lands inside the safe-integer range back to number
|
|
73
|
+
// (R1, util/numeric-canonical.ts) — on the success path only; the catch
|
|
74
|
+
// arms (division by zero, RangeError) still return null. Narrowing itself
|
|
75
|
+
// cannot throw.
|
|
76
|
+
return canonicalizeInteger(innerBigInt(v1, v2));
|
|
54
77
|
}
|
|
55
78
|
catch {
|
|
56
79
|
return null;
|
|
@@ -64,7 +87,8 @@ function mixedBigIntArithmetic(v1, v2, inner, innerBigInt) {
|
|
|
64
87
|
const num = typeof v1n === 'bigint' ? v2n : v1n;
|
|
65
88
|
if (Number.isInteger(num)) {
|
|
66
89
|
try {
|
|
67
|
-
|
|
90
|
+
// Same success-path narrowing as the both-bigint arm above.
|
|
91
|
+
return canonicalizeInteger(innerBigInt(typeof v1n === 'bigint' ? v1n : BigInt(v1n), typeof v2n === 'bigint' ? v2n : BigInt(v2n)));
|
|
68
92
|
}
|
|
69
93
|
catch {
|
|
70
94
|
// Fall through to float path (e.g., division by zero)
|
|
@@ -78,7 +102,43 @@ function mixedBigIntArithmetic(v1, v2, inner, innerBigInt) {
|
|
|
78
102
|
return null;
|
|
79
103
|
return result;
|
|
80
104
|
}
|
|
81
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The general arithmetic body: sniff the values for a temporal shape first, then fall back
|
|
107
|
+
* to coercing numeric arithmetic.
|
|
108
|
+
*
|
|
109
|
+
* Two branches of {@link buildNumericOpSpec} share it — the temporal arm whose declared
|
|
110
|
+
* types settle nothing, and the non-temporal generic path — because value sniffing is the
|
|
111
|
+
* defined semantics for both. They differ only in the note they carry; keeping one body
|
|
112
|
+
* means they cannot come to differ in behavior.
|
|
113
|
+
*/
|
|
114
|
+
function buildCoercingArithmeticRun(operator, inner, innerBigInt) {
|
|
115
|
+
return function runCoercingArithmetic(_ctx, v1, v2) {
|
|
116
|
+
const temporalResult = tryTemporalArithmetic(operator, v1, v2);
|
|
117
|
+
if (temporalResult !== undefined) {
|
|
118
|
+
return temporalResult;
|
|
119
|
+
}
|
|
120
|
+
if (v1 !== null && v2 !== null) {
|
|
121
|
+
if (typeof v1 === 'bigint' || typeof v2 === 'bigint') {
|
|
122
|
+
return mixedBigIntArithmetic(v1, v2, inner, innerBigInt);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const n1 = coerceToNumberForArithmetic(v1);
|
|
126
|
+
const n2 = coerceToNumberForArithmetic(v2);
|
|
127
|
+
try {
|
|
128
|
+
const result = inner(n1, n2);
|
|
129
|
+
if (!Number.isFinite(result))
|
|
130
|
+
return null;
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function buildNumericOpSpec(plan) {
|
|
82
142
|
let inner;
|
|
83
143
|
let innerBigInt;
|
|
84
144
|
switch (plan.expression.operator) {
|
|
@@ -111,51 +171,79 @@ export function emitNumericOp(plan, ctx) {
|
|
|
111
171
|
let run;
|
|
112
172
|
let note;
|
|
113
173
|
if (leftLogical.isTemporal || rightLogical.isTemporal) {
|
|
114
|
-
// Temporal path
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
// Temporal path. Both operands' declared logical types are already in hand — that
|
|
175
|
+
// is *why* this branch was selected — so the (operator, left kind, right kind)
|
|
176
|
+
// lookup into the operation table (types/temporal-ops.ts) happens once here rather
|
|
177
|
+
// than once per row. Same lookup `BinaryOpNode.generateType` used to announce the
|
|
178
|
+
// result type, through the same function, so the two cannot select different cases.
|
|
179
|
+
// Three arms, chosen at emit:
|
|
180
|
+
const { kinds, entry } = temporalOpCaseForTypes(plan.expression.operator, leftLogical, rightLogical);
|
|
181
|
+
if (kinds && entry) {
|
|
182
|
+
// (1) Specialized: the case is fixed, so the per-row path is one call into the
|
|
183
|
+
// shared envelope — no value sniffing at all.
|
|
184
|
+
//
|
|
185
|
+
// This arm TRUSTS the declared types. A DATE-declared operand actually holding
|
|
186
|
+
// `'garbage'` returns null here (runTemporalCase's catch), where the sniffing
|
|
187
|
+
// path below would have raised `Unsupported temporal operation`. That is the
|
|
188
|
+
// intended trade: write-side coercion enforces declared logical types on every
|
|
189
|
+
// normal path, so only a misbehaving virtual table can produce such a value.
|
|
190
|
+
run = function runTemporalCased(_ctx, v1, v2) {
|
|
191
|
+
return runTemporalCase(entry, v1, v2);
|
|
192
|
+
};
|
|
193
|
+
note = `${plan.expression.operator}(temporal-${kinds[0]}-${kinds[1]})`;
|
|
194
|
+
}
|
|
195
|
+
else if (kinds) {
|
|
196
|
+
// (2) Statically unsupported: both kinds are known and the table has no case
|
|
197
|
+
// for them (DATE + DATE, DATE * NUMBER, TIME − DATE, anything with `%`), so no
|
|
198
|
+
// combination of runtime values can succeed.
|
|
199
|
+
//
|
|
200
|
+
// The throw deliberately stays at RUNTIME rather than moving to emit/plan time.
|
|
201
|
+
// Today the error is raised only when a row is actually evaluated, so
|
|
202
|
+
// `select case when 0 then date_a + date_b else 1 end`, the same expression
|
|
203
|
+
// under `where 1 = 0`, and the same expression over an empty table all succeed.
|
|
204
|
+
// Hoisting the throw would turn each into a hard failure for no gain — a
|
|
205
|
+
// constant-throw closure costs nothing per row on the paths that never run it.
|
|
206
|
+
//
|
|
207
|
+
// The null check is load-bearing: `date_a + date_b` with a NULL operand returns
|
|
208
|
+
// null today rather than erroring, and must keep doing so.
|
|
209
|
+
run = function runTemporalUnsupported(_ctx, v1, v2) {
|
|
210
|
+
if (v1 === null || v2 === null)
|
|
211
|
+
return null;
|
|
212
|
+
unsupportedTemporalOp();
|
|
213
|
+
};
|
|
214
|
+
note = `${plan.expression.operator}(temporal-unsupported)`;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
// (3) Fallback: at least one operand is TEXT / ANY / NULL / TIMESTAMP / a
|
|
218
|
+
// plugin-registered temporal type, so the declared types cannot settle the
|
|
219
|
+
// case. Runtime sniffing IS the defined semantics there (a TEXT column holding
|
|
220
|
+
// a duration string is a supported shape), so this arm is the generic body
|
|
221
|
+
// below under a different note — same function, so they cannot drift.
|
|
222
|
+
run = buildCoercingArithmeticRun(plan.expression.operator, inner, innerBigInt);
|
|
223
|
+
note = `${plan.expression.operator}(temporal)`;
|
|
224
|
+
}
|
|
141
225
|
}
|
|
142
226
|
else if (leftLogical.isNumeric && rightLogical.isNumeric) {
|
|
143
227
|
// Numeric-only path: skip temporal check and coercion entirely
|
|
228
|
+
//
|
|
229
|
+
// NOTE: accepted tradeoff — the two null checks plus two `typeof === 'bigint'`
|
|
230
|
+
// checks below are NOT collapsed into one `typeof === 'number'` pair with the
|
|
231
|
+
// null/bigint handling behind a fallback. Measured at 1.19 ns vs 1.22 ns per
|
|
232
|
+
// operation (isolated microbench, see `compareSqlValuesFast` in
|
|
233
|
+
// util/comparison.ts) — no difference at all, so the guard would be pure noise.
|
|
144
234
|
run = function runNumericOnly(ctx, v1, v2) {
|
|
145
235
|
if (v1 !== null && v2 !== null) {
|
|
146
236
|
if (typeof v1 === 'bigint' || typeof v2 === 'bigint') {
|
|
147
237
|
return mixedBigIntArithmetic(v1, v2, inner, innerBigInt);
|
|
148
238
|
}
|
|
149
239
|
else {
|
|
150
|
-
try
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
240
|
+
// No try/catch: `inner` over plain numbers cannot throw (division by
|
|
241
|
+
// zero yields Infinity/NaN, caught by the finite check below); only
|
|
242
|
+
// the bigint arm above can throw (handled inside mixedBigIntArithmetic).
|
|
243
|
+
const result = inner(v1, v2);
|
|
244
|
+
if (!Number.isFinite(result))
|
|
157
245
|
return null;
|
|
158
|
-
|
|
246
|
+
return result;
|
|
159
247
|
}
|
|
160
248
|
}
|
|
161
249
|
return null;
|
|
@@ -164,42 +252,16 @@ export function emitNumericOp(plan, ctx) {
|
|
|
164
252
|
}
|
|
165
253
|
else {
|
|
166
254
|
// Generic path: temporal check + coercion (for TEXT or mixed types)
|
|
167
|
-
run =
|
|
168
|
-
const temporalResult = tryTemporalArithmetic(plan.expression.operator, v1, v2);
|
|
169
|
-
if (temporalResult !== undefined) {
|
|
170
|
-
return temporalResult;
|
|
171
|
-
}
|
|
172
|
-
if (v1 !== null && v2 !== null) {
|
|
173
|
-
if (typeof v1 === 'bigint' || typeof v2 === 'bigint') {
|
|
174
|
-
return mixedBigIntArithmetic(v1, v2, inner, innerBigInt);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
const n1 = coerceToNumberForArithmetic(v1);
|
|
178
|
-
const n2 = coerceToNumberForArithmetic(v2);
|
|
179
|
-
try {
|
|
180
|
-
const result = inner(n1, n2);
|
|
181
|
-
if (!Number.isFinite(result))
|
|
182
|
-
return null;
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
};
|
|
255
|
+
run = buildCoercingArithmeticRun(plan.expression.operator, inner, innerBigInt);
|
|
192
256
|
note = `${plan.expression.operator}(numeric)`;
|
|
193
257
|
}
|
|
194
|
-
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
195
|
-
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
196
258
|
return {
|
|
197
|
-
|
|
198
|
-
run
|
|
259
|
+
operands: [plan.left, plan.right],
|
|
260
|
+
run,
|
|
199
261
|
note
|
|
200
262
|
};
|
|
201
263
|
}
|
|
202
|
-
export function
|
|
264
|
+
export function buildComparisonOpSpec(plan, ctx) {
|
|
203
265
|
const leftType = plan.left.getType();
|
|
204
266
|
const rightType = plan.right.getType();
|
|
205
267
|
// One shared, symmetric resolution for plan-time facts and runtime behavior
|
|
@@ -241,6 +303,11 @@ export function emitComparisonOp(plan, ctx) {
|
|
|
241
303
|
else if (!needsTemporalCheck && bothSameCategory) {
|
|
242
304
|
// Fast same-category comparison: no temporal check, no coercion needed
|
|
243
305
|
// Use compareSqlValuesFast which handles runtime type mismatches gracefully
|
|
306
|
+
//
|
|
307
|
+
// NOTE: accepted tradeoff — this does NOT further specialize on the known category
|
|
308
|
+
// (both-TEXT → guarded `typeof` pair → collation call, both-numeric → guarded
|
|
309
|
+
// inline three-way). Measured and declined; the numbers and the revisit condition
|
|
310
|
+
// are on `compareSqlValuesFast` in util/comparison.ts.
|
|
244
311
|
const cmpToResult = buildCmpToResult(operator, plan);
|
|
245
312
|
run = function runSameCategoryCompare(ctx, v1, v2) {
|
|
246
313
|
if (v1 === null || v2 === null)
|
|
@@ -254,11 +321,9 @@ export function emitComparisonOp(plan, ctx) {
|
|
|
254
321
|
run = buildGenericComparisonRun(operator, plan, collationFunc);
|
|
255
322
|
noteTag = 'compare';
|
|
256
323
|
}
|
|
257
|
-
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
258
|
-
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
259
324
|
return {
|
|
260
|
-
|
|
261
|
-
run
|
|
325
|
+
operands: [plan.left, plan.right],
|
|
326
|
+
run,
|
|
262
327
|
note: `${plan.expression.operator}(${noteTag}${collationName !== 'BINARY' ? ` ${collationName}` : ''})`
|
|
263
328
|
};
|
|
264
329
|
}
|
|
@@ -297,121 +362,146 @@ function buildGenericComparisonRun(operator, plan, collationFunc) {
|
|
|
297
362
|
return cmpToResult(compareSqlValuesFast(v1, v2, collationFunc));
|
|
298
363
|
};
|
|
299
364
|
}
|
|
300
|
-
export function
|
|
365
|
+
export function buildConcatOpSpec(plan) {
|
|
301
366
|
function run(ctx, v1, v2) {
|
|
302
367
|
// SQL concatenation: NULL || anything -> NULL
|
|
303
368
|
if (v1 === null || v2 === null)
|
|
304
369
|
return null;
|
|
305
|
-
// Convert both operands to
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return s1 + s2;
|
|
370
|
+
// Convert both operands through the shared value-to-text rule, so `b || ''`
|
|
371
|
+
// spells a blob or a JSON document exactly as `cast(b as text)` does.
|
|
372
|
+
return valueToText(v1) + valueToText(v2);
|
|
309
373
|
}
|
|
310
|
-
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
311
|
-
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
312
374
|
return {
|
|
313
|
-
|
|
314
|
-
run
|
|
375
|
+
operands: [plan.left, plan.right],
|
|
376
|
+
run,
|
|
315
377
|
note: '||(concat)'
|
|
316
378
|
};
|
|
317
379
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
380
|
+
/** false dominates; else NULL if any operand is NULL; else true. */
|
|
381
|
+
const combineAnd = (b1, b2) => {
|
|
382
|
+
if (b1 === false || b2 === false)
|
|
383
|
+
return false;
|
|
384
|
+
if (b1 === null || b2 === null)
|
|
385
|
+
return null;
|
|
386
|
+
return true;
|
|
387
|
+
};
|
|
388
|
+
/** true dominates; else NULL if any operand is NULL; else false. */
|
|
389
|
+
const combineOr = (b1, b2) => {
|
|
390
|
+
if (b1 === true || b2 === true)
|
|
391
|
+
return true;
|
|
392
|
+
if (b1 === null || b2 === null)
|
|
393
|
+
return null;
|
|
394
|
+
return false;
|
|
395
|
+
};
|
|
396
|
+
/** NULL with anything -> NULL; else logical inequality. */
|
|
397
|
+
const combineXor = (b1, b2) => {
|
|
398
|
+
if (b1 === null || b2 === null)
|
|
399
|
+
return null;
|
|
400
|
+
return b1 !== b2;
|
|
401
|
+
};
|
|
402
|
+
function selectLogicalCombine(operator, plan) {
|
|
403
|
+
switch (operator) {
|
|
404
|
+
case 'AND': return combineAnd;
|
|
405
|
+
case 'OR': return combineOr;
|
|
406
|
+
case 'XOR': return combineXor;
|
|
407
|
+
default:
|
|
408
|
+
quereusError(`Unsupported logical operator: ${plan.expression.operator}`, StatusCode.UNSUPPORTED, undefined, plan.expression);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* The 3VL combine over raw operand values, resolved once at emit time (not per row) —
|
|
413
|
+
* single source of truth shared by the eager spec body and the deferred short-circuit
|
|
414
|
+
* path below, so the two cannot diverge (the parity tests in
|
|
415
|
+
* test/and-or-short-circuit.spec.ts guard exactly this).
|
|
416
|
+
*
|
|
417
|
+
* Coerces non-NULL operands to a boolean using SQL truthiness (isTruthy) rather than
|
|
418
|
+
* JS truthiness so that values like blobs and non-numeric strings agree with how
|
|
419
|
+
* FilterNode/CASE/NOT treat them — otherwise `<blob> AND true` and a bare `<blob>`
|
|
420
|
+
* predicate diverge.
|
|
421
|
+
*/
|
|
422
|
+
function buildCombineLogical(operator, plan) {
|
|
423
|
+
const combine = selectLogicalCombine(operator, plan);
|
|
424
|
+
return function combineLogical(v1, v2) {
|
|
329
425
|
const b1 = v1 === null ? null : isTruthy(v1);
|
|
330
426
|
const b2 = v2 === null ? null : isTruthy(v2);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
427
|
+
return combine(b1, b2);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Short-circuit deferral gate: AND/OR whose right operand contains a subquery
|
|
432
|
+
* (a scalar/IN/EXISTS subquery — a relational descendant). Such an operand is emitted
|
|
433
|
+
* as an on-demand callback and evaluated lazily, only when the left operand does not
|
|
434
|
+
* already decide the result (`false AND x` → false; `true OR x` → true). This stops an
|
|
435
|
+
* expensive/side-effecting subquery from running on every row for nothing. Left stays
|
|
436
|
+
* eager — it is always needed and always evaluated first, so operand order is unchanged.
|
|
437
|
+
*
|
|
438
|
+
* NOTE: only a *subquery* right operand defers. A non-subquery expensive scalar operand
|
|
439
|
+
* (deeply nested arithmetic, or a volatile/slow UDF called directly rather than inside a
|
|
440
|
+
* subquery) is still evaluated eagerly. Such operands are rare and the dominant expensive
|
|
441
|
+
* case in SQL is the subquery; if a non-subquery volatile/expensive scalar operand ever
|
|
442
|
+
* shows up hot, extend this gate with a cost or volatility check.
|
|
443
|
+
*/
|
|
444
|
+
function usesShortCircuit(plan, operator) {
|
|
445
|
+
return (operator === 'AND' || operator === 'OR') && hasRelationalDescendant(plan.right);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Eager two-operand combine. Used for XOR (both operands always required) and for
|
|
449
|
+
* AND/OR whose right operand is cheap (no subquery) — the zero-overhead path.
|
|
450
|
+
*
|
|
451
|
+
* `undefined` for the short-circuit form, whose right operand is a `SubProgram` param
|
|
452
|
+
* rather than a value and whose body returns `MaybePromise` — not a {@link ScalarOpSpec}
|
|
453
|
+
* body. Consumers fall back: the emitter below builds that Instruction itself, and a
|
|
454
|
+
* fusion consumer must decline to fuse.
|
|
455
|
+
*/
|
|
456
|
+
export function buildLogicalOpSpec(plan) {
|
|
457
|
+
// Normalize operator to uppercase for case-insensitive matching
|
|
458
|
+
const operator = plan.expression.operator.toUpperCase();
|
|
459
|
+
if (usesShortCircuit(plan, operator))
|
|
460
|
+
return undefined;
|
|
461
|
+
const combineLogical = buildCombineLogical(operator, plan);
|
|
360
462
|
function run(_ctx, v1, v2) {
|
|
361
463
|
return combineLogical(v1, v2);
|
|
362
464
|
}
|
|
465
|
+
return {
|
|
466
|
+
operands: [plan.left, plan.right],
|
|
467
|
+
run,
|
|
468
|
+
note: `${plan.expression.operator}(logical)`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function emitShortCircuitLogicalOp(plan, ctx) {
|
|
472
|
+
const operator = plan.expression.operator.toUpperCase();
|
|
473
|
+
const combineLogical = buildCombineLogical(operator, plan);
|
|
363
474
|
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
364
|
-
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (operator === 'AND' && b1 === false)
|
|
385
|
-
return false;
|
|
386
|
-
if (operator === 'OR' && b1 === true)
|
|
387
|
-
return true;
|
|
388
|
-
// Otherwise fetch the deferred right and combine with the shared 3VL
|
|
389
|
-
// (combineLogical) — byte-identical to the eager path. Stay synchronous
|
|
390
|
-
// when the right sub-program resolves synchronously; only take the
|
|
391
|
-
// microtask hop on a genuinely async subquery (see docs/runtime.md
|
|
392
|
-
// "Avoid a per-row microtask hop on the synchronous fast path"). A
|
|
393
|
-
// left-decides row returned above, so it never pays an async tick.
|
|
394
|
-
const raw = rightFn(ctx);
|
|
395
|
-
return raw instanceof Promise
|
|
396
|
-
? raw.then(v2 => combineLogical(v1, v2))
|
|
397
|
-
: combineLogical(v1, raw);
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
params: [leftExpr, rightCall],
|
|
401
|
-
run: asRun(runShortCircuit),
|
|
402
|
-
note: `${plan.expression.operator}(logical short-circuit)`
|
|
403
|
-
};
|
|
475
|
+
const rightCall = emitCallFromPlan(plan.right, ctx);
|
|
476
|
+
// The left-operand value that decides the result on its own, resolved once at
|
|
477
|
+
// emit time: `false AND x` → false, `true OR x` → true. Also the result itself.
|
|
478
|
+
const decidingValue = operator === 'AND' ? false : true;
|
|
479
|
+
function runShortCircuit(ctx, v1, rightFn) {
|
|
480
|
+
// Left decides — the right operand is never fetched (`false AND x` → false;
|
|
481
|
+
// `true OR x` → true). Same SQL truthiness as the eager path.
|
|
482
|
+
const b1 = v1 === null ? null : isTruthy(v1);
|
|
483
|
+
if (b1 === decidingValue)
|
|
484
|
+
return decidingValue;
|
|
485
|
+
// Otherwise fetch the deferred right and combine with the shared 3VL
|
|
486
|
+
// (combineLogical) — byte-identical to the eager path. Stay synchronous
|
|
487
|
+
// when the right sub-program resolves synchronously; only take the
|
|
488
|
+
// microtask hop on a genuinely async subquery (see docs/runtime.md
|
|
489
|
+
// "Avoid a per-row microtask hop on the synchronous fast path"). A
|
|
490
|
+
// left-decides row returned above, so it never pays an async tick.
|
|
491
|
+
const raw = rightFn(ctx);
|
|
492
|
+
return raw instanceof Promise
|
|
493
|
+
? raw.then(v2 => combineLogical(v1, v2))
|
|
494
|
+
: combineLogical(v1, raw);
|
|
404
495
|
}
|
|
405
|
-
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
406
496
|
return {
|
|
407
|
-
params: [leftExpr,
|
|
408
|
-
run: asRun(
|
|
409
|
-
note: `${plan.expression.operator}(logical)`
|
|
497
|
+
params: [leftExpr, rightCall],
|
|
498
|
+
run: asRun(runShortCircuit),
|
|
499
|
+
note: `${plan.expression.operator}(logical short-circuit)`
|
|
410
500
|
};
|
|
411
501
|
}
|
|
412
502
|
/**
|
|
413
503
|
* If `node` is a literal-constant, non-NULL pattern, return the exact string the
|
|
414
|
-
* per-row path would derive from it (
|
|
504
|
+
* per-row path would derive from it ({@link valueToText}), otherwise undefined.
|
|
415
505
|
* A NULL literal, a not-yet-resolved Promise value, or any non-literal node
|
|
416
506
|
* falls through to the dynamic (memoized) per-row path so semantics are
|
|
417
507
|
* unchanged. Cast/collate-wrapped literals are intentionally NOT unwrapped —
|
|
@@ -423,14 +513,14 @@ function constLikePattern(node) {
|
|
|
423
513
|
const value = node.expression.value;
|
|
424
514
|
if (value === null || value === undefined || value instanceof Promise)
|
|
425
515
|
return undefined;
|
|
426
|
-
return
|
|
516
|
+
return valueToText(value);
|
|
427
517
|
}
|
|
428
|
-
export function
|
|
429
|
-
const leftExpr = emitPlanNode(plan.left, ctx);
|
|
518
|
+
export function buildLikeOpSpec(plan) {
|
|
430
519
|
// Fast path: the pattern operand is a literal constant. Compile the matcher
|
|
431
520
|
// once here at emit time and capture it in the closure, so no per-row compile
|
|
432
|
-
// or cache lookup happens at all. The literal operand is
|
|
433
|
-
//
|
|
521
|
+
// or cache lookup happens at all. The literal operand is NOT declared as an
|
|
522
|
+
// operand since its value is already baked into `matcher` — this spec's arity
|
|
523
|
+
// is one, while the plan node still has two children.
|
|
434
524
|
const constPattern = constLikePattern(plan.right);
|
|
435
525
|
if (constPattern !== undefined) {
|
|
436
526
|
const matcher = compileLikeMatcher(constPattern);
|
|
@@ -438,11 +528,11 @@ export function emitLikeOp(plan, ctx) {
|
|
|
438
528
|
// text LIKE <const>: NULL text → NULL, else run the pre-compiled matcher.
|
|
439
529
|
if (text === null)
|
|
440
530
|
return null;
|
|
441
|
-
return matcher(
|
|
531
|
+
return matcher(valueToText(text));
|
|
442
532
|
}
|
|
443
533
|
return {
|
|
444
|
-
|
|
445
|
-
run:
|
|
534
|
+
operands: [plan.left],
|
|
535
|
+
run: runConstPattern,
|
|
446
536
|
note: 'LIKE(like-const)'
|
|
447
537
|
};
|
|
448
538
|
}
|
|
@@ -452,15 +542,16 @@ export function emitLikeOp(plan, ctx) {
|
|
|
452
542
|
if (text === null || pattern === null) {
|
|
453
543
|
return null;
|
|
454
544
|
}
|
|
455
|
-
// Convert both operands to
|
|
456
|
-
|
|
457
|
-
|
|
545
|
+
// Convert both operands through the shared value-to-text rule and perform LIKE
|
|
546
|
+
// matching (memoized compile). Must agree with `constLikePattern` above, or the
|
|
547
|
+
// constant-pattern fast path starts answering differently from this one.
|
|
548
|
+
const textStr = valueToText(text);
|
|
549
|
+
const patternStr = valueToText(pattern);
|
|
458
550
|
return simpleLike(patternStr, textStr);
|
|
459
551
|
}
|
|
460
|
-
const rightExpr = emitPlanNode(plan.right, ctx);
|
|
461
552
|
return {
|
|
462
|
-
|
|
463
|
-
run
|
|
553
|
+
operands: [plan.left, plan.right],
|
|
554
|
+
run,
|
|
464
555
|
note: 'LIKE(like)'
|
|
465
556
|
};
|
|
466
557
|
}
|