@quereus/quereus 0.6.4 → 0.6.6
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 +19 -1
- package/dist/src/common/logger.d.ts +59 -0
- package/dist/src/common/logger.d.ts.map +1 -1
- package/dist/src/common/logger.js +68 -0
- package/dist/src/common/logger.js.map +1 -1
- package/dist/src/func/builtins/datetime.d.ts.map +1 -1
- package/dist/src/func/builtins/datetime.js +10 -5
- package/dist/src/func/builtins/datetime.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +4 -0
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +2 -1
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +4 -1
- 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 +4 -2
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +8 -2
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js +11 -2
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/validation/determinism-validator.d.ts +47 -0
- package/dist/src/planner/validation/determinism-validator.d.ts.map +1 -0
- package/dist/src/planner/validation/determinism-validator.js +63 -0
- package/dist/src/planner/validation/determinism-validator.js.map +1 -0
- package/dist/src/runtime/async-util.d.ts.map +1 -1
- package/dist/src/runtime/async-util.js +4 -3
- package/dist/src/runtime/async-util.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +3 -0
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +84 -8
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/emitters.d.ts.map +1 -1
- package/dist/src/runtime/emitters.js +2 -1
- package/dist/src/runtime/emitters.js.map +1 -1
- package/dist/src/runtime/types.d.ts +1 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/runtime/utils.d.ts +14 -0
- package/dist/src/runtime/utils.d.ts.map +1 -1
- package/dist/src/runtime/utils.js +40 -1
- package/dist/src/runtime/utils.js.map +1 -1
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +50 -0
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/util/ast-stringify.d.ts.map +1 -1
- package/dist/src/util/ast-stringify.js +14 -1
- package/dist/src/util/ast-stringify.js.map +1 -1
- package/dist/src/util/mutation-statement.d.ts +16 -0
- package/dist/src/util/mutation-statement.d.ts.map +1 -0
- package/dist/src/util/mutation-statement.js +92 -0
- package/dist/src/util/mutation-statement.js.map +1 -0
- package/dist/src/util/sql-literal.d.ts +11 -0
- package/dist/src/util/sql-literal.d.ts.map +1 -0
- package/dist/src/util/sql-literal.js +18 -0
- package/dist/src/util/sql-literal.js.map +1 -0
- package/dist/src/vtab/memory/table.d.ts +1 -2
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +3 -3
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/table.d.ts +23 -5
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js +6 -0
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +1 -1
- package/src/common/logger.ts +75 -1
- package/src/func/builtins/datetime.ts +10 -5
- package/src/index.ts +4 -1
- package/src/planner/building/constraint-builder.ts +178 -173
- package/src/planner/building/delete.ts +5 -1
- package/src/planner/building/insert.ts +8 -1
- package/src/planner/building/update.ts +10 -2
- package/src/planner/nodes/dml-executor-node.ts +8 -2
- package/src/planner/validation/determinism-validator.ts +104 -0
- package/src/runtime/async-util.ts +4 -3
- package/src/runtime/emit/add-constraint.ts +3 -0
- package/src/runtime/emit/dml-executor.ts +105 -9
- package/src/runtime/emitters.ts +2 -1
- package/src/runtime/types.ts +3 -0
- package/src/runtime/utils.ts +41 -1
- package/src/schema/manager.ts +800 -742
- package/src/util/ast-stringify.ts +24 -1
- package/src/util/hash.ts +90 -90
- package/src/util/mutation-statement.ts +129 -0
- package/src/util/plugin-helper.ts +110 -110
- package/src/util/sql-literal.ts +22 -0
- package/src/vtab/memory/table.ts +3 -8
- package/src/vtab/table.ts +25 -10
|
@@ -1,173 +1,178 @@
|
|
|
1
|
-
import type { PlanningContext } from '../planning-context.js';
|
|
2
|
-
import type { TableSchema, RowConstraintSchema } from '../../schema/table.js';
|
|
3
|
-
import type { RowOpFlag } from '../../schema/table.js';
|
|
4
|
-
import type { Attribute, RowDescriptor } from '../nodes/plan-node.js';
|
|
5
|
-
import type { ConstraintCheck } from '../nodes/constraint-check-node.js';
|
|
6
|
-
import { RegisteredScope } from '../scopes/registered.js';
|
|
7
|
-
import { buildExpression } from './expression.js';
|
|
8
|
-
import { PlanNodeType } from '../nodes/plan-node-type.js';
|
|
9
|
-
import { ColumnReferenceNode } from '../nodes/reference.js';
|
|
10
|
-
import type { ScalarPlanNode } from '../nodes/plan-node.js';
|
|
11
|
-
import * as AST from '../../parser/ast.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
1
|
+
import type { PlanningContext } from '../planning-context.js';
|
|
2
|
+
import type { TableSchema, RowConstraintSchema } from '../../schema/table.js';
|
|
3
|
+
import type { RowOpFlag } from '../../schema/table.js';
|
|
4
|
+
import type { Attribute, RowDescriptor } from '../nodes/plan-node.js';
|
|
5
|
+
import type { ConstraintCheck } from '../nodes/constraint-check-node.js';
|
|
6
|
+
import { RegisteredScope } from '../scopes/registered.js';
|
|
7
|
+
import { buildExpression } from './expression.js';
|
|
8
|
+
import { PlanNodeType } from '../nodes/plan-node-type.js';
|
|
9
|
+
import { ColumnReferenceNode } from '../nodes/reference.js';
|
|
10
|
+
import type { ScalarPlanNode } from '../nodes/plan-node.js';
|
|
11
|
+
import * as AST from '../../parser/ast.js';
|
|
12
|
+
import { validateDeterministicConstraint } from '../validation/determinism-validator.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Determines if a constraint should be checked for the given operation
|
|
16
|
+
*/
|
|
17
|
+
function shouldCheckConstraint(constraint: RowConstraintSchema, operation: RowOpFlag): boolean {
|
|
18
|
+
// Check if the current operation is in the constraint's operations bitmask
|
|
19
|
+
return (constraint.operations & operation) !== 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds constraint check expressions at plan time.
|
|
24
|
+
* This allows the optimizer to see and optimize constraint expressions.
|
|
25
|
+
*/
|
|
26
|
+
export function buildConstraintChecks(
|
|
27
|
+
ctx: PlanningContext,
|
|
28
|
+
tableSchema: TableSchema,
|
|
29
|
+
operation: RowOpFlag,
|
|
30
|
+
oldAttributes: Attribute[],
|
|
31
|
+
newAttributes: Attribute[],
|
|
32
|
+
_flatRowDescriptor: RowDescriptor,
|
|
33
|
+
contextAttributes: Attribute[] = []
|
|
34
|
+
): ConstraintCheck[] {
|
|
35
|
+
// Build attribute ID mappings for column registration
|
|
36
|
+
const newAttrIdByCol: Record<string, number> = {};
|
|
37
|
+
const oldAttrIdByCol: Record<string, number> = {};
|
|
38
|
+
|
|
39
|
+
newAttributes.forEach((attr, columnIndex) => {
|
|
40
|
+
if (columnIndex < tableSchema.columns.length) {
|
|
41
|
+
const column = tableSchema.columns[columnIndex];
|
|
42
|
+
newAttrIdByCol[column.name.toLowerCase()] = attr.id;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
oldAttributes.forEach((attr, columnIndex) => {
|
|
47
|
+
if (columnIndex < tableSchema.columns.length) {
|
|
48
|
+
const column = tableSchema.columns[columnIndex];
|
|
49
|
+
oldAttrIdByCol[column.name.toLowerCase()] = attr.id;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Filter constraints by operation
|
|
54
|
+
const applicableConstraints = tableSchema.checkConstraints
|
|
55
|
+
.filter(constraint => shouldCheckConstraint(constraint, operation));
|
|
56
|
+
|
|
57
|
+
// Build expression nodes for each constraint
|
|
58
|
+
return applicableConstraints.map(constraint => {
|
|
59
|
+
// Create scope with OLD/NEW column access for constraint evaluation
|
|
60
|
+
const constraintScope = new RegisteredScope(ctx.scope);
|
|
61
|
+
|
|
62
|
+
// Register mutation context variables FIRST (so they shadow column names if conflicts exist)
|
|
63
|
+
contextAttributes.forEach((attr, contextVarIndex) => {
|
|
64
|
+
if (contextVarIndex < (tableSchema.mutationContext?.length || 0)) {
|
|
65
|
+
const contextVar = tableSchema.mutationContext![contextVarIndex];
|
|
66
|
+
const varNameLower = contextVar.name.toLowerCase();
|
|
67
|
+
|
|
68
|
+
// Register both unqualified and qualified names
|
|
69
|
+
constraintScope.subscribeFactory(varNameLower, (exp, s) =>
|
|
70
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, contextVarIndex)
|
|
71
|
+
);
|
|
72
|
+
constraintScope.subscribeFactory(`context.${varNameLower}`, (exp, s) =>
|
|
73
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, contextVarIndex)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Register column symbols (similar to current emitConstraintCheck logic)
|
|
79
|
+
tableSchema.columns.forEach((tableColumn, tableColIndex) => {
|
|
80
|
+
const colNameLower = tableColumn.name.toLowerCase();
|
|
81
|
+
|
|
82
|
+
// Register NEW.col and unqualified col (defaults to NEW for INSERT/UPDATE, OLD for DELETE)
|
|
83
|
+
const newAttrId = newAttrIdByCol[colNameLower];
|
|
84
|
+
if (newAttrId !== undefined) {
|
|
85
|
+
const newColumnType = {
|
|
86
|
+
typeClass: 'scalar' as const,
|
|
87
|
+
logicalType: tableColumn.logicalType,
|
|
88
|
+
nullable: !tableColumn.notNull,
|
|
89
|
+
isReadOnly: false
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// NEW.column
|
|
93
|
+
constraintScope.registerSymbol(`new.${colNameLower}`, (exp, s) =>
|
|
94
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, newColumnType, newAttrId, tableColIndex));
|
|
95
|
+
|
|
96
|
+
// For INSERT/UPDATE, unqualified column defaults to NEW
|
|
97
|
+
if (operation === 1 || operation === 2) { // INSERT or UPDATE
|
|
98
|
+
constraintScope.registerSymbol(colNameLower, (exp, s) =>
|
|
99
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, newColumnType, newAttrId, tableColIndex));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Register OLD.col
|
|
104
|
+
const oldAttrId = oldAttrIdByCol[colNameLower];
|
|
105
|
+
if (oldAttrId !== undefined) {
|
|
106
|
+
const oldColumnType = {
|
|
107
|
+
typeClass: 'scalar' as const,
|
|
108
|
+
logicalType: tableColumn.logicalType,
|
|
109
|
+
nullable: true, // OLD values can be NULL (especially for INSERT)
|
|
110
|
+
isReadOnly: false
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// OLD.column
|
|
114
|
+
constraintScope.registerSymbol(`old.${colNameLower}`, (exp, s) =>
|
|
115
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldColumnType, oldAttrId, tableColIndex));
|
|
116
|
+
|
|
117
|
+
// For DELETE, unqualified column defaults to OLD
|
|
118
|
+
if (operation === 4) { // DELETE
|
|
119
|
+
constraintScope.registerSymbol(colNameLower, (exp, s) =>
|
|
120
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldColumnType, oldAttrId, tableColIndex));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Build the constraint expression using the specialized scope
|
|
126
|
+
// Temporarily set the current schema to match the table's schema
|
|
127
|
+
// This ensures unqualified table references in CHECK constraints resolve correctly
|
|
128
|
+
const originalCurrentSchema = ctx.schemaManager.getCurrentSchemaName();
|
|
129
|
+
const needsSchemaSwitch = tableSchema.schemaName !== originalCurrentSchema;
|
|
130
|
+
|
|
131
|
+
if (needsSchemaSwitch) {
|
|
132
|
+
ctx.schemaManager.setCurrentSchema(tableSchema.schemaName);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const expression = buildExpression(
|
|
137
|
+
{ ...ctx, scope: constraintScope },
|
|
138
|
+
constraint.expr
|
|
139
|
+
) as ScalarPlanNode;
|
|
140
|
+
|
|
141
|
+
// Validate that the constraint expression is deterministic
|
|
142
|
+
const constraintName = constraint.name ?? `_check_${tableSchema.name}`;
|
|
143
|
+
validateDeterministicConstraint(expression, constraintName, tableSchema.name);
|
|
144
|
+
|
|
145
|
+
// Heuristic: auto-defer if the expression contains a subquery
|
|
146
|
+
// or references a different relation via attribute bindings (NEW/OLD already localized).
|
|
147
|
+
const needsDeferred = containsSubquery(expression);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
constraint,
|
|
151
|
+
expression,
|
|
152
|
+
deferrable: needsDeferred,
|
|
153
|
+
initiallyDeferred: needsDeferred,
|
|
154
|
+
containsSubquery: needsDeferred
|
|
155
|
+
} satisfies ConstraintCheck;
|
|
156
|
+
} finally {
|
|
157
|
+
// Restore original schema context
|
|
158
|
+
if (needsSchemaSwitch) {
|
|
159
|
+
ctx.schemaManager.setCurrentSchema(originalCurrentSchema);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function containsSubquery(expr: ScalarPlanNode): boolean {
|
|
166
|
+
const stack: ScalarPlanNode[] = [expr];
|
|
167
|
+
while (stack.length) {
|
|
168
|
+
const n = stack.pop()!;
|
|
169
|
+
if (n.nodeType === PlanNodeType.ScalarSubquery || n.nodeType === PlanNodeType.Exists) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
for (const c of n.getChildren()) {
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
stack.push(c as any);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
@@ -148,7 +148,11 @@ export function buildDeleteStmt(
|
|
|
148
148
|
deleteCtx.scope,
|
|
149
149
|
deleteNode,
|
|
150
150
|
tableReference,
|
|
151
|
-
'delete'
|
|
151
|
+
'delete',
|
|
152
|
+
undefined, // onConflict not used for DELETE
|
|
153
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
154
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
155
|
+
contextDescriptor
|
|
152
156
|
);
|
|
153
157
|
|
|
154
158
|
const resultNode: RelationalPlanNode = dmlExecutorNode;
|
|
@@ -22,6 +22,7 @@ import { ProjectNode, type Projection } from '../nodes/project-node.js';
|
|
|
22
22
|
import { buildOldNewRowDescriptors } from '../../util/row-descriptor.js';
|
|
23
23
|
import { DmlExecutorNode } from '../nodes/dml-executor-node.js';
|
|
24
24
|
import { buildConstraintChecks } from './constraint-builder.js';
|
|
25
|
+
import { validateDeterministicDefault } from '../validation/determinism-validator.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Creates a uniform row expansion projection that maps any relational source
|
|
@@ -103,6 +104,9 @@ function createRowExpansionProjection(
|
|
|
103
104
|
if (typeof tableColumn.defaultValue === 'object' && tableColumn.defaultValue !== null && 'type' in tableColumn.defaultValue) {
|
|
104
105
|
// It's an AST.Expression - build it into a plan node with context scope
|
|
105
106
|
defaultNode = buildExpression(defaultCtx, tableColumn.defaultValue as AST.Expression) as ScalarPlanNode;
|
|
107
|
+
|
|
108
|
+
// Validate that the default expression is deterministic
|
|
109
|
+
validateDeterministicDefault(defaultNode, tableColumn.name, tableSchema.name);
|
|
106
110
|
} else {
|
|
107
111
|
// Literal default value
|
|
108
112
|
defaultNode = buildExpression(defaultCtx, { type: 'literal', value: tableColumn.defaultValue }) as ScalarPlanNode;
|
|
@@ -358,7 +362,10 @@ export function buildInsertStmt(
|
|
|
358
362
|
constraintCheckNode,
|
|
359
363
|
tableReference,
|
|
360
364
|
'insert',
|
|
361
|
-
stmt.onConflict
|
|
365
|
+
stmt.onConflict,
|
|
366
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
367
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
368
|
+
contextDescriptor
|
|
362
369
|
);
|
|
363
370
|
|
|
364
371
|
const resultNode: RelationalPlanNode = dmlExecutorNode;
|
|
@@ -270,7 +270,11 @@ export function buildUpdateStmt(
|
|
|
270
270
|
updateCtx.scope,
|
|
271
271
|
constraintCheckNode,
|
|
272
272
|
tableReference,
|
|
273
|
-
'update'
|
|
273
|
+
'update',
|
|
274
|
+
undefined, // onConflict not used for UPDATE
|
|
275
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
276
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
277
|
+
contextDescriptor
|
|
274
278
|
);
|
|
275
279
|
|
|
276
280
|
// Return the RETURNING results from the executed update
|
|
@@ -312,7 +316,11 @@ export function buildUpdateStmt(
|
|
|
312
316
|
updateCtx.scope,
|
|
313
317
|
constraintCheckNode,
|
|
314
318
|
tableReference,
|
|
315
|
-
'update'
|
|
319
|
+
'update',
|
|
320
|
+
undefined, // onConflict not used for UPDATE
|
|
321
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
322
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
323
|
+
contextDescriptor
|
|
316
324
|
);
|
|
317
325
|
|
|
318
326
|
return new SinkNode(updateCtx.scope, updateExecutorNode, 'update');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Scope } from '../scopes/scope.js';
|
|
2
|
-
import { PlanNode, type RelationalPlanNode, type Attribute, type PhysicalProperties, isRelationalNode } from './plan-node.js';
|
|
2
|
+
import { PlanNode, type RelationalPlanNode, type Attribute, type PhysicalProperties, type ScalarPlanNode, type RowDescriptor, isRelationalNode } from './plan-node.js';
|
|
3
3
|
import { PlanNodeType } from './plan-node-type.js';
|
|
4
4
|
import type { TableReferenceNode } from './reference.js';
|
|
5
5
|
import type { RelationType } from '../../common/datatype.js';
|
|
@@ -20,6 +20,9 @@ export class DmlExecutorNode extends PlanNode implements RelationalPlanNode {
|
|
|
20
20
|
public readonly table: TableReferenceNode,
|
|
21
21
|
public readonly operation: RowOp,
|
|
22
22
|
public readonly onConflict?: ConflictResolution, // Used for INSERT operations
|
|
23
|
+
public readonly mutationContextValues?: Map<string, ScalarPlanNode>, // Mutation context value expressions
|
|
24
|
+
public readonly contextAttributes?: Attribute[], // Mutation context attributes
|
|
25
|
+
public readonly contextDescriptor?: RowDescriptor, // Mutation context row descriptor
|
|
23
26
|
) {
|
|
24
27
|
super(scope);
|
|
25
28
|
}
|
|
@@ -63,7 +66,10 @@ export class DmlExecutorNode extends PlanNode implements RelationalPlanNode {
|
|
|
63
66
|
newSource as RelationalPlanNode,
|
|
64
67
|
this.table,
|
|
65
68
|
this.operation,
|
|
66
|
-
this.onConflict
|
|
69
|
+
this.onConflict,
|
|
70
|
+
this.mutationContextValues,
|
|
71
|
+
this.contextAttributes,
|
|
72
|
+
this.contextDescriptor
|
|
67
73
|
);
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { QuereusError } from '../../common/errors.js';
|
|
2
|
+
import { StatusCode } from '../../common/types.js';
|
|
3
|
+
import type { ScalarPlanNode } from '../nodes/plan-node.js';
|
|
4
|
+
import { createLogger } from '../../common/logger.js';
|
|
5
|
+
|
|
6
|
+
const log = createLogger('planner:validation:determinism');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result of determinism validation. If valid, `error` is undefined.
|
|
10
|
+
* If invalid, contains the information needed to construct an error message.
|
|
11
|
+
*/
|
|
12
|
+
export interface DeterminismValidationResult {
|
|
13
|
+
/** True if the expression is deterministic */
|
|
14
|
+
valid: boolean;
|
|
15
|
+
/** String representation of the offending expression (if invalid) */
|
|
16
|
+
expression?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks if an expression is deterministic (suitable for constraints and defaults).
|
|
21
|
+
* Returns a result object instead of throwing, allowing the caller to decide how to handle.
|
|
22
|
+
*
|
|
23
|
+
* @param expr The expression plan node to check
|
|
24
|
+
* @returns Validation result indicating if deterministic
|
|
25
|
+
*/
|
|
26
|
+
export function checkDeterministic(expr: ScalarPlanNode): DeterminismValidationResult {
|
|
27
|
+
const physical = expr.physical;
|
|
28
|
+
|
|
29
|
+
if (physical.deterministic === false) {
|
|
30
|
+
log('Non-deterministic expression detected: %s', expr.toString());
|
|
31
|
+
return {
|
|
32
|
+
valid: false,
|
|
33
|
+
expression: expr.toString()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { valid: true };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validates that an expression is deterministic (suitable for constraints and defaults).
|
|
42
|
+
* Non-deterministic expressions must be passed via mutation context instead.
|
|
43
|
+
*
|
|
44
|
+
* @param expr The expression plan node to validate
|
|
45
|
+
* @param context Description of where the expression is used (e.g., "DEFAULT for column 'created_at'")
|
|
46
|
+
* @throws QuereusError if the expression is non-deterministic
|
|
47
|
+
*/
|
|
48
|
+
export function validateDeterministicExpression(
|
|
49
|
+
expr: ScalarPlanNode,
|
|
50
|
+
context: string
|
|
51
|
+
): void {
|
|
52
|
+
log('Validating determinism for: %s', context);
|
|
53
|
+
|
|
54
|
+
const result = checkDeterministic(expr);
|
|
55
|
+
|
|
56
|
+
if (!result.valid) {
|
|
57
|
+
throw new QuereusError(
|
|
58
|
+
`Non-deterministic expression not allowed in ${context}. ` +
|
|
59
|
+
`Expression: ${result.expression}. ` +
|
|
60
|
+
`Use mutation context to pass non-deterministic values (e.g., WITH CONTEXT (timestamp = datetime('now'))).`,
|
|
61
|
+
StatusCode.ERROR
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
log('Expression is deterministic: %s', expr.toString());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validates that a CHECK constraint expression is deterministic.
|
|
70
|
+
*
|
|
71
|
+
* @param expr The constraint expression plan node
|
|
72
|
+
* @param constraintName The name of the constraint (for error messages)
|
|
73
|
+
* @param tableName The name of the table (for error messages)
|
|
74
|
+
* @throws QuereusError if the expression is non-deterministic
|
|
75
|
+
*/
|
|
76
|
+
export function validateDeterministicConstraint(
|
|
77
|
+
expr: ScalarPlanNode,
|
|
78
|
+
constraintName: string,
|
|
79
|
+
tableName: string
|
|
80
|
+
): void {
|
|
81
|
+
validateDeterministicExpression(
|
|
82
|
+
expr,
|
|
83
|
+
`CHECK constraint '${constraintName}' on table '${tableName}'`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates that a DEFAULT expression is deterministic.
|
|
89
|
+
*
|
|
90
|
+
* @param expr The default value expression plan node
|
|
91
|
+
* @param columnName The name of the column (for error messages)
|
|
92
|
+
* @param tableName The name of the table (for error messages)
|
|
93
|
+
* @throws QuereusError if the expression is non-deterministic
|
|
94
|
+
*/
|
|
95
|
+
export function validateDeterministicDefault(
|
|
96
|
+
expr: ScalarPlanNode,
|
|
97
|
+
columnName: string,
|
|
98
|
+
tableName: string
|
|
99
|
+
): void {
|
|
100
|
+
validateDeterministicExpression(
|
|
101
|
+
expr,
|
|
102
|
+
`DEFAULT for column '${columnName}' in table '${tableName}'`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { MaybePromise, Row } from '../common/types.js';
|
|
7
7
|
import { createLogger } from '../common/logger.js';
|
|
8
|
+
import { getAsyncIterator } from './utils.js';
|
|
8
9
|
|
|
9
10
|
const log = createLogger('runtime:async-util');
|
|
10
11
|
|
|
@@ -52,7 +53,7 @@ export function tee<T>(src: AsyncIterable<T>): [AsyncIterable<T>, AsyncIterable<
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
if (!srcIterator) {
|
|
55
|
-
srcIterator = src
|
|
56
|
+
srcIterator = getAsyncIterator(src);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
while (buffer.length <= targetIndex && !srcDone) {
|
|
@@ -124,7 +125,7 @@ export async function* buffered<T>(
|
|
|
124
125
|
maxBuffer: number
|
|
125
126
|
): AsyncIterable<T> {
|
|
126
127
|
const buffer: T[] = [];
|
|
127
|
-
const srcIterator = src
|
|
128
|
+
const srcIterator = getAsyncIterator(src);
|
|
128
129
|
let srcDone = false;
|
|
129
130
|
let fillPromise: Promise<void> | null = null;
|
|
130
131
|
|
|
@@ -217,7 +218,7 @@ export async function collect<T>(src: AsyncIterable<T>): Promise<T[]> {
|
|
|
217
218
|
* Items are yielded as soon as they become available from any source
|
|
218
219
|
*/
|
|
219
220
|
export async function* merge<T>(...sources: AsyncIterable<T>[]): AsyncIterable<T> {
|
|
220
|
-
const iterators = sources.map(src => src
|
|
221
|
+
const iterators = sources.map(src => getAsyncIterator(src));
|
|
221
222
|
const pending = new Map<number, Promise<IteratorResult<T>>>();
|
|
222
223
|
|
|
223
224
|
// Start initial reads
|
|
@@ -31,6 +31,9 @@ export function emitAddConstraint(plan: AddConstraintNode, _ctx: EmissionContext
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Create the constraint schema object
|
|
34
|
+
// Note: We don't validate determinism here because constraints may reference NEW/OLD
|
|
35
|
+
// which require special scoping. Determinism is validated at INSERT/UPDATE plan time
|
|
36
|
+
// in constraint-builder.ts when the constraint is actually checked.
|
|
34
37
|
const constraintSchema: RowConstraintSchema = {
|
|
35
38
|
name: constraint.name || `check_${tableSchema.checkConstraints.length}`,
|
|
36
39
|
expr: constraint.expr,
|