@quereus/quereus 4.13.1 → 4.15.2

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.
Files changed (135) hide show
  1. package/dist/src/core/statement.d.ts +30 -0
  2. package/dist/src/core/statement.d.ts.map +1 -1
  3. package/dist/src/core/statement.js +49 -0
  4. package/dist/src/core/statement.js.map +1 -1
  5. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  6. package/dist/src/emit/ast-stringify.js +0 -4
  7. package/dist/src/emit/ast-stringify.js.map +1 -1
  8. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  9. package/dist/src/func/builtins/explain.js +89 -62
  10. package/dist/src/func/builtins/explain.js.map +1 -1
  11. package/dist/src/func/registration.d.ts +7 -0
  12. package/dist/src/func/registration.d.ts.map +1 -1
  13. package/dist/src/func/registration.js +7 -0
  14. package/dist/src/func/registration.js.map +1 -1
  15. package/dist/src/index.d.ts +3 -0
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/index.js +1 -0
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/parser/ast.d.ts +0 -2
  20. package/dist/src/parser/ast.d.ts.map +1 -1
  21. package/dist/src/parser/parser.d.ts +1 -0
  22. package/dist/src/parser/parser.d.ts.map +1 -1
  23. package/dist/src/parser/parser.js +29 -28
  24. package/dist/src/parser/parser.js.map +1 -1
  25. package/dist/src/planner/mutation/capture-correlation.d.ts +57 -0
  26. package/dist/src/planner/mutation/capture-correlation.d.ts.map +1 -0
  27. package/dist/src/planner/mutation/capture-correlation.js +57 -0
  28. package/dist/src/planner/mutation/capture-correlation.js.map +1 -0
  29. package/dist/src/planner/mutation/decomposition.d.ts.map +1 -1
  30. package/dist/src/planner/mutation/decomposition.js +86 -23
  31. package/dist/src/planner/mutation/decomposition.js.map +1 -1
  32. package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -1
  33. package/dist/src/planner/mutation/lens-enforcement.js +15 -20
  34. package/dist/src/planner/mutation/lens-enforcement.js.map +1 -1
  35. package/dist/src/planner/mutation/multi-source.d.ts +52 -11
  36. package/dist/src/planner/mutation/multi-source.d.ts.map +1 -1
  37. package/dist/src/planner/mutation/multi-source.js +273 -86
  38. package/dist/src/planner/mutation/multi-source.js.map +1 -1
  39. package/dist/src/planner/mutation/set-op.d.ts.map +1 -1
  40. package/dist/src/planner/mutation/set-op.js +4 -12
  41. package/dist/src/planner/mutation/set-op.js.map +1 -1
  42. package/dist/src/planner/mutation/single-source.js +1 -1
  43. package/dist/src/planner/mutation/single-source.js.map +1 -1
  44. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  45. package/dist/src/runtime/emit/alter-table.js +8 -10
  46. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  47. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  48. package/dist/src/runtime/emit/dml-executor.js +23 -0
  49. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  50. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +16 -4
  51. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  52. package/dist/src/runtime/emit/materialized-view-helpers.js +182 -25
  53. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  54. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  55. package/dist/src/runtime/emit/materialized-view.js +1 -1
  56. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  57. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  58. package/dist/src/runtime/emit/scan.js +52 -9
  59. package/dist/src/runtime/emit/scan.js.map +1 -1
  60. package/dist/src/runtime/emit/set-object-tags.js +4 -4
  61. package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
  62. package/dist/src/runtime/emitters.d.ts.map +1 -1
  63. package/dist/src/runtime/emitters.js +6 -0
  64. package/dist/src/runtime/emitters.js.map +1 -1
  65. package/dist/src/runtime/parallel-driver.d.ts +5 -0
  66. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  67. package/dist/src/runtime/parallel-driver.js +6 -0
  68. package/dist/src/runtime/parallel-driver.js.map +1 -1
  69. package/dist/src/runtime/scheduler.d.ts +9 -0
  70. package/dist/src/runtime/scheduler.d.ts.map +1 -1
  71. package/dist/src/runtime/scheduler.js +27 -11
  72. package/dist/src/runtime/scheduler.js.map +1 -1
  73. package/dist/src/runtime/types.d.ts +19 -0
  74. package/dist/src/runtime/types.d.ts.map +1 -1
  75. package/dist/src/runtime/types.js.map +1 -1
  76. package/dist/src/runtime/work-counters.d.ts +180 -0
  77. package/dist/src/runtime/work-counters.d.ts.map +1 -0
  78. package/dist/src/runtime/work-counters.js +210 -0
  79. package/dist/src/runtime/work-counters.js.map +1 -0
  80. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  81. package/dist/src/schema/ddl-generator.js +65 -24
  82. package/dist/src/schema/ddl-generator.js.map +1 -1
  83. package/dist/src/schema/lens-ack.d.ts +1 -1
  84. package/dist/src/schema/lens-ack.js +2 -2
  85. package/dist/src/schema/lens-compiler.d.ts.map +1 -1
  86. package/dist/src/schema/lens-compiler.js +63 -34
  87. package/dist/src/schema/lens-compiler.js.map +1 -1
  88. package/dist/src/schema/lens-prover.d.ts +4 -4
  89. package/dist/src/schema/lens-prover.d.ts.map +1 -1
  90. package/dist/src/schema/lens-prover.js +13 -5
  91. package/dist/src/schema/lens-prover.js.map +1 -1
  92. package/dist/src/schema/lens.d.ts +1 -1
  93. package/dist/src/schema/manager.d.ts.map +1 -1
  94. package/dist/src/schema/manager.js +25 -13
  95. package/dist/src/schema/manager.js.map +1 -1
  96. package/dist/src/schema/reserved-tags.js +1 -1
  97. package/dist/src/schema/reserved-tags.js.map +1 -1
  98. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  99. package/dist/src/schema/schema-differ.js +4 -3
  100. package/dist/src/schema/schema-differ.js.map +1 -1
  101. package/dist/src/schema/table.d.ts +41 -34
  102. package/dist/src/schema/table.d.ts.map +1 -1
  103. package/dist/src/schema/table.js +24 -43
  104. package/dist/src/schema/table.js.map +1 -1
  105. package/dist/src/vtab/alter-event-shape.d.ts +35 -0
  106. package/dist/src/vtab/alter-event-shape.d.ts.map +1 -0
  107. package/dist/src/vtab/alter-event-shape.js +47 -0
  108. package/dist/src/vtab/alter-event-shape.js.map +1 -0
  109. package/dist/src/vtab/memory/layer/alter-column.d.ts +8 -1
  110. package/dist/src/vtab/memory/layer/alter-column.d.ts.map +1 -1
  111. package/dist/src/vtab/memory/layer/alter-column.js +20 -10
  112. package/dist/src/vtab/memory/layer/alter-column.js.map +1 -1
  113. package/dist/src/vtab/memory/layer/base.d.ts +9 -1
  114. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  115. package/dist/src/vtab/memory/layer/base.js +12 -1
  116. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  117. package/dist/src/vtab/memory/layer/manager.d.ts +23 -18
  118. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  119. package/dist/src/vtab/memory/layer/manager.js +44 -33
  120. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  121. package/dist/src/vtab/memory/layer/row-convert.d.ts +13 -0
  122. package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -1
  123. package/dist/src/vtab/memory/layer/row-convert.js +24 -0
  124. package/dist/src/vtab/memory/layer/row-convert.js.map +1 -1
  125. package/dist/src/vtab/memory/layer/transaction.d.ts +21 -7
  126. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  127. package/dist/src/vtab/memory/layer/transaction.js +30 -14
  128. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  129. package/dist/src/vtab/memory/module.d.ts +0 -8
  130. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  131. package/dist/src/vtab/memory/module.js +2 -25
  132. package/dist/src/vtab/memory/module.js.map +1 -1
  133. package/dist/src/vtab/module.d.ts +20 -2
  134. package/dist/src/vtab/module.d.ts.map +1 -1
  135. package/package.json +6 -3
@@ -0,0 +1,57 @@
1
+ import type * as AST from '../../parser/ast.js';
2
+ import type { TableSchema } from '../../schema/table.js';
3
+ /**
4
+ * One identifying key column of a capture correlation, with its **declared**
5
+ * nullability. Key membership does not imply NOT NULL (docs/schema.md
6
+ * § Primary-key nullability), and key comparison treats NULL as a value equal to
7
+ * itself — so a capture reader correlating on a nullable key column must use the
8
+ * NULL-safe equality {@link captureKeyEquality} emits, or a row keyed NULL is
9
+ * silently unaddressable.
10
+ */
11
+ export interface KeyColumnInfo {
12
+ readonly name: string;
13
+ /** True when the column's declared type admits NULL (`ColumnSchema.notNull === false`). */
14
+ readonly nullable: boolean;
15
+ }
16
+ /**
17
+ * Look one correlation key column up on its base schema (case-insensitively). An
18
+ * unknown column (defensive — every caller resolves the name off validated
19
+ * schema/advertisement metadata) reads as **nullable**, so a correlation over it
20
+ * falls back to the NULL-safe form: semantically identical for a NOT NULL column,
21
+ * merely less index-friendly. The one place declared nullability is read, so the
22
+ * decomposition write fan-out and the lens read body cannot drift apart on the
23
+ * gate that decides {@link captureKeyEquality}'s shape.
24
+ */
25
+ export declare function keyColumnInfo(schema: TableSchema, name: string): KeyColumnInfo;
26
+ /**
27
+ * Per-key-column equality for correlating two spellings of one key value — a
28
+ * capture (`__vmupd_keys`) reader to its target row, and equally a decomposition
29
+ * lens's stitch-key correlations (the get body's `anchor ⋈ member` ON condition
30
+ * and EAV entity subquery in `schema/lens-compiler.ts`, and the write fan-out's
31
+ * anchor EXISTS correlation in `decomposition.ts`):
32
+ *
33
+ * ```
34
+ * left = right -- column declared NOT NULL
35
+ * left = right or (left is null and right is null) -- column declared nullable
36
+ * ```
37
+ *
38
+ * The nullability gate exists because SQL `=` yields UNKNOWN when either operand
39
+ * is NULL: a nullable key column needs the NULL-safe disjunction (NULL is a
40
+ * self-equal key value), while a NOT NULL column keeps the plain `=`, which
41
+ * stays index-friendly where a disjunction can cost the engine a seek. Sibling
42
+ * implementation: `planner/analysis/key-filter.ts` emits the same per-column
43
+ * shape for residual key filters (as plan nodes rather than AST).
44
+ *
45
+ * Operands are cloned into each emitted position, so a caller may pass the same
46
+ * node it uses elsewhere without sharing AST subtrees.
47
+ *
48
+ * NOTE: the gate reads the **declared** nullability, so a database run under
49
+ * `pragma default_column_nullability = nullable` declares every key column nullable
50
+ * and every capture correlation takes the disjunction — including the ones a NOT NULL
51
+ * default would have left as a seekable `=`. Correct either way, but if view writes
52
+ * under that pragma ever show up as slow, narrow the gate to the *reachable* nullability
53
+ * (a key column with a NOT NULL check, or one the body's predicate already excludes NULL
54
+ * on) rather than the declared one.
55
+ */
56
+ export declare function captureKeyEquality(left: AST.Expression, right: AST.Expression, nullable: boolean): AST.Expression;
57
+ //# sourceMappingURL=capture-correlation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-correlation.d.ts","sourceRoot":"","sources":["../../../../src/planner/mutation/capture-correlation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAG9E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,GAAG,GAAG,CAAC,UAAU,CAUjH"}
@@ -0,0 +1,57 @@
1
+ import { cloneExpr } from './scope-transform.js';
2
+ /**
3
+ * Look one correlation key column up on its base schema (case-insensitively). An
4
+ * unknown column (defensive — every caller resolves the name off validated
5
+ * schema/advertisement metadata) reads as **nullable**, so a correlation over it
6
+ * falls back to the NULL-safe form: semantically identical for a NOT NULL column,
7
+ * merely less index-friendly. The one place declared nullability is read, so the
8
+ * decomposition write fan-out and the lens read body cannot drift apart on the
9
+ * gate that decides {@link captureKeyEquality}'s shape.
10
+ */
11
+ export function keyColumnInfo(schema, name) {
12
+ const col = schema.columns.find(c => c.name.toLowerCase() === name.toLowerCase());
13
+ return { name, nullable: !col || !col.notNull };
14
+ }
15
+ /**
16
+ * Per-key-column equality for correlating two spellings of one key value — a
17
+ * capture (`__vmupd_keys`) reader to its target row, and equally a decomposition
18
+ * lens's stitch-key correlations (the get body's `anchor ⋈ member` ON condition
19
+ * and EAV entity subquery in `schema/lens-compiler.ts`, and the write fan-out's
20
+ * anchor EXISTS correlation in `decomposition.ts`):
21
+ *
22
+ * ```
23
+ * left = right -- column declared NOT NULL
24
+ * left = right or (left is null and right is null) -- column declared nullable
25
+ * ```
26
+ *
27
+ * The nullability gate exists because SQL `=` yields UNKNOWN when either operand
28
+ * is NULL: a nullable key column needs the NULL-safe disjunction (NULL is a
29
+ * self-equal key value), while a NOT NULL column keeps the plain `=`, which
30
+ * stays index-friendly where a disjunction can cost the engine a seek. Sibling
31
+ * implementation: `planner/analysis/key-filter.ts` emits the same per-column
32
+ * shape for residual key filters (as plan nodes rather than AST).
33
+ *
34
+ * Operands are cloned into each emitted position, so a caller may pass the same
35
+ * node it uses elsewhere without sharing AST subtrees.
36
+ *
37
+ * NOTE: the gate reads the **declared** nullability, so a database run under
38
+ * `pragma default_column_nullability = nullable` declares every key column nullable
39
+ * and every capture correlation takes the disjunction — including the ones a NOT NULL
40
+ * default would have left as a seekable `=`. Correct either way, but if view writes
41
+ * under that pragma ever show up as slow, narrow the gate to the *reachable* nullability
42
+ * (a key column with a NOT NULL check, or one the body's predicate already excludes NULL
43
+ * on) rather than the declared one.
44
+ */
45
+ export function captureKeyEquality(left, right, nullable) {
46
+ const eq = { type: 'binary', operator: '=', left: cloneExpr(left), right: cloneExpr(right) };
47
+ if (!nullable)
48
+ return eq;
49
+ const bothNull = {
50
+ type: 'binary',
51
+ operator: 'AND',
52
+ left: { type: 'unary', operator: 'IS NULL', expr: cloneExpr(left) },
53
+ right: { type: 'unary', operator: 'IS NULL', expr: cloneExpr(right) },
54
+ };
55
+ return { type: 'binary', operator: 'OR', left: eq, right: bothNull };
56
+ }
57
+ //# sourceMappingURL=capture-correlation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-correlation.js","sourceRoot":"","sources":["../../../../src/planner/mutation/capture-correlation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAgBjD;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,MAAmB,EAAE,IAAY;IAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAClF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAoB,EAAE,KAAqB,EAAE,QAAiB;IAChG,MAAM,EAAE,GAAmB,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7G,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAmB;QAChC,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAmB;QACpF,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,EAAmB;KACtF,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACtE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"decomposition.d.ts","sourceRoot":"","sources":["../../../../src/planner/mutation/decomposition.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"decomposition.d.ts","sourceRoot":"","sources":["../../../../src/planner/mutation/decomposition.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { FilterNode } from '../nodes/filter.js';
2
2
  import { ProjectNode } from '../nodes/project-node.js';
3
3
  import { buildTableReference } from '../building/table.js';
4
- import { combineAnd } from './single-source.js';
4
+ import { combineAnd, SELF_ALIAS } from './single-source.js';
5
5
  import { transformExpr, cloneExpr } from './scope-transform.js';
6
6
  import { buildExpression } from '../building/expression.js';
7
7
  import { columnSchemaToScalarType } from '../type-utils.js';
@@ -10,6 +10,7 @@ import { containsNonDeterministicCall } from '../analysis/check-extraction.js';
10
10
  import { FunctionFlags } from '../../common/constants.js';
11
11
  import { analyzeBodyLineage } from './backward-body.js';
12
12
  import { keyColumnName, capturedValueSubquery } from './multi-source.js';
13
+ import { captureKeyEquality, keyColumnInfo } from './capture-correlation.js';
13
14
  import { raiseMutationDiagnostic } from './mutation-diagnostic.js';
14
15
  /**
15
16
  * Plan the synthesized get body **once** and read its threaded `updateLineage`
@@ -435,19 +436,26 @@ function decomposeDelete(ctx, view, shape, stmt) {
435
436
  * One member's delete. No predicate ⇒ an unconditional `delete from <member>`
436
437
  * (truncate the component — also the sound singleton path, which has no key to
437
438
  * thread). With an anchor predicate ⇒ `delete from <member> where
438
- * <memberKeyOrEntity> in (select <anchorKey> from <anchor> where <pred>)`.
439
+ * <memberKeyOrEntity> in (select <anchorKey> from <anchor> where <pred>)` — or
440
+ * the NULL-safe correlated EXISTS with the target aliased {@link SELF_ALIAS}
441
+ * when both stitch columns are nullable ({@link anchorKeyCorrelation}), so a
442
+ * NULL-keyed component row does not survive the fan-out as an orphan.
439
443
  */
440
444
  function memberDeleteOp(ctx, view, shape, member, pred, stmt) {
441
445
  let where;
446
+ let targetAlias;
442
447
  if (pred) {
443
448
  const memberCol = member.attributePivot
444
449
  ? member.attributePivot.entityColumn // EAV: delete every triple for the matched entities
445
450
  : singleKeyColumn(view, shape, member);
446
- where = { type: 'in', expr: { type: 'column', name: memberCol }, subquery: anchorKeySubquery(shape, pred) };
451
+ const correlation = anchorKeyCorrelation(ctx, shape, member, memberCol, pred);
452
+ where = correlation.where;
453
+ targetAlias = correlation.targetAlias;
447
454
  }
448
455
  const statement = {
449
456
  type: 'delete',
450
457
  table: memberIdentifier(member),
458
+ alias: targetAlias,
451
459
  where,
452
460
  contextValues: stmt.contextValues,
453
461
  schemaPath: stmt.schemaPath,
@@ -754,19 +762,20 @@ function collectValueScopes(expr) {
754
762
  walk(expr);
755
763
  return { qualifiers, hasUnqualifiedColumn, hasSubquery };
756
764
  }
757
- /** `update <member> set <cols> where <memberKey> in (select <anchorKey> from <anchor> where <pred>)`. */
765
+ /**
766
+ * `update <member> set <cols> where <memberKey> in (select <anchorKey> from <anchor>
767
+ * where <pred>)` — or the NULL-safe correlated EXISTS with the target aliased
768
+ * {@link SELF_ALIAS} when both stitch columns are nullable ({@link anchorKeyCorrelation}).
769
+ */
758
770
  function memberUpdateOp(ctx, view, shape, member, assignments, pred, stmt) {
759
771
  const memberKey = singleKeyColumn(view, shape, member);
760
- const where = {
761
- type: 'in',
762
- expr: { type: 'column', name: memberKey },
763
- subquery: anchorKeySubquery(shape, pred),
764
- };
772
+ const correlation = anchorKeyCorrelation(ctx, shape, member, memberKey, pred);
765
773
  const statement = {
766
774
  type: 'update',
767
775
  table: memberIdentifier(member),
776
+ alias: correlation.targetAlias,
768
777
  assignments: assignments.map(a => ({ column: a.column, value: a.value })),
769
- where,
778
+ where: correlation.where,
770
779
  contextValues: stmt.contextValues,
771
780
  schemaPath: stmt.schemaPath,
772
781
  loc: stmt.loc,
@@ -941,7 +950,7 @@ function emitCapturedMemberUpdate(ctx, view, shape, member, cells, pred, stmt, o
941
950
  // basis column so the two read-back sites (member key, anchor key) bind the same alias.
942
951
  const srcByCell = cells.map(c => registerCapturedExpr(`cap:${member.relationId.toLowerCase()}:${c.basisColumn.toLowerCase()}`, cloneExpr(c.value)));
943
952
  // Matched UPDATE (unfiltered over the present rows): each value read back by the member key.
944
- ops.push(memberUpdateOp(ctx, view, shape, member, cells.map((c, i) => ({ column: c.basisColumn, value: capturedValueSubquery(srcByCell[i], 0, [memberKey]) })), pred, stmt));
953
+ ops.push(memberUpdateOp(ctx, view, shape, member, cells.map((c, i) => ({ column: c.basisColumn, value: capturedValueSubquery(srcByCell[i], 0, [keyColumnInfo(schema, memberKey)]) })), pred, stmt));
945
954
  // Materialize INSERT (filtered over the absent rows): each value read back by the anchor key.
946
955
  ops.push(buildCapturedMaterializeInsert(ctx, view, shape, member, cells, srcByCell, pred, stmt));
947
956
  }
@@ -963,6 +972,7 @@ function buildCapturedMaterializeInsert(ctx, view, shape, member, cells, srcByCe
963
972
  const ref = resolveMemberTable(ctx, member);
964
973
  const schema = ref.tableSchema;
965
974
  const anchorKey = singleKeyColumn(view, shape, shape.anchor);
975
+ const anchorKeyInfo = keyColumnInfo(resolveMemberTable(ctx, shape.anchor).tableSchema, anchorKey);
966
976
  const memberKey = singleKeyColumn(view, shape, member);
967
977
  const targetColumns = [memberKey];
968
978
  const projections = [
@@ -970,14 +980,14 @@ function buildCapturedMaterializeInsert(ctx, view, shape, member, cells, srcByCe
970
980
  ];
971
981
  cells.forEach((c, i) => {
972
982
  targetColumns.push(c.basisColumn);
973
- projections.push({ type: 'column', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKey]) });
983
+ projections.push({ type: 'column', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKeyInfo]) });
974
984
  });
975
985
  assertNoMissingNotNull(view, schema, targetColumns.map((baseColumn) => ({ baseColumn })));
976
986
  // Non-empty image filter: OR over each captured value (read back by the anchor key) being
977
987
  // non-null. A fresh `capturedValueSubquery` per disjunct (distinct AST nodes from the
978
988
  // projections') keeps the filter self-contained.
979
989
  const nonEmpty = cells
980
- .map((_, i) => ({ type: 'unary', operator: 'IS NOT NULL', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKey]) }))
990
+ .map((_, i) => ({ type: 'unary', operator: 'IS NOT NULL', expr: capturedValueSubquery(srcByCell[i], 0, [anchorKeyInfo]) }))
981
991
  .reduce((acc, e) => acc ? { type: 'binary', operator: 'OR', left: acc, right: e } : e);
982
992
  const where = combineAnd(pred ? cloneExpr(pred) : undefined, nonEmpty);
983
993
  const select = {
@@ -1327,34 +1337,34 @@ function emitEavCapturedAttr(ctx, view, shape, member, pivot, cell, pred, stmt,
1327
1337
  // so both read-back sites (entity column, anchor key) bind the same alias.
1328
1338
  const srcAlias = registerCapturedExpr(`cap:${member.relationId.toLowerCase()}:attr:${cell.attribute.toLowerCase()}`, cloneExpr(cell.value));
1329
1339
  // Matched UPDATE: existing triples read the captured value back by the entity column.
1330
- ops.push(buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, 'update', capturedValueSubquery(srcAlias, 0, [pivot.entityColumn])));
1340
+ ops.push(buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, 'update', capturedValueSubquery(srcAlias, 0, [keyColumnInfo(resolveMemberTable(ctx, member).tableSchema, pivot.entityColumn)])));
1331
1341
  // Materialize INSERT: entities lacking the triple, value read back by the anchor key, non-null filtered.
1332
1342
  ops.push(buildEavCapturedInsert(ctx, view, shape, member, pivot, cell, srcAlias, pred, stmt));
1333
1343
  }
1334
1344
  /**
1335
1345
  * One matched EAV op for an attribute: `update <pivot> set <valCol> = <value>` (upsert
1336
1346
  * value branch) or `delete from <pivot>` (null branch), each scoped
1337
- * `where <attrCol> = '<attribute>' and <entityCol> in (<anchor subquery>)`. `valueOverride`
1347
+ * `where <attrCol> = '<attribute>' and <entityCol> in (<anchor subquery>)` — the entity
1348
+ * correlation taking the NULL-safe EXISTS form (target aliased {@link SELF_ALIAS}) when
1349
+ * both the entity column and the anchor key are nullable ({@link anchorKeyCorrelation});
1350
+ * the attribute conjunct compares a non-null literal and stays plain `=`. `valueOverride`
1338
1351
  * supplies the matched-UPDATE value for a `captured` cell (the `__vmupd_keys` read-back correlated
1339
1352
  * by the entity column); absent, the cell's own lowered value is used (the `constant` branch).
1340
1353
  */
1341
1354
  function buildEavAttrOp(ctx, view, shape, member, pivot, cell, pred, stmt, op, valueOverride) {
1342
- const where = combineAnd(eavAttrEquals(pivot, cell.attribute), {
1343
- type: 'in',
1344
- expr: { type: 'column', name: pivot.entityColumn },
1345
- subquery: anchorKeySubquery(shape, pred),
1346
- });
1355
+ const correlation = anchorKeyCorrelation(ctx, shape, member, pivot.entityColumn, pred);
1356
+ const where = combineAnd(eavAttrEquals(pivot, cell.attribute), correlation.where);
1347
1357
  const table = memberIdentifier(member);
1348
1358
  const resolved = resolveMemberTable(ctx, member);
1349
1359
  if (op === 'delete') {
1350
1360
  const statement = {
1351
- type: 'delete', table, where,
1361
+ type: 'delete', table, alias: correlation.targetAlias, where,
1352
1362
  contextValues: stmt.contextValues, schemaPath: stmt.schemaPath, loc: stmt.loc,
1353
1363
  };
1354
1364
  return { table: resolved, op: 'delete', statement };
1355
1365
  }
1356
1366
  const statement = {
1357
- type: 'update', table,
1367
+ type: 'update', table, alias: correlation.targetAlias,
1358
1368
  assignments: [{ column: pivot.valueColumn, value: valueOverride ?? cloneExpr(cell.value) }],
1359
1369
  where,
1360
1370
  contextValues: stmt.contextValues, schemaPath: stmt.schemaPath, loc: stmt.loc,
@@ -1426,8 +1436,9 @@ function buildEavInsertSelect(ctx, view, shape, member, pivot, cell, pred, stmt,
1426
1436
  function buildEavCapturedInsert(ctx, view, shape, member, pivot, cell, srcAlias, pred, stmt) {
1427
1437
  const ref = resolveMemberTable(ctx, member);
1428
1438
  const anchorKey = singleKeyColumn(view, shape, shape.anchor);
1439
+ const anchorKeyInfo = keyColumnInfo(resolveMemberTable(ctx, shape.anchor).tableSchema, anchorKey);
1429
1440
  // A fresh read-back subquery per use (projection + filter) — distinct AST nodes.
1430
- const capturedVal = () => capturedValueSubquery(srcAlias, 0, [anchorKey]);
1441
+ const capturedVal = () => capturedValueSubquery(srcAlias, 0, [anchorKeyInfo]);
1431
1442
  const projections = [
1432
1443
  { type: 'column', expr: { type: 'column', name: anchorKey, table: shape.anchor.relationId } },
1433
1444
  { type: 'column', expr: { type: 'literal', value: cell.attribute } },
@@ -1627,6 +1638,58 @@ function anchorKeySubquery(shape, pred) {
1627
1638
  where: pred ? cloneExpr(pred) : undefined,
1628
1639
  };
1629
1640
  }
1641
+ /**
1642
+ * The identifying correlation a member op routes on: `<memberCol> in (select <anchorKey>
1643
+ * from <anchor> [where <pred>])` when either stitch column is declared NOT NULL, or the
1644
+ * NULL-safe correlated `exists (select 1 from <anchor> where [<pred> and] <per-column
1645
+ * {@link captureKeyEquality}>)` when **both** are nullable — NULL is a self-equal key
1646
+ * value (docs/schema.md § Primary-key nullability), and `NULL IN (…)` is UNKNOWN, so
1647
+ * the IN shape leaves a NULL-keyed member row unreachable (an update misses it; a
1648
+ * fan-out delete strands it as an orphan). The both-sides gate keeps the emitted
1649
+ * statement byte-identical to the legacy IN form for any schema whose stitch key is
1650
+ * NOT NULL on either side. `memberCol` is the member's stitch key, or the EAV pivot's
1651
+ * entity column.
1652
+ *
1653
+ * On the EXISTS branch the member operand is qualified with {@link SELF_ALIAS} — under
1654
+ * a logical-tuple key the anchor and member key columns are spelled identically, so a
1655
+ * bare ref inside the subquery would bind the anchor's column, not the outer target
1656
+ * row. The caller must stamp the returned `targetAlias` onto the emitted UPDATE/DELETE
1657
+ * (`stmt.alias` → `AliasedScope`, `building/update.ts` / `building/delete.ts`);
1658
+ * `targetAlias` is undefined on the IN branch so NOT NULL schemas keep an alias-free
1659
+ * statement. Composite stitch keys are deferred by {@link singleKeyColumn}, so the
1660
+ * per-column shape degenerates to one column here.
1661
+ *
1662
+ * NOTE: the EXISTS branch is **correlated**, so unlike the uncorrelated IN subquery it
1663
+ * cannot be evaluated once per statement. The anchor's own value-column UPDATE routes
1664
+ * through here too (it is a mandatory member of its own fan-out) and so self-correlates
1665
+ * against the table it writes — where {@link anchorDeleteOp} sidesteps the same shape by
1666
+ * applying the bare predicate. Not measured; if a nullable-stitch-key lens write ever
1667
+ * shows up in profiles, give the anchor member the bare-predicate form the delete side
1668
+ * already uses, and consider a NULL-safe uncorrelated membership shape for the rest.
1669
+ */
1670
+ function anchorKeyCorrelation(ctx, shape, member, memberCol, pred) {
1671
+ const anchorKey = singleKeyColumn(undefined, shape, shape.anchor);
1672
+ const anchorInfo = keyColumnInfo(resolveMemberTable(ctx, shape.anchor).tableSchema, anchorKey);
1673
+ const memberInfo = keyColumnInfo(resolveMemberTable(ctx, member).tableSchema, memberCol);
1674
+ if (!(anchorInfo.nullable && memberInfo.nullable)) {
1675
+ return {
1676
+ where: { type: 'in', expr: { type: 'column', name: memberCol }, subquery: anchorKeySubquery(shape, pred) },
1677
+ };
1678
+ }
1679
+ const keyEq = captureKeyEquality({ type: 'column', name: anchorKey, table: shape.anchor.relationId }, { type: 'column', name: memberCol, table: SELF_ALIAS }, true);
1680
+ return {
1681
+ where: {
1682
+ type: 'exists',
1683
+ subquery: {
1684
+ type: 'select',
1685
+ columns: [{ type: 'column', expr: { type: 'literal', value: 1 } }],
1686
+ from: [{ ...memberIdentifierSource(shape.anchor), alias: shape.anchor.relationId }],
1687
+ where: combineAnd(pred ? cloneExpr(pred) : undefined, keyEq),
1688
+ },
1689
+ },
1690
+ targetAlias: SELF_ALIAS,
1691
+ };
1692
+ }
1630
1693
  /**
1631
1694
  * Substitute references to logical columns (unqualified, or qualified by the
1632
1695
  * logical table's own name) with their backing get-body expression. Base-member-