@quereus/quereus 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/common/type-inference.d.ts +9 -1
- package/dist/src/common/type-inference.d.ts.map +1 -1
- package/dist/src/common/type-inference.js +11 -3
- package/dist/src/common/type-inference.js.map +1 -1
- package/dist/src/core/database.d.ts +27 -1
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +39 -6
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/param.d.ts +17 -1
- package/dist/src/core/param.d.ts.map +1 -1
- package/dist/src/core/param.js +23 -1
- package/dist/src/core/param.js.map +1 -1
- package/dist/src/core/statement.d.ts +10 -1
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +71 -5
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/planner/scopes/param.d.ts +2 -2
- package/dist/src/planner/scopes/param.d.ts.map +1 -1
- package/dist/src/planner/scopes/param.js +9 -9
- package/dist/src/planner/scopes/param.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +1 -1
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/schema/schema-hasher.d.ts +3 -3
- package/dist/src/schema/schema-hasher.d.ts.map +1 -1
- package/dist/src/schema/schema-hasher.js +9 -27
- package/dist/src/schema/schema-hasher.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/logical-type.d.ts +5 -0
- package/dist/src/types/logical-type.d.ts.map +1 -1
- package/dist/src/types/logical-type.js +15 -0
- package/dist/src/types/logical-type.js.map +1 -1
- package/dist/src/util/hash.d.ts +19 -0
- package/dist/src/util/hash.d.ts.map +1 -0
- package/dist/src/util/hash.js +76 -0
- package/dist/src/util/hash.js.map +1 -0
- package/package.json +1 -1
- package/src/common/type-inference.ts +11 -3
- package/src/core/database.ts +41 -6
- package/src/core/param.ts +23 -1
- package/src/core/statement.ts +89 -5
- package/src/planner/building/delete.ts +214 -214
- package/src/planner/building/insert.ts +428 -428
- package/src/planner/building/update.ts +319 -319
- package/src/planner/scopes/param.ts +9 -9
- package/src/runtime/emit/schema-declarative.ts +1 -1
- package/src/schema/schema-hasher.ts +9 -27
- package/src/types/index.ts +1 -1
- package/src/types/logical-type.ts +16 -0
- package/src/util/ast-stringify.ts +864 -864
- package/src/util/hash.ts +90 -0
- package/src/vtab/memory/table.ts +256 -256
- package/src/vtab/table.ts +162 -162
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
import type * as AST from '../../parser/ast.js';
|
|
2
|
-
import type { PlanningContext } from '../planning-context.js';
|
|
3
|
-
import { DeleteNode } from '../nodes/delete-node.js';
|
|
4
|
-
import { buildTableReference } from './table.js';
|
|
5
|
-
import { buildExpression } from './expression.js';
|
|
6
|
-
import { PlanNode, type RelationalPlanNode, type ScalarPlanNode, type Attribute, type RowDescriptor } from '../nodes/plan-node.js';
|
|
7
|
-
import { FilterNode } from '../nodes/filter.js';
|
|
8
|
-
import { QuereusError } from '../../common/errors.js';
|
|
9
|
-
import { StatusCode } from '../../common/types.js';
|
|
10
|
-
import { RegisteredScope } from '../scopes/registered.js';
|
|
11
|
-
import { ColumnReferenceNode } from '../nodes/reference.js';
|
|
12
|
-
import { SinkNode } from '../nodes/sink-node.js';
|
|
13
|
-
import { ConstraintCheckNode } from '../nodes/constraint-check-node.js';
|
|
14
|
-
import { RowOpFlag } from '../../schema/table.js';
|
|
15
|
-
import { ReturningNode } from '../nodes/returning-node.js';
|
|
16
|
-
import { buildOldNewRowDescriptors } from '../../util/row-descriptor.js';
|
|
17
|
-
import { DmlExecutorNode } from '../nodes/dml-executor-node.js';
|
|
18
|
-
import { buildConstraintChecks } from './constraint-builder.js';
|
|
19
|
-
|
|
20
|
-
export function buildDeleteStmt(
|
|
21
|
-
ctx: PlanningContext,
|
|
22
|
-
stmt: AST.DeleteStmt,
|
|
23
|
-
): PlanNode {
|
|
24
|
-
const tableRetrieve = buildTableReference({ type: 'table', table: stmt.table }, ctx);
|
|
25
|
-
const tableReference = tableRetrieve.tableRef; // Extract the actual TableReferenceNode
|
|
26
|
-
|
|
27
|
-
// Process mutation context assignments if present
|
|
28
|
-
const mutationContextValues = new Map<string, ScalarPlanNode>();
|
|
29
|
-
const contextAttributes: Attribute[] = [];
|
|
30
|
-
|
|
31
|
-
if (stmt.contextValues && tableReference.tableSchema.mutationContext) {
|
|
32
|
-
// Create context attributes
|
|
33
|
-
tableReference.tableSchema.mutationContext.forEach((contextVar) => {
|
|
34
|
-
contextAttributes.push({
|
|
35
|
-
id: PlanNode.nextAttrId(),
|
|
36
|
-
name: contextVar.name,
|
|
37
|
-
type: {
|
|
38
|
-
typeClass: 'scalar' as const,
|
|
39
|
-
logicalType: contextVar.logicalType,
|
|
40
|
-
nullable: !contextVar.notNull,
|
|
41
|
-
isReadOnly: true
|
|
42
|
-
},
|
|
43
|
-
sourceRelation: `context.${tableReference.tableSchema.name}`
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Build context value expressions (evaluated in the base scope, before table scope)
|
|
48
|
-
stmt.contextValues.forEach((assignment) => {
|
|
49
|
-
const valueExpr = buildExpression(ctx, assignment.value) as ScalarPlanNode;
|
|
50
|
-
mutationContextValues.set(assignment.name, valueExpr);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Plan the source of rows to delete. This is typically the table itself, potentially filtered.
|
|
55
|
-
let sourceNode: RelationalPlanNode = tableRetrieve; // Use the RetrieveNode as source
|
|
56
|
-
|
|
57
|
-
// Create a new scope with the table columns registered for column resolution
|
|
58
|
-
const tableScope = new RegisteredScope(ctx.scope);
|
|
59
|
-
const sourceAttributes = sourceNode.getAttributes();
|
|
60
|
-
sourceNode.getType().columns.forEach((c, i) => {
|
|
61
|
-
const attr = sourceAttributes[i];
|
|
62
|
-
tableScope.registerSymbol(c.name.toLowerCase(), (exp, s) =>
|
|
63
|
-
new ColumnReferenceNode(s, exp as AST.ColumnExpr, c.type, attr.id, i));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Create a new planning context with the updated scope for WHERE clause resolution
|
|
67
|
-
const deleteCtx = { ...ctx, scope: tableScope };
|
|
68
|
-
|
|
69
|
-
if (stmt.where) {
|
|
70
|
-
const filterExpression = buildExpression(deleteCtx, stmt.where);
|
|
71
|
-
sourceNode = new FilterNode(deleteCtx.scope, sourceNode, filterExpression);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Create OLD/NEW attributes for DELETE (OLD = actual values being deleted, NEW = all NULL)
|
|
75
|
-
const oldAttributes = tableReference.tableSchema.columns.map((col: any) => ({
|
|
76
|
-
id: PlanNode.nextAttrId(),
|
|
77
|
-
name: col.name,
|
|
78
|
-
type: {
|
|
79
|
-
typeClass: 'scalar' as const,
|
|
80
|
-
logicalType: col.logicalType,
|
|
81
|
-
nullable: !col.notNull,
|
|
82
|
-
isReadOnly: false
|
|
83
|
-
},
|
|
84
|
-
sourceRelation: `OLD.${tableReference.tableSchema.name}`
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
const newAttributes = tableReference.tableSchema.columns.map((col: any) => ({
|
|
88
|
-
id: PlanNode.nextAttrId(),
|
|
89
|
-
name: col.name,
|
|
90
|
-
type: {
|
|
91
|
-
typeClass: 'scalar' as const,
|
|
92
|
-
logicalType: col.logicalType,
|
|
93
|
-
nullable: true, // NEW values are always NULL for DELETE
|
|
94
|
-
isReadOnly: false
|
|
95
|
-
},
|
|
96
|
-
sourceRelation: `NEW.${tableReference.tableSchema.name}`
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
|
-
const { oldRowDescriptor, newRowDescriptor, flatRowDescriptor } = buildOldNewRowDescriptors(oldAttributes, newAttributes);
|
|
100
|
-
|
|
101
|
-
// Build context descriptor if we have context attributes
|
|
102
|
-
const contextDescriptor: RowDescriptor = contextAttributes.length > 0 ? [] : undefined as any;
|
|
103
|
-
if (contextDescriptor) {
|
|
104
|
-
contextAttributes.forEach((attr, index) => {
|
|
105
|
-
contextDescriptor[attr.id] = index;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Build constraint checks at plan time
|
|
110
|
-
const constraintChecks = buildConstraintChecks(
|
|
111
|
-
deleteCtx,
|
|
112
|
-
tableReference.tableSchema,
|
|
113
|
-
RowOpFlag.DELETE,
|
|
114
|
-
oldAttributes,
|
|
115
|
-
newAttributes,
|
|
116
|
-
flatRowDescriptor,
|
|
117
|
-
contextAttributes
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
// Always inject ConstraintCheckNode for DELETE operations
|
|
121
|
-
const constraintCheckNode = new ConstraintCheckNode(
|
|
122
|
-
deleteCtx.scope,
|
|
123
|
-
sourceNode,
|
|
124
|
-
tableReference,
|
|
125
|
-
RowOpFlag.DELETE,
|
|
126
|
-
oldRowDescriptor,
|
|
127
|
-
newRowDescriptor,
|
|
128
|
-
flatRowDescriptor,
|
|
129
|
-
constraintChecks,
|
|
130
|
-
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
131
|
-
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
132
|
-
contextDescriptor
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const deleteNode = new DeleteNode(
|
|
136
|
-
deleteCtx.scope,
|
|
137
|
-
tableReference,
|
|
138
|
-
constraintCheckNode,
|
|
139
|
-
oldRowDescriptor,
|
|
140
|
-
flatRowDescriptor,
|
|
141
|
-
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
142
|
-
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
143
|
-
contextDescriptor
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Add DML executor node to perform the actual database delete operations
|
|
147
|
-
const dmlExecutorNode = new DmlExecutorNode(
|
|
148
|
-
deleteCtx.scope,
|
|
149
|
-
deleteNode,
|
|
150
|
-
tableReference,
|
|
151
|
-
'delete'
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const resultNode: RelationalPlanNode = dmlExecutorNode;
|
|
155
|
-
|
|
156
|
-
if (stmt.returning && stmt.returning.length > 0) {
|
|
157
|
-
// Create returning scope with OLD/NEW attribute access
|
|
158
|
-
const returningScope = new RegisteredScope(deleteCtx.scope);
|
|
159
|
-
|
|
160
|
-
// Register OLD.* symbols (actual values being deleted)
|
|
161
|
-
oldAttributes.forEach((attr: any, columnIndex: any) => {
|
|
162
|
-
const tableColumn = tableReference.tableSchema.columns[columnIndex];
|
|
163
|
-
returningScope.registerSymbol(`old.${tableColumn.name.toLowerCase()}`, (exp, s) =>
|
|
164
|
-
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, columnIndex)
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Register NEW.* symbols (always NULL for DELETE) and unqualified column names (default to OLD for DELETE)
|
|
169
|
-
newAttributes.forEach((attr: any, columnIndex: any) => {
|
|
170
|
-
const tableColumn = tableReference.tableSchema.columns[columnIndex];
|
|
171
|
-
|
|
172
|
-
// NEW.column (always NULL for DELETE)
|
|
173
|
-
returningScope.registerSymbol(`new.${tableColumn.name.toLowerCase()}`, (exp, s) =>
|
|
174
|
-
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, columnIndex)
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// Unqualified column (defaults to OLD for DELETE)
|
|
178
|
-
const oldAttr = oldAttributes[columnIndex];
|
|
179
|
-
returningScope.registerSymbol(tableColumn.name.toLowerCase(), (exp, s) =>
|
|
180
|
-
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldAttr.type, oldAttr.id, columnIndex)
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
// Table-qualified form (table.column -> OLD for DELETE)
|
|
184
|
-
const tblQualified = `${tableReference.tableSchema.name.toLowerCase()}.${tableColumn.name.toLowerCase()}`;
|
|
185
|
-
returningScope.registerSymbol(tblQualified, (exp, s) =>
|
|
186
|
-
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldAttr.type, oldAttr.id, columnIndex)
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Build RETURNING projections in the OLD/NEW context
|
|
191
|
-
const returningProjections = stmt.returning.map(rc => {
|
|
192
|
-
// TODO: Support RETURNING *
|
|
193
|
-
if (rc.type === 'all') throw new QuereusError('RETURNING * not yet supported', StatusCode.UNSUPPORTED);
|
|
194
|
-
|
|
195
|
-
// Infer alias from column name if not explicitly provided
|
|
196
|
-
let alias = rc.alias;
|
|
197
|
-
if (!alias && rc.expr.type === 'column') {
|
|
198
|
-
// For qualified column references like OLD.id, normalize to lowercase
|
|
199
|
-
alias = rc.expr.table
|
|
200
|
-
? `${rc.expr.table.toLowerCase()}.${rc.expr.name.toLowerCase()}`
|
|
201
|
-
: rc.expr.name.toLowerCase();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
node: buildExpression({ ...deleteCtx, scope: returningScope }, rc.expr) as ScalarPlanNode,
|
|
206
|
-
alias: alias
|
|
207
|
-
};
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
return new ReturningNode(deleteCtx.scope, dmlExecutorNode, returningProjections);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return new SinkNode(deleteCtx.scope, resultNode, 'delete');
|
|
214
|
-
}
|
|
1
|
+
import type * as AST from '../../parser/ast.js';
|
|
2
|
+
import type { PlanningContext } from '../planning-context.js';
|
|
3
|
+
import { DeleteNode } from '../nodes/delete-node.js';
|
|
4
|
+
import { buildTableReference } from './table.js';
|
|
5
|
+
import { buildExpression } from './expression.js';
|
|
6
|
+
import { PlanNode, type RelationalPlanNode, type ScalarPlanNode, type Attribute, type RowDescriptor } from '../nodes/plan-node.js';
|
|
7
|
+
import { FilterNode } from '../nodes/filter.js';
|
|
8
|
+
import { QuereusError } from '../../common/errors.js';
|
|
9
|
+
import { StatusCode } from '../../common/types.js';
|
|
10
|
+
import { RegisteredScope } from '../scopes/registered.js';
|
|
11
|
+
import { ColumnReferenceNode } from '../nodes/reference.js';
|
|
12
|
+
import { SinkNode } from '../nodes/sink-node.js';
|
|
13
|
+
import { ConstraintCheckNode } from '../nodes/constraint-check-node.js';
|
|
14
|
+
import { RowOpFlag } from '../../schema/table.js';
|
|
15
|
+
import { ReturningNode } from '../nodes/returning-node.js';
|
|
16
|
+
import { buildOldNewRowDescriptors } from '../../util/row-descriptor.js';
|
|
17
|
+
import { DmlExecutorNode } from '../nodes/dml-executor-node.js';
|
|
18
|
+
import { buildConstraintChecks } from './constraint-builder.js';
|
|
19
|
+
|
|
20
|
+
export function buildDeleteStmt(
|
|
21
|
+
ctx: PlanningContext,
|
|
22
|
+
stmt: AST.DeleteStmt,
|
|
23
|
+
): PlanNode {
|
|
24
|
+
const tableRetrieve = buildTableReference({ type: 'table', table: stmt.table }, ctx);
|
|
25
|
+
const tableReference = tableRetrieve.tableRef; // Extract the actual TableReferenceNode
|
|
26
|
+
|
|
27
|
+
// Process mutation context assignments if present
|
|
28
|
+
const mutationContextValues = new Map<string, ScalarPlanNode>();
|
|
29
|
+
const contextAttributes: Attribute[] = [];
|
|
30
|
+
|
|
31
|
+
if (stmt.contextValues && tableReference.tableSchema.mutationContext) {
|
|
32
|
+
// Create context attributes
|
|
33
|
+
tableReference.tableSchema.mutationContext.forEach((contextVar) => {
|
|
34
|
+
contextAttributes.push({
|
|
35
|
+
id: PlanNode.nextAttrId(),
|
|
36
|
+
name: contextVar.name,
|
|
37
|
+
type: {
|
|
38
|
+
typeClass: 'scalar' as const,
|
|
39
|
+
logicalType: contextVar.logicalType,
|
|
40
|
+
nullable: !contextVar.notNull,
|
|
41
|
+
isReadOnly: true
|
|
42
|
+
},
|
|
43
|
+
sourceRelation: `context.${tableReference.tableSchema.name}`
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Build context value expressions (evaluated in the base scope, before table scope)
|
|
48
|
+
stmt.contextValues.forEach((assignment) => {
|
|
49
|
+
const valueExpr = buildExpression(ctx, assignment.value) as ScalarPlanNode;
|
|
50
|
+
mutationContextValues.set(assignment.name, valueExpr);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Plan the source of rows to delete. This is typically the table itself, potentially filtered.
|
|
55
|
+
let sourceNode: RelationalPlanNode = tableRetrieve; // Use the RetrieveNode as source
|
|
56
|
+
|
|
57
|
+
// Create a new scope with the table columns registered for column resolution
|
|
58
|
+
const tableScope = new RegisteredScope(ctx.scope);
|
|
59
|
+
const sourceAttributes = sourceNode.getAttributes();
|
|
60
|
+
sourceNode.getType().columns.forEach((c, i) => {
|
|
61
|
+
const attr = sourceAttributes[i];
|
|
62
|
+
tableScope.registerSymbol(c.name.toLowerCase(), (exp, s) =>
|
|
63
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, c.type, attr.id, i));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Create a new planning context with the updated scope for WHERE clause resolution
|
|
67
|
+
const deleteCtx = { ...ctx, scope: tableScope };
|
|
68
|
+
|
|
69
|
+
if (stmt.where) {
|
|
70
|
+
const filterExpression = buildExpression(deleteCtx, stmt.where);
|
|
71
|
+
sourceNode = new FilterNode(deleteCtx.scope, sourceNode, filterExpression);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create OLD/NEW attributes for DELETE (OLD = actual values being deleted, NEW = all NULL)
|
|
75
|
+
const oldAttributes = tableReference.tableSchema.columns.map((col: any) => ({
|
|
76
|
+
id: PlanNode.nextAttrId(),
|
|
77
|
+
name: col.name,
|
|
78
|
+
type: {
|
|
79
|
+
typeClass: 'scalar' as const,
|
|
80
|
+
logicalType: col.logicalType,
|
|
81
|
+
nullable: !col.notNull,
|
|
82
|
+
isReadOnly: false
|
|
83
|
+
},
|
|
84
|
+
sourceRelation: `OLD.${tableReference.tableSchema.name}`
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
const newAttributes = tableReference.tableSchema.columns.map((col: any) => ({
|
|
88
|
+
id: PlanNode.nextAttrId(),
|
|
89
|
+
name: col.name,
|
|
90
|
+
type: {
|
|
91
|
+
typeClass: 'scalar' as const,
|
|
92
|
+
logicalType: col.logicalType,
|
|
93
|
+
nullable: true, // NEW values are always NULL for DELETE
|
|
94
|
+
isReadOnly: false
|
|
95
|
+
},
|
|
96
|
+
sourceRelation: `NEW.${tableReference.tableSchema.name}`
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const { oldRowDescriptor, newRowDescriptor, flatRowDescriptor } = buildOldNewRowDescriptors(oldAttributes, newAttributes);
|
|
100
|
+
|
|
101
|
+
// Build context descriptor if we have context attributes
|
|
102
|
+
const contextDescriptor: RowDescriptor = contextAttributes.length > 0 ? [] : undefined as any;
|
|
103
|
+
if (contextDescriptor) {
|
|
104
|
+
contextAttributes.forEach((attr, index) => {
|
|
105
|
+
contextDescriptor[attr.id] = index;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Build constraint checks at plan time
|
|
110
|
+
const constraintChecks = buildConstraintChecks(
|
|
111
|
+
deleteCtx,
|
|
112
|
+
tableReference.tableSchema,
|
|
113
|
+
RowOpFlag.DELETE,
|
|
114
|
+
oldAttributes,
|
|
115
|
+
newAttributes,
|
|
116
|
+
flatRowDescriptor,
|
|
117
|
+
contextAttributes
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Always inject ConstraintCheckNode for DELETE operations
|
|
121
|
+
const constraintCheckNode = new ConstraintCheckNode(
|
|
122
|
+
deleteCtx.scope,
|
|
123
|
+
sourceNode,
|
|
124
|
+
tableReference,
|
|
125
|
+
RowOpFlag.DELETE,
|
|
126
|
+
oldRowDescriptor,
|
|
127
|
+
newRowDescriptor,
|
|
128
|
+
flatRowDescriptor,
|
|
129
|
+
constraintChecks,
|
|
130
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
131
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
132
|
+
contextDescriptor
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const deleteNode = new DeleteNode(
|
|
136
|
+
deleteCtx.scope,
|
|
137
|
+
tableReference,
|
|
138
|
+
constraintCheckNode,
|
|
139
|
+
oldRowDescriptor,
|
|
140
|
+
flatRowDescriptor,
|
|
141
|
+
mutationContextValues.size > 0 ? mutationContextValues : undefined,
|
|
142
|
+
contextAttributes.length > 0 ? contextAttributes : undefined,
|
|
143
|
+
contextDescriptor
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Add DML executor node to perform the actual database delete operations
|
|
147
|
+
const dmlExecutorNode = new DmlExecutorNode(
|
|
148
|
+
deleteCtx.scope,
|
|
149
|
+
deleteNode,
|
|
150
|
+
tableReference,
|
|
151
|
+
'delete'
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const resultNode: RelationalPlanNode = dmlExecutorNode;
|
|
155
|
+
|
|
156
|
+
if (stmt.returning && stmt.returning.length > 0) {
|
|
157
|
+
// Create returning scope with OLD/NEW attribute access
|
|
158
|
+
const returningScope = new RegisteredScope(deleteCtx.scope);
|
|
159
|
+
|
|
160
|
+
// Register OLD.* symbols (actual values being deleted)
|
|
161
|
+
oldAttributes.forEach((attr: any, columnIndex: any) => {
|
|
162
|
+
const tableColumn = tableReference.tableSchema.columns[columnIndex];
|
|
163
|
+
returningScope.registerSymbol(`old.${tableColumn.name.toLowerCase()}`, (exp, s) =>
|
|
164
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, columnIndex)
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Register NEW.* symbols (always NULL for DELETE) and unqualified column names (default to OLD for DELETE)
|
|
169
|
+
newAttributes.forEach((attr: any, columnIndex: any) => {
|
|
170
|
+
const tableColumn = tableReference.tableSchema.columns[columnIndex];
|
|
171
|
+
|
|
172
|
+
// NEW.column (always NULL for DELETE)
|
|
173
|
+
returningScope.registerSymbol(`new.${tableColumn.name.toLowerCase()}`, (exp, s) =>
|
|
174
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, attr.type, attr.id, columnIndex)
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Unqualified column (defaults to OLD for DELETE)
|
|
178
|
+
const oldAttr = oldAttributes[columnIndex];
|
|
179
|
+
returningScope.registerSymbol(tableColumn.name.toLowerCase(), (exp, s) =>
|
|
180
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldAttr.type, oldAttr.id, columnIndex)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Table-qualified form (table.column -> OLD for DELETE)
|
|
184
|
+
const tblQualified = `${tableReference.tableSchema.name.toLowerCase()}.${tableColumn.name.toLowerCase()}`;
|
|
185
|
+
returningScope.registerSymbol(tblQualified, (exp, s) =>
|
|
186
|
+
new ColumnReferenceNode(s, exp as AST.ColumnExpr, oldAttr.type, oldAttr.id, columnIndex)
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Build RETURNING projections in the OLD/NEW context
|
|
191
|
+
const returningProjections = stmt.returning.map(rc => {
|
|
192
|
+
// TODO: Support RETURNING *
|
|
193
|
+
if (rc.type === 'all') throw new QuereusError('RETURNING * not yet supported', StatusCode.UNSUPPORTED);
|
|
194
|
+
|
|
195
|
+
// Infer alias from column name if not explicitly provided
|
|
196
|
+
let alias = rc.alias;
|
|
197
|
+
if (!alias && rc.expr.type === 'column') {
|
|
198
|
+
// For qualified column references like OLD.id, normalize to lowercase
|
|
199
|
+
alias = rc.expr.table
|
|
200
|
+
? `${rc.expr.table.toLowerCase()}.${rc.expr.name.toLowerCase()}`
|
|
201
|
+
: rc.expr.name.toLowerCase();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
node: buildExpression({ ...deleteCtx, scope: returningScope }, rc.expr) as ScalarPlanNode,
|
|
206
|
+
alias: alias
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return new ReturningNode(deleteCtx.scope, dmlExecutorNode, returningProjections);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return new SinkNode(deleteCtx.scope, resultNode, 'delete');
|
|
214
|
+
}
|