@quereus/quereus 4.4.0 → 4.4.1
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 +5 -0
- package/dist/src/common/types.d.ts +9 -0
- package/dist/src/common/types.d.ts.map +1 -1
- package/dist/src/common/types.js.map +1 -1
- package/dist/src/core/database-events.d.ts +49 -0
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +148 -0
- package/dist/src/core/database-events.js.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts +2 -1
- package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views-plan-builders.js +26 -5
- package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
- package/dist/src/core/database-materialized-views-plans.d.ts +6 -3
- package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.d.ts +3 -2
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +26 -5
- package/dist/src/core/database-materialized-views.js.map +1 -1
- package/dist/src/core/database.d.ts +5 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +21 -1
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/internal-statement-cache.d.ts +18 -0
- package/dist/src/core/internal-statement-cache.d.ts.map +1 -0
- package/dist/src/core/internal-statement-cache.js +211 -0
- package/dist/src/core/internal-statement-cache.js.map +1 -0
- package/dist/src/func/builtins/aggregate.d.ts +13 -12
- package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
- package/dist/src/func/builtins/aggregate.js +45 -67
- package/dist/src/func/builtins/aggregate.js.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
- package/dist/src/func/builtins/builtin-window-functions.js +63 -48
- package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
- package/dist/src/func/registration.d.ts +14 -1
- package/dist/src/func/registration.d.ts.map +1 -1
- package/dist/src/func/registration.js +25 -0
- package/dist/src/func/registration.js.map +1 -1
- package/dist/src/index.d.ts +8 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/parser.d.ts +6 -0
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +45 -34
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.d.ts +2 -0
- package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
- package/dist/src/planner/analysis/comparison-collation.js +1 -1
- package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
- package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
- package/dist/src/planner/analysis/expression-fingerprint.js +29 -0
- package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +6 -0
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +11 -2
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
- package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
- package/dist/src/planner/analysis/sat-checker.js +43 -4
- package/dist/src/planner/analysis/sat-checker.js.map +1 -1
- package/dist/src/planner/building/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +10 -3
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/expression.d.ts.map +1 -1
- package/dist/src/planner/building/expression.js +87 -6
- package/dist/src/planner/building/expression.js.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts +44 -1
- package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
- package/dist/src/planner/building/foreign-key-builder.js +54 -0
- package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +9 -12
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +13 -4
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/framework/pass.js +3 -3
- package/dist/src/planner/nodes/dml-executor-node.d.ts +0 -9
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +26 -3
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +5 -12
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +43 -18
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +23 -4
- package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +10 -8
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +10 -8
- package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +8 -1
- package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +4 -0
- package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
- package/dist/src/runtime/deferred-constraint-queue.js +10 -0
- package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
- package/dist/src/runtime/emit/aggregate-setup.d.ts +49 -0
- package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -0
- package/dist/src/runtime/emit/aggregate-setup.js +99 -0
- package/dist/src/runtime/emit/aggregate-setup.js.map +1 -0
- package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/aggregate.js +4 -69
- 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 +348 -165
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/asof-scan.js +6 -0
- package/dist/src/runtime/emit/asof-scan.js.map +1 -1
- package/dist/src/runtime/emit/between.d.ts.map +1 -1
- package/dist/src/runtime/emit/between.js +32 -3
- package/dist/src/runtime/emit/between.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +22 -2
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/bloom-join.js +24 -3
- package/dist/src/runtime/emit/bloom-join.js.map +1 -1
- package/dist/src/runtime/emit/cast.d.ts.map +1 -1
- package/dist/src/runtime/emit/cast.js +10 -23
- package/dist/src/runtime/emit/cast.js.map +1 -1
- package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
- package/dist/src/runtime/emit/constraint-check.js +41 -44
- package/dist/src/runtime/emit/constraint-check.js.map +1 -1
- package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/create-view.js +6 -0
- package/dist/src/runtime/emit/create-view.js.map +1 -1
- package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
- package/dist/src/runtime/emit/distinct.js +6 -3
- package/dist/src/runtime/emit/distinct.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +182 -81
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
- package/dist/src/runtime/emit/hash-aggregate.js +16 -65
- package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
- package/dist/src/runtime/emit/insert.d.ts.map +1 -1
- package/dist/src/runtime/emit/insert.js +18 -7
- package/dist/src/runtime/emit/insert.js.map +1 -1
- package/dist/src/runtime/emit/join.js +5 -0
- package/dist/src/runtime/emit/join.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +14 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +33 -3
- 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 +47 -6
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/merge-join.js +18 -8
- package/dist/src/runtime/emit/merge-join.js.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.d.ts +8 -0
- package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-object-tags.js +8 -0
- package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
- package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
- package/dist/src/runtime/emit/set-operation.js +7 -4
- package/dist/src/runtime/emit/set-operation.js.map +1 -1
- package/dist/src/runtime/emit/sort.d.ts.map +1 -1
- package/dist/src/runtime/emit/sort.js +5 -2
- package/dist/src/runtime/emit/sort.js.map +1 -1
- package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
- package/dist/src/runtime/emit/subquery.js +113 -13
- package/dist/src/runtime/emit/subquery.js.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts +7 -0
- package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
- package/dist/src/runtime/emit/temporal-arithmetic.js +15 -7
- package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
- package/dist/src/runtime/emit/update.d.ts.map +1 -1
- package/dist/src/runtime/emit/update.js +44 -3
- package/dist/src/runtime/emit/update.js.map +1 -1
- package/dist/src/runtime/emit/window.d.ts.map +1 -1
- package/dist/src/runtime/emit/window.js +72 -55
- package/dist/src/runtime/emit/window.js.map +1 -1
- package/dist/src/runtime/foreign-key-actions.d.ts +36 -1
- package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
- package/dist/src/runtime/foreign-key-actions.js +238 -116
- package/dist/src/runtime/foreign-key-actions.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +6 -1
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +7 -1
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/types.d.ts +18 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/catalog-persistability.d.ts +58 -0
- package/dist/src/schema/catalog-persistability.d.ts.map +1 -0
- package/dist/src/schema/catalog-persistability.js +87 -0
- package/dist/src/schema/catalog-persistability.js.map +1 -0
- package/dist/src/schema/catalog.d.ts +17 -1
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +25 -3
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/constraint-builder.d.ts +20 -22
- package/dist/src/schema/constraint-builder.d.ts.map +1 -1
- package/dist/src/schema/constraint-builder.js +43 -36
- package/dist/src/schema/constraint-builder.js.map +1 -1
- package/dist/src/schema/ddl-generator.d.ts +23 -0
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +27 -0
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/function.d.ts +30 -1
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/manager.d.ts +28 -0
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +82 -14
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +51 -12
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +31 -0
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +37 -1
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/schema/window-function.d.ts +30 -10
- package/dist/src/schema/window-function.d.ts.map +1 -1
- package/dist/src/schema/window-function.js +20 -16
- package/dist/src/schema/window-function.js.map +1 -1
- package/dist/src/types/cast-semantics.d.ts +43 -0
- package/dist/src/types/cast-semantics.d.ts.map +1 -0
- package/dist/src/types/cast-semantics.js +59 -0
- package/dist/src/types/cast-semantics.js.map +1 -0
- 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/json-type.d.ts.map +1 -1
- package/dist/src/types/json-type.js +31 -10
- package/dist/src/types/json-type.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +22 -0
- package/dist/src/types/logical-type.d.ts.map +1 -1
- package/dist/src/types/logical-type.js.map +1 -1
- package/dist/src/types/temporal-types.d.ts.map +1 -1
- package/dist/src/types/temporal-types.js +33 -11
- package/dist/src/types/temporal-types.js.map +1 -1
- package/dist/src/types/validation.d.ts +39 -1
- package/dist/src/types/validation.d.ts.map +1 -1
- package/dist/src/types/validation.js +60 -0
- package/dist/src/types/validation.js.map +1 -1
- package/dist/src/util/ast-spine-clone.d.ts +23 -0
- package/dist/src/util/ast-spine-clone.d.ts.map +1 -0
- package/dist/src/util/ast-spine-clone.js +41 -0
- package/dist/src/util/ast-spine-clone.js.map +1 -0
- package/dist/src/util/coercion.d.ts +13 -0
- package/dist/src/util/coercion.d.ts.map +1 -1
- package/dist/src/util/coercion.js +10 -1
- package/dist/src/util/coercion.js.map +1 -1
- package/dist/src/util/comparison.d.ts +89 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +156 -27
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/vtab/manifest.d.ts +7 -0
- package/dist/src/vtab/manifest.d.ts.map +1 -1
- package/dist/src/vtab/memory/connection.d.ts +14 -1
- package/dist/src/vtab/memory/connection.d.ts.map +1 -1
- package/dist/src/vtab/memory/connection.js +20 -2
- package/dist/src/vtab/memory/connection.js.map +1 -1
- package/dist/src/vtab/memory/layer/base.d.ts +6 -1
- package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/base.js +14 -5
- package/dist/src/vtab/memory/layer/base.js.map +1 -1
- package/dist/src/vtab/memory/layer/connection.d.ts +17 -0
- package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/connection.js +26 -0
- package/dist/src/vtab/memory/layer/connection.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +96 -13
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +595 -119
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.d.ts +41 -20
- package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/plan-filter.js +57 -27
- package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
- package/dist/src/vtab/memory/layer/row-convert.d.ts +21 -0
- package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -0
- package/dist/src/vtab/memory/layer/row-convert.js +30 -0
- package/dist/src/vtab/memory/layer/row-convert.js.map +1 -0
- package/dist/src/vtab/memory/layer/scan-layer.d.ts +6 -3
- package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/scan-layer.js +48 -34
- package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.d.ts +127 -13
- package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/transaction.js +214 -65
- package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +9 -2
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +3 -2
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/memory/types.d.ts +27 -1
- package/dist/src/vtab/memory/types.d.ts.map +1 -1
- package/dist/src/vtab/memory/types.js +16 -1
- package/dist/src/vtab/memory/types.js.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.d.ts +9 -0
- package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
- package/dist/src/vtab/memory/utils/primary-key.js +11 -0
- package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
- package/dist/src/vtab/module.d.ts +63 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/dist/src/vtab/table.d.ts +24 -6
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +3 -3
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +0 -26
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +0 -1
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +0 -66
- package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +0 -1
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
import type { Database } from '../core/database.js';
|
|
2
2
|
import type { TableSchema } from '../schema/table.js';
|
|
3
|
-
import type { Row } from '../common/types.js';
|
|
3
|
+
import type { Row, SqlValue } from '../common/types.js';
|
|
4
|
+
import type { BatchableRestrictFk } from '../planner/building/foreign-key-builder.js';
|
|
5
|
+
/** Per-FK accumulation state for one statement execution. */
|
|
6
|
+
export interface ParentRestrictBatchEntry extends BatchableRestrictFk {
|
|
7
|
+
/** Deduplicated OLD referenced-key tuples, keyed by an injective serialization. */
|
|
8
|
+
readonly keys: Map<string, SqlValue[]>;
|
|
9
|
+
}
|
|
10
|
+
export type ParentRestrictBatch = ParentRestrictBatchEntry[];
|
|
11
|
+
/**
|
|
12
|
+
* Allocate the per-statement-execution accumulation state for the batchable
|
|
13
|
+
* inbound RESTRICT FKs. Must be called once per execution (never cached on the
|
|
14
|
+
* emit closure) so a re-run prepared statement starts empty.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createParentRestrictBatch(fks: readonly BatchableRestrictFk[]): ParentRestrictBatch;
|
|
17
|
+
/**
|
|
18
|
+
* Accumulate one mutated parent row's OLD referenced-key tuples into the batch
|
|
19
|
+
* — the batched replacement for the per-row RESTRICT probes, applying the same
|
|
20
|
+
* skip rules: MATCH SIMPLE (a tuple containing NULL is unreferenceable) and,
|
|
21
|
+
* for UPDATE, the referenced-column-change short-circuit.
|
|
22
|
+
*/
|
|
23
|
+
export declare function accumulateParentRestrictKeys(batch: ParentRestrictBatch, operation: 'delete' | 'update', oldRow: Row, newRow?: Row): void;
|
|
24
|
+
/**
|
|
25
|
+
* End-of-statement flush: one chunked probe per inbound RESTRICT FK over the
|
|
26
|
+
* accumulated key tuples. Fired by `runWithStatementSavepoints` after the row
|
|
27
|
+
* loop and BEFORE the deferred-maintenance flush and the statement savepoint
|
|
28
|
+
* release, so a hit throws the existing RESTRICT error shape and rolls the
|
|
29
|
+
* whole statement back. Re-checks the `foreign_keys` pragma and the
|
|
30
|
+
* trust-the-origin suppression at flush time, mirroring the per-row pre-check.
|
|
31
|
+
*
|
|
32
|
+
* The probe selects the FK columns (not `select 1`) so the violating key stays
|
|
33
|
+
* available for a future richer message; the message itself is unchanged and
|
|
34
|
+
* matcher-compatible (`/constraint|foreign|fk/i`). Comparison rides plain SQL
|
|
35
|
+
* `=` / `IN` against the child column — identical collation semantics to the
|
|
36
|
+
* per-row `NOT EXISTS` by construction.
|
|
37
|
+
*/
|
|
38
|
+
export declare function flushParentRestrictBatch(db: Database, parentTable: TableSchema, operation: 'delete' | 'update', batch: ParentRestrictBatch): Promise<void>;
|
|
4
39
|
/**
|
|
5
40
|
* Executes cascading foreign key actions when a parent row is deleted or updated.
|
|
6
41
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foreign-key-actions.d.ts","sourceRoot":"","sources":["../../../src/runtime/foreign-key-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAA8B,MAAM,oBAAoB,CAAC;AAGlF,OAAO,KAAK,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"foreign-key-actions.d.ts","sourceRoot":"","sources":["../../../src/runtime/foreign-key-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,154 @@ import { sqlValueIdentical } from '../util/comparison.js';
|
|
|
7
7
|
import { resolveSlotBasisSource } from '../schema/lens-prover.js';
|
|
8
8
|
import { findLogicalParentFkRefs, logicalToBasisColumnMap, matchingBasisFksForLensRef, basisFksOverriddenByDivergentLensFk, } from '../schema/lens-fk-discovery.js';
|
|
9
9
|
const log = createLogger('runtime:fk-actions');
|
|
10
|
+
/** Keys probed per child-table query — a handful of compiles per statement, not one per row. */
|
|
11
|
+
const RESTRICT_BATCH_CHUNK = 500;
|
|
12
|
+
/**
|
|
13
|
+
* Allocate the per-statement-execution accumulation state for the batchable
|
|
14
|
+
* inbound RESTRICT FKs. Must be called once per execution (never cached on the
|
|
15
|
+
* emit closure) so a re-run prepared statement starts empty.
|
|
16
|
+
*/
|
|
17
|
+
export function createParentRestrictBatch(fks) {
|
|
18
|
+
return fks.map(fk => ({ ...fk, keys: new Map() }));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Injective serialization of a referenced-key tuple for dedup. Each segment is
|
|
22
|
+
* type-tagged and (for variable-length payloads) length-prefixed, so two
|
|
23
|
+
* distinct tuples can never collide — a collision would silently drop a probe.
|
|
24
|
+
*/
|
|
25
|
+
function serializeKeyTuple(values) {
|
|
26
|
+
let out = '';
|
|
27
|
+
for (const v of values) {
|
|
28
|
+
if (typeof v === 'number')
|
|
29
|
+
out += `n${v};`;
|
|
30
|
+
else if (typeof v === 'bigint')
|
|
31
|
+
out += `i${v};`;
|
|
32
|
+
else if (typeof v === 'string')
|
|
33
|
+
out += `s${v.length}:${v};`;
|
|
34
|
+
else if (v instanceof Uint8Array)
|
|
35
|
+
out += `b${v.length}:${Array.from(v).join(',')};`;
|
|
36
|
+
else
|
|
37
|
+
out += `x${String(v)};`;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The shared UPDATE short-circuit behind every parent-side RESTRICT / cascade site:
|
|
43
|
+
* true when at least one of `colIndices` would actually STORE a different value.
|
|
44
|
+
*
|
|
45
|
+
* Both rows arrive in declared (stored) form: `oldRow` comes from the source scan,
|
|
46
|
+
* and `newRow` was converted to declared column types by the DML emitters before
|
|
47
|
+
* the executor's pre-vtab checks run (ticket `json-coerce-once-at-dml-source` —
|
|
48
|
+
* this used to re-convert `newRow` per cell because it arrived raw). So the test
|
|
49
|
+
* is plain value identity.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately BINARY identity rather than the column's collation: a NOCASE parent
|
|
52
|
+
* column still stores `'A'` and `'a'` as distinct values, and the child's own match
|
|
53
|
+
* runs under the CHILD column's collation, which may well be BINARY. A spurious
|
|
54
|
+
* "changed" costs one redundant RESTRICT probe; a spurious "unchanged" would SKIP
|
|
55
|
+
* enforcement entirely — so identity is the safe direction.
|
|
56
|
+
*
|
|
57
|
+
* NOTE: every caller today supplies declared-form rows (the DML executor's converted
|
|
58
|
+
* NEW row; materialized-view maintenance passes the changes `applyMaintenance`
|
|
59
|
+
* reports, i.e. what the backing actually stored). A future caller that hands in a
|
|
60
|
+
* RAW proposed row would see spurious "changed" on any cell conversion normalizes —
|
|
61
|
+
* harmless on the cascade path, but a spurious RESTRICT error on the assert path. If
|
|
62
|
+
* one appears, convert at that caller rather than re-introducing a per-cell coercion
|
|
63
|
+
* here.
|
|
64
|
+
*/
|
|
65
|
+
function anyReferencedColumnChanged(colIndices, oldRow, newRow) {
|
|
66
|
+
for (const idx of colIndices) {
|
|
67
|
+
if (!sqlValueIdentical(oldRow[idx], newRow[idx]))
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Accumulate one mutated parent row's OLD referenced-key tuples into the batch
|
|
74
|
+
* — the batched replacement for the per-row RESTRICT probes, applying the same
|
|
75
|
+
* skip rules: MATCH SIMPLE (a tuple containing NULL is unreferenceable) and,
|
|
76
|
+
* for UPDATE, the referenced-column-change short-circuit.
|
|
77
|
+
*/
|
|
78
|
+
export function accumulateParentRestrictKeys(batch, operation, oldRow, newRow) {
|
|
79
|
+
for (const entry of batch) {
|
|
80
|
+
// UPDATE: only rows that actually re-key a referenced column contribute.
|
|
81
|
+
if (operation === 'update' && newRow !== undefined) {
|
|
82
|
+
if (!anyReferencedColumnChanged(entry.parentColIndices, oldRow, newRow))
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// MATCH SIMPLE: NULL parent values cannot be referenced.
|
|
86
|
+
const values = entry.parentColIndices.map(idx => oldRow[idx]);
|
|
87
|
+
if (values.some(v => v === null || v === undefined))
|
|
88
|
+
continue;
|
|
89
|
+
const key = serializeKeyTuple(values);
|
|
90
|
+
if (!entry.keys.has(key))
|
|
91
|
+
entry.keys.set(key, values);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* End-of-statement flush: one chunked probe per inbound RESTRICT FK over the
|
|
96
|
+
* accumulated key tuples. Fired by `runWithStatementSavepoints` after the row
|
|
97
|
+
* loop and BEFORE the deferred-maintenance flush and the statement savepoint
|
|
98
|
+
* release, so a hit throws the existing RESTRICT error shape and rolls the
|
|
99
|
+
* whole statement back. Re-checks the `foreign_keys` pragma and the
|
|
100
|
+
* trust-the-origin suppression at flush time, mirroring the per-row pre-check.
|
|
101
|
+
*
|
|
102
|
+
* The probe selects the FK columns (not `select 1`) so the violating key stays
|
|
103
|
+
* available for a future richer message; the message itself is unchanged and
|
|
104
|
+
* matcher-compatible (`/constraint|foreign|fk/i`). Comparison rides plain SQL
|
|
105
|
+
* `=` / `IN` against the child column — identical collation semantics to the
|
|
106
|
+
* per-row `NOT EXISTS` by construction.
|
|
107
|
+
*/
|
|
108
|
+
export async function flushParentRestrictBatch(db, parentTable, operation, batch) {
|
|
109
|
+
if (db._isFkRestrictSuppressed())
|
|
110
|
+
return;
|
|
111
|
+
if (!db.options.getBooleanOption('foreign_keys'))
|
|
112
|
+
return;
|
|
113
|
+
for (const entry of batch) {
|
|
114
|
+
if (entry.keys.size === 0)
|
|
115
|
+
continue;
|
|
116
|
+
const { childTable, fk } = entry;
|
|
117
|
+
const childColQuoted = fk.columns.map(idx => quoteIdentifier(childTable.columns[idx].name));
|
|
118
|
+
const schemaPrefix = childTable.schemaName.toLowerCase() !== 'main'
|
|
119
|
+
? `${quoteIdentifier(childTable.schemaName)}.`
|
|
120
|
+
: '';
|
|
121
|
+
const from = `${schemaPrefix}${quoteIdentifier(childTable.name)}`;
|
|
122
|
+
const tuples = [...entry.keys.values()];
|
|
123
|
+
for (let i = 0; i < tuples.length; i += RESTRICT_BATCH_CHUNK) {
|
|
124
|
+
const chunk = tuples.slice(i, i + RESTRICT_BATCH_CHUNK);
|
|
125
|
+
let whereClause;
|
|
126
|
+
let params;
|
|
127
|
+
if (childColQuoted.length === 1) {
|
|
128
|
+
whereClause = `${childColQuoted[0]} in (${chunk.map(() => '?').join(', ')})`;
|
|
129
|
+
params = chunk.map(t => t[0]);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
whereClause = chunk
|
|
133
|
+
.map(() => `(${childColQuoted.map(c => `${c} = ?`).join(' and ')})`)
|
|
134
|
+
.join(' or ');
|
|
135
|
+
params = chunk.flat();
|
|
136
|
+
}
|
|
137
|
+
const sql = `select ${childColQuoted.join(', ')} from ${from} where ${whereClause} limit 1`;
|
|
138
|
+
log('RESTRICT batch check (%s): child=%s keys=%d', operation, childTable.name, chunk.length);
|
|
139
|
+
const stmt = db.prepare(sql);
|
|
140
|
+
try {
|
|
141
|
+
stmt.bindAll(params);
|
|
142
|
+
let referenced = false;
|
|
143
|
+
for await (const _row of stmt._iterateRowsRaw()) {
|
|
144
|
+
referenced = true;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
if (referenced) {
|
|
148
|
+
const opName = operation === 'delete' ? 'DELETE' : 'UPDATE';
|
|
149
|
+
throw new QuereusError(`FOREIGN KEY constraint failed: ${opName} on '${parentTable.name}' violates RESTRICT from '${childTable.name}'`, StatusCode.CONSTRAINT);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await stmt.finalize();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
10
158
|
/**
|
|
11
159
|
* Executes cascading foreign key actions when a parent row is deleted or updated.
|
|
12
160
|
*
|
|
@@ -56,6 +204,17 @@ export async function executeForeignKeyActions(db, parentTable, operation, oldRo
|
|
|
56
204
|
const parentColIndices = resolveReferencedColumns(fk, parentTable);
|
|
57
205
|
if (parentColIndices.length !== fk.columns.length)
|
|
58
206
|
continue;
|
|
207
|
+
// UPDATE: an ON UPDATE action propagates a change to the parent's REFERENCED
|
|
208
|
+
// columns, so an update that leaves them alone must not touch the child at
|
|
209
|
+
// all. Without this the child DML re-issues unconditionally: SET DEFAULT /
|
|
210
|
+
// SET NULL silently re-point or null the child, and CASCADE rewrites the
|
|
211
|
+
// child to the value it already holds (a real storage write plus a
|
|
212
|
+
// data-change event with an empty changed-column list). Same short-circuit
|
|
213
|
+
// the RESTRICT sites and the lens walker already apply.
|
|
214
|
+
if (operation === 'update' && newRow !== undefined) {
|
|
215
|
+
if (!anyReferencedColumnChanged(parentColIndices, oldRow, newRow))
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
59
218
|
// Get old parent values for the referenced columns
|
|
60
219
|
const oldParentValues = parentColIndices.map(idx => oldRow[idx]);
|
|
61
220
|
// Skip if any old value is NULL (NULLs don't participate in FK matching)
|
|
@@ -186,14 +345,7 @@ export async function assertTransitiveRestrictsForParentMutation(db, parentTable
|
|
|
186
345
|
// UPDATE-only short-circuit: skip if no referenced parent column changed.
|
|
187
346
|
let newParentValues;
|
|
188
347
|
if (operation === 'update' && newRow !== undefined) {
|
|
189
|
-
|
|
190
|
-
for (const idx of parentColIndices) {
|
|
191
|
-
if (!sqlValueIdentical(oldRow[idx], newRow[idx])) {
|
|
192
|
-
anyChanged = true;
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (!anyChanged)
|
|
348
|
+
if (!anyReferencedColumnChanged(parentColIndices, oldRow, newRow))
|
|
197
349
|
continue;
|
|
198
350
|
newParentValues = parentColIndices.map(idx => newRow[idx]);
|
|
199
351
|
}
|
|
@@ -205,62 +357,61 @@ export async function assertTransitiveRestrictsForParentMutation(db, parentTable
|
|
|
205
357
|
: '';
|
|
206
358
|
const sql = `select * from ${schemaPrefix}${quoteIdentifier(childTable.name)} where ${whereClause}`;
|
|
207
359
|
log('TRANSITIVE pre-walk: %s with params %o', sql, oldParentValues);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else if (action === 'setNull') {
|
|
227
|
-
childOp = 'update';
|
|
228
|
-
const next = [...childOldRow];
|
|
229
|
-
for (let i = 0; i < fk.columns.length; i++) {
|
|
230
|
-
next[fk.columns[i]] = null;
|
|
231
|
-
}
|
|
232
|
-
childNewRow = next;
|
|
233
|
-
}
|
|
234
|
-
else if (action === 'setDefault') {
|
|
235
|
-
// SET DEFAULT recursion: pass the child OLD row as both
|
|
236
|
-
// old and new. The recursion's column-change short-circuit
|
|
237
|
-
// will treat this as "no FK column moved" and the per-target
|
|
238
|
-
// cascade SQL (executeSingleFKAction) still fires its own
|
|
239
|
-
// RESTRICT enforcement for non-rowid-chained backends. This
|
|
240
|
-
// matches the coverage gap SET DEFAULT already has in
|
|
241
|
-
// rowid-chained backends — no regression beyond status quo.
|
|
242
|
-
childOp = 'update';
|
|
243
|
-
childNewRow = childOldRow;
|
|
360
|
+
// Cached internal statement leased for the whole scan (compiled once per
|
|
361
|
+
// shape, not re-prepared per parent row). The recursion inside this loop
|
|
362
|
+
// can re-enter with the SAME SQL text (self-referential / diamond FK graph)
|
|
363
|
+
// while this statement is still iterating — that re-entry hits the cache's
|
|
364
|
+
// busy-guard and gets its own fresh one-shot statement, never this live
|
|
365
|
+
// cursor. See InternalStatementCache.iterate.
|
|
366
|
+
for await (const childOldRow of db._internalStatementCache.iterate(sql, oldParentValues)) {
|
|
367
|
+
let childNewRow;
|
|
368
|
+
let childOp;
|
|
369
|
+
if (action === 'cascade' && operation === 'delete') {
|
|
370
|
+
childOp = 'delete';
|
|
371
|
+
childNewRow = undefined;
|
|
372
|
+
}
|
|
373
|
+
else if (action === 'cascade' && operation === 'update' && newParentValues) {
|
|
374
|
+
childOp = 'update';
|
|
375
|
+
const next = [...childOldRow];
|
|
376
|
+
for (let i = 0; i < fk.columns.length; i++) {
|
|
377
|
+
next[fk.columns[i]] = newParentValues[i];
|
|
244
378
|
}
|
|
245
|
-
|
|
246
|
-
|
|
379
|
+
childNewRow = next;
|
|
380
|
+
}
|
|
381
|
+
else if (action === 'setNull') {
|
|
382
|
+
childOp = 'update';
|
|
383
|
+
const next = [...childOldRow];
|
|
384
|
+
for (let i = 0; i < fk.columns.length; i++) {
|
|
385
|
+
next[fk.columns[i]] = null;
|
|
247
386
|
}
|
|
248
|
-
|
|
249
|
-
// transitive closure of *this* write, so when the top-level write is
|
|
250
|
-
// lens-routed every level inherits the logical FK semantics. This is
|
|
251
|
-
// load-bearing for a logical RESTRICT sitting below an *agreeing*
|
|
252
|
-
// basis cascade (basis + logical both cascade): at runtime that basis
|
|
253
|
-
// cascade executes as a basis-direct write — which does NOT fire the
|
|
254
|
-
// deeper lens RESTRICT — and the agreeing lens cascade is elided, so it
|
|
255
|
-
// never re-enters through the child view either. The ONLY place that
|
|
256
|
-
// deeper RESTRICT is caught is this pre-walk recursing with the lens flag
|
|
257
|
-
// still set. For a basis-direct top-level write `lensRouted` is already
|
|
258
|
-
// false, so the recursion correctly stays basis-only throughout.
|
|
259
|
-
await assertTransitiveRestrictsForParentMutation(db, childTable, childOp, childOldRow, childNewRow, lensRouted, visitedSet);
|
|
387
|
+
childNewRow = next;
|
|
260
388
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
389
|
+
else if (action === 'setDefault') {
|
|
390
|
+
// SET DEFAULT recursion: pass the child OLD row as both
|
|
391
|
+
// old and new. The recursion's column-change short-circuit
|
|
392
|
+
// will treat this as "no FK column moved" and the per-target
|
|
393
|
+
// cascade SQL (executeSingleFKAction) still fires its own
|
|
394
|
+
// RESTRICT enforcement for non-rowid-chained backends. This
|
|
395
|
+
// matches the coverage gap SET DEFAULT already has in
|
|
396
|
+
// rowid-chained backends — no regression beyond status quo.
|
|
397
|
+
childOp = 'update';
|
|
398
|
+
childNewRow = childOldRow;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
// Recurse carrying the SAME `lensRouted`: the nested levels are the
|
|
404
|
+
// transitive closure of *this* write, so when the top-level write is
|
|
405
|
+
// lens-routed every level inherits the logical FK semantics. This is
|
|
406
|
+
// load-bearing for a logical RESTRICT sitting below an *agreeing*
|
|
407
|
+
// basis cascade (basis + logical both cascade): at runtime that basis
|
|
408
|
+
// cascade executes as a basis-direct write — which does NOT fire the
|
|
409
|
+
// deeper lens RESTRICT — and the agreeing lens cascade is elided, so it
|
|
410
|
+
// never re-enters through the child view either. The ONLY place that
|
|
411
|
+
// deeper RESTRICT is caught is this pre-walk recursing with the lens flag
|
|
412
|
+
// still set. For a basis-direct top-level write `lensRouted` is already
|
|
413
|
+
// false, so the recursion correctly stays basis-only throughout.
|
|
414
|
+
await assertTransitiveRestrictsForParentMutation(db, childTable, childOp, childOldRow, childNewRow, lensRouted, visitedSet);
|
|
264
415
|
}
|
|
265
416
|
}
|
|
266
417
|
}
|
|
@@ -317,14 +468,7 @@ export async function assertNoRestrictedChildrenForParentMutation(db, parentTabl
|
|
|
317
468
|
continue;
|
|
318
469
|
// UPDATE: only enforce when at least one referenced parent column changed.
|
|
319
470
|
if (operation === 'update' && newRow !== undefined) {
|
|
320
|
-
|
|
321
|
-
for (const idx of parentColIndices) {
|
|
322
|
-
if (!sqlValueIdentical(oldRow[idx], newRow[idx])) {
|
|
323
|
-
anyChanged = true;
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (!anyChanged)
|
|
471
|
+
if (!anyReferencedColumnChanged(parentColIndices, oldRow, newRow))
|
|
328
472
|
continue;
|
|
329
473
|
}
|
|
330
474
|
// MATCH SIMPLE: NULL parent values cannot be referenced.
|
|
@@ -338,21 +482,11 @@ export async function assertNoRestrictedChildrenForParentMutation(db, parentTabl
|
|
|
338
482
|
: '';
|
|
339
483
|
const sql = `select 1 from ${schemaPrefix}${quoteIdentifier(childTable.name)} where ${whereClause} limit 1`;
|
|
340
484
|
log('RESTRICT check (%s): %s with params %o', operation, sql, oldParentValues);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
referenced = true;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
if (referenced) {
|
|
350
|
-
const opName = operation === 'delete' ? 'DELETE' : 'UPDATE';
|
|
351
|
-
throw new QuereusError(`FOREIGN KEY constraint failed: ${opName} on '${parentTable.name}' violates RESTRICT from '${childTable.name}'`, StatusCode.CONSTRAINT);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
finally {
|
|
355
|
-
await stmt.finalize();
|
|
485
|
+
// Cached internal statement (compiled once per shape) rather than a fresh
|
|
486
|
+
// per-row prepare/finalize — see InternalStatementCache.
|
|
487
|
+
if (await db._internalStatementCache.probe(sql, oldParentValues)) {
|
|
488
|
+
const opName = operation === 'delete' ? 'DELETE' : 'UPDATE';
|
|
489
|
+
throw new QuereusError(`FOREIGN KEY constraint failed: ${opName} on '${parentTable.name}' violates RESTRICT from '${childTable.name}'`, StatusCode.CONSTRAINT);
|
|
356
490
|
}
|
|
357
491
|
}
|
|
358
492
|
}
|
|
@@ -390,11 +524,16 @@ async function executeSingleFKAction(db, childTable, fk, action, parentTable, pa
|
|
|
390
524
|
const qualifiedChildTable = `"${childTable.schemaName}"."${childTable.name}"`;
|
|
391
525
|
switch (action) {
|
|
392
526
|
case 'cascade': {
|
|
527
|
+
// NOTE: this branches purely on `newRow === undefined` to tell cascade DELETE
|
|
528
|
+
// from cascade UPDATE. Every current caller passes operation === 'update' with
|
|
529
|
+
// a defined `newRow` (see the three UPDATE sites in emit/dml-executor.ts) — if a
|
|
530
|
+
// future caller ever invoked executeForeignKeyActions with operation: 'update'
|
|
531
|
+
// and no newRow, this would silently issue a child DELETE instead.
|
|
393
532
|
if (newRow === undefined) {
|
|
394
533
|
// CASCADE DELETE: delete matching child rows
|
|
395
534
|
const sql = `DELETE FROM ${qualifiedChildTable} WHERE ${whereClause}`;
|
|
396
535
|
log('CASCADE DELETE: %s with params %o', sql, oldParentValues);
|
|
397
|
-
await withFkCascadeReentry(db, () => db.
|
|
536
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
398
537
|
}
|
|
399
538
|
else {
|
|
400
539
|
// CASCADE UPDATE: update child FK columns to new parent values
|
|
@@ -408,7 +547,7 @@ async function executeSingleFKAction(db, childTable, fk, action, parentTable, pa
|
|
|
408
547
|
const sql = `UPDATE ${qualifiedChildTable} SET ${setClauses} WHERE ${whereParamsClause}`;
|
|
409
548
|
const params = [...newParentValues, ...oldParentValues];
|
|
410
549
|
log('CASCADE UPDATE: %s with params %o', sql, params);
|
|
411
|
-
await withFkCascadeReentry(db, () => db.
|
|
550
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, params));
|
|
412
551
|
}
|
|
413
552
|
break;
|
|
414
553
|
}
|
|
@@ -416,7 +555,7 @@ async function executeSingleFKAction(db, childTable, fk, action, parentTable, pa
|
|
|
416
555
|
const setClauses = childColNames.map(name => `"${name}" = NULL`).join(', ');
|
|
417
556
|
const sql = `UPDATE ${qualifiedChildTable} SET ${setClauses} WHERE ${whereClause}`;
|
|
418
557
|
log('SET NULL: %s with params %o', sql, oldParentValues);
|
|
419
|
-
await withFkCascadeReentry(db, () => db.
|
|
558
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
420
559
|
break;
|
|
421
560
|
}
|
|
422
561
|
case 'setDefault': {
|
|
@@ -431,7 +570,7 @@ async function executeSingleFKAction(db, childTable, fk, action, parentTable, pa
|
|
|
431
570
|
}).join(', ');
|
|
432
571
|
const sql = `UPDATE ${qualifiedChildTable} SET ${setClauses} WHERE ${whereClause}`;
|
|
433
572
|
log('SET DEFAULT: %s with params %o', sql, oldParentValues);
|
|
434
|
-
await withFkCascadeReentry(db, () => db.
|
|
573
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
435
574
|
break;
|
|
436
575
|
}
|
|
437
576
|
}
|
|
@@ -503,7 +642,7 @@ export async function executeLensForeignKeyActions(db, basisParentTable, operati
|
|
|
503
642
|
* (⇒ skip this ref) when:
|
|
504
643
|
* - a referenced column has no plain basis projection (cannot read its basis value);
|
|
505
644
|
* - MATCH SIMPLE: any OLD referenced value is NULL (participates in no FK match); or
|
|
506
|
-
* - UPDATE: no referenced parent column actually changed (
|
|
645
|
+
* - UPDATE: no referenced parent column actually changed ({@link anyReferencedColumnChanged}).
|
|
507
646
|
*/
|
|
508
647
|
function resolveLensFkParentReferencedValues(ref, parentMap, basisParentTable, operation, oldRow, newRow) {
|
|
509
648
|
// Logical referenced column → basis column → basis index. A column with no plain basis
|
|
@@ -525,14 +664,7 @@ function resolveLensFkParentReferencedValues(ref, parentMap, basisParentTable, o
|
|
|
525
664
|
// UPDATE short-circuit: skip when no referenced parent column actually changed.
|
|
526
665
|
let newParentValues;
|
|
527
666
|
if (operation === 'update' && newRow !== undefined) {
|
|
528
|
-
|
|
529
|
-
for (const i of basisIndices) {
|
|
530
|
-
if (!sqlValueIdentical(oldRow[i], newRow[i])) {
|
|
531
|
-
anyChanged = true;
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (!anyChanged)
|
|
667
|
+
if (!anyReferencedColumnChanged(basisIndices, oldRow, newRow))
|
|
536
668
|
return undefined;
|
|
537
669
|
newParentValues = basisIndices.map(i => newRow[i]);
|
|
538
670
|
}
|
|
@@ -607,7 +739,7 @@ async function issueLensFkAction(db, ref, action, operation, oldParentValues, ne
|
|
|
607
739
|
if (operation === 'delete') {
|
|
608
740
|
const sql = `delete from ${qualifiedChild} where ${whereClause}`;
|
|
609
741
|
log('LENS CASCADE DELETE: %s with params %o', sql, oldParentValues);
|
|
610
|
-
await withFkCascadeReentry(db, () => db.
|
|
742
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
611
743
|
}
|
|
612
744
|
else {
|
|
613
745
|
// CASCADE UPDATE: rewrite the child FK columns to the NEW parent values
|
|
@@ -616,7 +748,7 @@ async function issueLensFkAction(db, ref, action, operation, oldParentValues, ne
|
|
|
616
748
|
const sql = `update ${qualifiedChild} set ${setClauses} where ${whereClause}`;
|
|
617
749
|
const params = [...(newParentValues ?? []), ...oldParentValues];
|
|
618
750
|
log('LENS CASCADE UPDATE: %s with params %o', sql, params);
|
|
619
|
-
await withFkCascadeReentry(db, () => db.
|
|
751
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, params));
|
|
620
752
|
}
|
|
621
753
|
break;
|
|
622
754
|
}
|
|
@@ -624,7 +756,7 @@ async function issueLensFkAction(db, ref, action, operation, oldParentValues, ne
|
|
|
624
756
|
const setClauses = childLogicalColumns.map(c => `${quoteIdentifier(c)} = null`).join(', ');
|
|
625
757
|
const sql = `update ${qualifiedChild} set ${setClauses} where ${whereClause}`;
|
|
626
758
|
log('LENS SET NULL: %s with params %o', sql, oldParentValues);
|
|
627
|
-
await withFkCascadeReentry(db, () => db.
|
|
759
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
628
760
|
break;
|
|
629
761
|
}
|
|
630
762
|
case 'setDefault': {
|
|
@@ -638,7 +770,7 @@ async function issueLensFkAction(db, ref, action, operation, oldParentValues, ne
|
|
|
638
770
|
}).join(', ');
|
|
639
771
|
const sql = `update ${qualifiedChild} set ${setClauses} where ${whereClause}`;
|
|
640
772
|
log('LENS SET DEFAULT: %s with params %o', sql, oldParentValues);
|
|
641
|
-
await withFkCascadeReentry(db, () => db.
|
|
773
|
+
await withFkCascadeReentry(db, () => db._internalStatementCache.run(sql, oldParentValues));
|
|
642
774
|
break;
|
|
643
775
|
}
|
|
644
776
|
}
|
|
@@ -745,21 +877,11 @@ async function assertNoLensChildReferences(db, ref, parentSlot, operation, oldPa
|
|
|
745
877
|
const whereClause = ref.childLogicalColumns.map(c => `${quoteIdentifier(c)} = ?`).join(' and ');
|
|
746
878
|
const sql = `select 1 from ${qualifiedChild} where ${whereClause} limit 1`;
|
|
747
879
|
log('LENS RESTRICT check (%s): %s with params %o', operation, sql, oldParentValues);
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
referenced = true;
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
if (referenced) {
|
|
757
|
-
const opName = operation === 'delete' ? 'DELETE' : 'UPDATE';
|
|
758
|
-
throw new QuereusError(`FOREIGN KEY constraint failed: ${opName} on '${parentSlot.logicalTable.name}' violates RESTRICT from '${childTable.name}'`, StatusCode.CONSTRAINT);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
finally {
|
|
762
|
-
await stmt.finalize();
|
|
880
|
+
// Cached internal statement (compiled once per shape) rather than a fresh
|
|
881
|
+
// per-row prepare/finalize — see InternalStatementCache.
|
|
882
|
+
if (await db._internalStatementCache.probe(sql, oldParentValues)) {
|
|
883
|
+
const opName = operation === 'delete' ? 'DELETE' : 'UPDATE';
|
|
884
|
+
throw new QuereusError(`FOREIGN KEY constraint failed: ${opName} on '${parentSlot.logicalTable.name}' violates RESTRICT from '${childTable.name}'`, StatusCode.CONSTRAINT);
|
|
763
885
|
}
|
|
764
886
|
}
|
|
765
887
|
//# sourceMappingURL=foreign-key-actions.js.map
|