metal-orm 1.1.8 → 1.1.10
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 +769 -764
- package/dist/index.cjs +2352 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +605 -40
- package/dist/index.d.ts +605 -40
- package/dist/index.js +2324 -226
- package/dist/index.js.map +1 -1
- package/package.json +22 -17
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +89 -0
- package/src/bulk/bulk-executor.base.ts +73 -0
- package/src/bulk/bulk-insert-executor.ts +74 -0
- package/src/bulk/bulk-types.ts +70 -0
- package/src/bulk/bulk-update-executor.ts +192 -0
- package/src/bulk/bulk-upsert-executor.ts +95 -0
- package/src/bulk/bulk-utils.ts +91 -0
- package/src/bulk/index.ts +18 -0
- package/src/codegen/typescript.ts +30 -21
- package/src/core/ast/expression-builders.ts +107 -10
- package/src/core/ast/expression-nodes.ts +52 -22
- package/src/core/ast/expression-visitor.ts +23 -13
- package/src/core/dialect/abstract.ts +30 -17
- package/src/core/dialect/mysql/index.ts +20 -5
- package/src/core/execution/db-executor.ts +96 -64
- package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
- package/src/core/execution/executors/mssql-executor.ts +66 -34
- package/src/core/execution/executors/mysql-executor.ts +98 -66
- package/src/core/execution/executors/postgres-executor.ts +33 -11
- package/src/core/execution/executors/sqlite-executor.ts +86 -30
- package/src/decorators/bootstrap.ts +482 -398
- package/src/decorators/column-decorator.ts +87 -96
- package/src/decorators/decorator-metadata.ts +100 -24
- package/src/decorators/entity.ts +27 -24
- package/src/decorators/relations.ts +231 -149
- package/src/decorators/transformers/transformer-decorators.ts +26 -29
- package/src/decorators/validators/country-validators-decorators.ts +9 -15
- package/src/dto/apply-filter.ts +568 -551
- package/src/index.ts +16 -9
- package/src/orm/entity-hydration.ts +116 -72
- package/src/orm/entity-metadata.ts +347 -301
- package/src/orm/entity-relations.ts +264 -207
- package/src/orm/entity.ts +199 -199
- package/src/orm/execute.ts +13 -13
- package/src/orm/lazy-batch/morph-many.ts +70 -0
- package/src/orm/lazy-batch/morph-one.ts +69 -0
- package/src/orm/lazy-batch/morph-to.ts +59 -0
- package/src/orm/lazy-batch.ts +4 -1
- package/src/orm/orm-session.ts +170 -104
- package/src/orm/pooled-executor-factory.ts +99 -58
- package/src/orm/query-logger.ts +49 -40
- package/src/orm/relation-change-processor.ts +198 -96
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -0
- package/src/orm/relations/morph-one.ts +151 -0
- package/src/orm/relations/morph-to.ts +162 -0
- package/src/orm/save-graph.ts +116 -1
- package/src/query-builder/expression-table-mapper.ts +5 -0
- package/src/query-builder/hydration-manager.ts +345 -345
- package/src/query-builder/hydration-planner.ts +178 -148
- package/src/query-builder/relation-conditions.ts +171 -151
- package/src/query-builder/relation-cte-builder.ts +5 -1
- package/src/query-builder/relation-filter-utils.ts +9 -6
- package/src/query-builder/relation-include-strategies.ts +44 -2
- package/src/query-builder/relation-join-strategies.ts +8 -1
- package/src/query-builder/relation-service.ts +250 -241
- package/src/query-builder/select/cursor-pagination.ts +323 -0
- package/src/query-builder/select/select-operations.ts +110 -105
- package/src/query-builder/select.ts +42 -1
- package/src/query-builder/update-include.ts +4 -0
- package/src/schema/relation.ts +296 -188
- package/src/schema/types.ts +138 -123
- package/src/tree/tree-decorator.ts +127 -137
|
@@ -1,248 +1,257 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { ColumnDef } from '../schema/column-types.js';
|
|
3
|
-
import { RelationDef } from '../schema/relation.js';
|
|
4
|
-
import { SelectQueryNode, TableSourceNode, TableNode } from '../core/ast/query.js';
|
|
5
|
-
import { ColumnNode, ExpressionNode, and } from '../core/ast/expression.js';
|
|
6
|
-
import { SelectQueryState } from './select-query-state.js';
|
|
7
|
-
import { HydrationManager } from './hydration-manager.js';
|
|
8
|
-
import { QueryAstService } from './query-ast-service.js';
|
|
9
|
-
import { findPrimaryKey } from './hydration-planner.js';
|
|
10
|
-
import { RelationProjectionHelper } from './relation-projection-helper.js';
|
|
11
|
-
import type { RelationResult } from './relation-projection-helper.js';
|
|
12
|
-
import { buildRelationCorrelation } from './relation-conditions.js';
|
|
13
|
-
import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
|
|
14
|
-
import { RelationIncludeOptions } from './relation-types.js';
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { ColumnDef } from '../schema/column-types.js';
|
|
3
|
+
import { RelationDef, RelationKinds, isSingleTargetRelation } from '../schema/relation.js';
|
|
4
|
+
import { SelectQueryNode, TableSourceNode, TableNode } from '../core/ast/query.js';
|
|
5
|
+
import { ColumnNode, ExpressionNode, and } from '../core/ast/expression.js';
|
|
6
|
+
import { SelectQueryState } from './select-query-state.js';
|
|
7
|
+
import { HydrationManager } from './hydration-manager.js';
|
|
8
|
+
import { QueryAstService } from './query-ast-service.js';
|
|
9
|
+
import { findPrimaryKey } from './hydration-planner.js';
|
|
10
|
+
import { RelationProjectionHelper } from './relation-projection-helper.js';
|
|
11
|
+
import type { RelationResult } from './relation-projection-helper.js';
|
|
12
|
+
import { buildRelationCorrelation } from './relation-conditions.js';
|
|
13
|
+
import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
|
|
14
|
+
import { RelationIncludeOptions } from './relation-types.js';
|
|
15
15
|
import { splitFilterExpressions } from './relation-filter-utils.js';
|
|
16
16
|
import { RelationJoinPlanner } from './relation-join-planner.js';
|
|
17
17
|
import { RelationCteBuilder } from './relation-cte-builder.js';
|
|
18
18
|
import { relationIncludeStrategies } from './relation-include-strategies.js';
|
|
19
19
|
import { hasJoinForRelationKey } from './join-utils.js';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Service for handling relation operations (joins, includes, etc.)
|
|
23
|
-
*/
|
|
24
|
-
export class RelationService {
|
|
25
|
-
private readonly projectionHelper: RelationProjectionHelper;
|
|
26
|
-
private readonly joinPlanner: RelationJoinPlanner;
|
|
27
|
-
private readonly cteBuilder: RelationCteBuilder;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Creates a new RelationService instance
|
|
31
|
-
* @param table - Table definition
|
|
32
|
-
* @param state - Current query state
|
|
33
|
-
* @param hydration - Hydration manager
|
|
34
|
-
*/
|
|
35
|
-
constructor(
|
|
36
|
-
private readonly table: TableDef,
|
|
37
|
-
private readonly state: SelectQueryState,
|
|
38
|
-
private readonly hydration: HydrationManager,
|
|
39
|
-
private readonly createQueryAstService: (table: TableDef, state: SelectQueryState) => QueryAstService
|
|
40
|
-
) {
|
|
41
|
-
this.projectionHelper = new RelationProjectionHelper(table, (state, hydration, columns) =>
|
|
42
|
-
this.selectColumns(state, hydration, columns)
|
|
43
|
-
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Service for handling relation operations (joins, includes, etc.)
|
|
23
|
+
*/
|
|
24
|
+
export class RelationService {
|
|
25
|
+
private readonly projectionHelper: RelationProjectionHelper;
|
|
26
|
+
private readonly joinPlanner: RelationJoinPlanner;
|
|
27
|
+
private readonly cteBuilder: RelationCteBuilder;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new RelationService instance
|
|
31
|
+
* @param table - Table definition
|
|
32
|
+
* @param state - Current query state
|
|
33
|
+
* @param hydration - Hydration manager
|
|
34
|
+
*/
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly table: TableDef,
|
|
37
|
+
private readonly state: SelectQueryState,
|
|
38
|
+
private readonly hydration: HydrationManager,
|
|
39
|
+
private readonly createQueryAstService: (table: TableDef, state: SelectQueryState) => QueryAstService
|
|
40
|
+
) {
|
|
41
|
+
this.projectionHelper = new RelationProjectionHelper(table, (state, hydration, columns) =>
|
|
42
|
+
this.selectColumns(state, hydration, columns)
|
|
43
|
+
);
|
|
44
44
|
this.joinPlanner = new RelationJoinPlanner(table);
|
|
45
|
-
this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Joins a relation to the query
|
|
50
|
-
* @param relationName - Name of the relation to join
|
|
51
|
-
* @param joinKind - Type of join to use
|
|
52
|
-
* @param extraCondition - Additional join condition
|
|
53
|
-
* @returns Relation result with updated state and hydration
|
|
54
|
-
*/
|
|
55
|
-
joinRelation(
|
|
56
|
-
relationName: string,
|
|
57
|
-
joinKind: JoinKind,
|
|
58
|
-
extraCondition?: ExpressionNode,
|
|
59
|
-
tableSource?: TableSourceNode
|
|
60
|
-
): RelationResult {
|
|
61
|
-
const relation = this.getRelation(relationName);
|
|
62
|
-
const nextState = this.joinPlanner.withJoin(
|
|
63
|
-
this.state,
|
|
64
|
-
relationName,
|
|
65
|
-
relation,
|
|
66
|
-
joinKind,
|
|
67
|
-
extraCondition,
|
|
68
|
-
tableSource
|
|
69
|
-
);
|
|
70
|
-
return { state: nextState, hydration: this.hydration };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Matches records based on a relation with an optional predicate
|
|
75
|
-
* @param relationName - Name of the relation to match
|
|
76
|
-
* @param predicate - Optional predicate expression
|
|
77
|
-
* @returns Relation result with updated state and hydration
|
|
78
|
-
*/
|
|
79
|
-
match(
|
|
80
|
-
relationName: string,
|
|
81
|
-
predicate?: ExpressionNode
|
|
82
|
-
): RelationResult {
|
|
83
|
-
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
84
|
-
const pk = findPrimaryKey(this.table);
|
|
85
|
-
const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
|
|
86
|
-
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
87
|
-
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
88
|
-
return { state: nextState, hydration: joined.hydration };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Includes a relation in the query result
|
|
93
|
-
* @param relationName - Name of the relation to include
|
|
94
|
-
* @param options - Options for relation inclusion
|
|
95
|
-
* @returns Relation result with updated state and hydration
|
|
96
|
-
*/
|
|
97
|
-
include(relationName: string, options?: RelationIncludeOptions): RelationResult {
|
|
98
|
-
let state = this.state;
|
|
99
|
-
let hydration = this.hydration;
|
|
100
|
-
|
|
101
|
-
const relation = this.getRelation(relationName);
|
|
102
|
-
|
|
45
|
+
this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Joins a relation to the query
|
|
50
|
+
* @param relationName - Name of the relation to join
|
|
51
|
+
* @param joinKind - Type of join to use
|
|
52
|
+
* @param extraCondition - Additional join condition
|
|
53
|
+
* @returns Relation result with updated state and hydration
|
|
54
|
+
*/
|
|
55
|
+
joinRelation(
|
|
56
|
+
relationName: string,
|
|
57
|
+
joinKind: JoinKind,
|
|
58
|
+
extraCondition?: ExpressionNode,
|
|
59
|
+
tableSource?: TableSourceNode
|
|
60
|
+
): RelationResult {
|
|
61
|
+
const relation = this.getRelation(relationName);
|
|
62
|
+
const nextState = this.joinPlanner.withJoin(
|
|
63
|
+
this.state,
|
|
64
|
+
relationName,
|
|
65
|
+
relation,
|
|
66
|
+
joinKind,
|
|
67
|
+
extraCondition,
|
|
68
|
+
tableSource
|
|
69
|
+
);
|
|
70
|
+
return { state: nextState, hydration: this.hydration };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Matches records based on a relation with an optional predicate
|
|
75
|
+
* @param relationName - Name of the relation to match
|
|
76
|
+
* @param predicate - Optional predicate expression
|
|
77
|
+
* @returns Relation result with updated state and hydration
|
|
78
|
+
*/
|
|
79
|
+
match(
|
|
80
|
+
relationName: string,
|
|
81
|
+
predicate?: ExpressionNode
|
|
82
|
+
): RelationResult {
|
|
83
|
+
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
84
|
+
const pk = findPrimaryKey(this.table);
|
|
85
|
+
const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
|
|
86
|
+
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
87
|
+
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
88
|
+
return { state: nextState, hydration: joined.hydration };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Includes a relation in the query result
|
|
93
|
+
* @param relationName - Name of the relation to include
|
|
94
|
+
* @param options - Options for relation inclusion
|
|
95
|
+
* @returns Relation result with updated state and hydration
|
|
96
|
+
*/
|
|
97
|
+
include(relationName: string, options?: RelationIncludeOptions): RelationResult {
|
|
98
|
+
let state = this.state;
|
|
99
|
+
let hydration = this.hydration;
|
|
100
|
+
|
|
101
|
+
const relation = this.getRelation(relationName);
|
|
102
|
+
if (relation.type === RelationKinds.MorphTo) {
|
|
103
|
+
throw new Error(`MorphTo relation '${relationName}' does not support include() via JOIN. Use lazy loading ($load) instead.`);
|
|
104
|
+
}
|
|
105
|
+
if (!isSingleTargetRelation(relation)) {
|
|
106
|
+
return { state, hydration };
|
|
107
|
+
}
|
|
108
|
+
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
103
109
|
const alreadyJoined = hasJoinForRelationKey(state.ast.joins, relationName);
|
|
104
|
-
const { selfFilters, crossFilters } = splitFilterExpressions(
|
|
105
|
-
options?.filter,
|
|
106
|
-
new Set([relation.target.name])
|
|
107
|
-
);
|
|
108
|
-
const canUseCte = !alreadyJoined && selfFilters.length > 0;
|
|
109
|
-
const joinFilters = [...crossFilters];
|
|
110
|
-
if (!canUseCte) {
|
|
111
|
-
joinFilters.push(...selfFilters);
|
|
112
|
-
}
|
|
113
|
-
const joinCondition = this.combineWithAnd(joinFilters);
|
|
114
|
-
|
|
115
|
-
let tableSourceOverride: TableNode | undefined;
|
|
116
|
-
if (canUseCte) {
|
|
117
|
-
const predicate = this.combineWithAnd(selfFilters);
|
|
118
|
-
const cteInfo = this.cteBuilder.createFilteredRelationCte(
|
|
119
|
-
state,
|
|
120
|
-
relationName,
|
|
121
|
-
relation,
|
|
122
|
-
predicate
|
|
123
|
-
);
|
|
124
|
-
state = cteInfo.state;
|
|
125
|
-
tableSourceOverride = cteInfo.table;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!alreadyJoined) {
|
|
129
|
-
state = this.joinPlanner.withJoin(
|
|
130
|
-
state,
|
|
131
|
-
relationName,
|
|
132
|
-
relation,
|
|
133
|
-
options?.joinKind ?? JOIN_KINDS.LEFT,
|
|
134
|
-
joinCondition,
|
|
135
|
-
tableSourceOverride
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
|
|
140
|
-
state = projectionResult.state;
|
|
141
|
-
hydration = projectionResult.hydration;
|
|
142
|
-
|
|
143
|
-
const strategy = relationIncludeStrategies[relation.type];
|
|
144
|
-
const result = strategy({
|
|
145
|
-
rootTable: this.table,
|
|
146
|
-
state,
|
|
147
|
-
hydration,
|
|
148
|
-
relation,
|
|
149
|
-
relationName,
|
|
150
|
-
aliasPrefix,
|
|
151
|
-
options,
|
|
152
|
-
selectColumns: (nextState, nextHydration, columns) =>
|
|
153
|
-
this.selectColumns(nextState, nextHydration, columns)
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
return { state: result.state, hydration: result.hydration };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Applies relation correlation to a query AST
|
|
161
|
-
* @param relationName - Name of the relation
|
|
162
|
-
* @param ast - Query AST to modify
|
|
163
|
-
* @returns Modified query AST with relation correlation
|
|
164
|
-
*/
|
|
165
|
-
applyRelationCorrelation(
|
|
166
|
-
relationName: string,
|
|
167
|
-
ast: SelectQueryNode,
|
|
168
|
-
additionalCorrelation?: ExpressionNode
|
|
169
|
-
): SelectQueryNode {
|
|
170
|
-
const relation = this.getRelation(relationName);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
* @
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
*
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
110
|
+
const { selfFilters, crossFilters } = splitFilterExpressions(
|
|
111
|
+
options?.filter,
|
|
112
|
+
new Set([relation.target.name])
|
|
113
|
+
);
|
|
114
|
+
const canUseCte = !alreadyJoined && selfFilters.length > 0;
|
|
115
|
+
const joinFilters = [...crossFilters];
|
|
116
|
+
if (!canUseCte) {
|
|
117
|
+
joinFilters.push(...selfFilters);
|
|
118
|
+
}
|
|
119
|
+
const joinCondition = this.combineWithAnd(joinFilters);
|
|
120
|
+
|
|
121
|
+
let tableSourceOverride: TableNode | undefined;
|
|
122
|
+
if (canUseCte) {
|
|
123
|
+
const predicate = this.combineWithAnd(selfFilters);
|
|
124
|
+
const cteInfo = this.cteBuilder.createFilteredRelationCte(
|
|
125
|
+
state,
|
|
126
|
+
relationName,
|
|
127
|
+
relation,
|
|
128
|
+
predicate
|
|
129
|
+
);
|
|
130
|
+
state = cteInfo.state;
|
|
131
|
+
tableSourceOverride = cteInfo.table;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!alreadyJoined) {
|
|
135
|
+
state = this.joinPlanner.withJoin(
|
|
136
|
+
state,
|
|
137
|
+
relationName,
|
|
138
|
+
relation,
|
|
139
|
+
options?.joinKind ?? JOIN_KINDS.LEFT,
|
|
140
|
+
joinCondition,
|
|
141
|
+
tableSourceOverride
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
|
|
146
|
+
state = projectionResult.state;
|
|
147
|
+
hydration = projectionResult.hydration;
|
|
148
|
+
|
|
149
|
+
const strategy = relationIncludeStrategies[relation.type];
|
|
150
|
+
const result = strategy({
|
|
151
|
+
rootTable: this.table,
|
|
152
|
+
state,
|
|
153
|
+
hydration,
|
|
154
|
+
relation,
|
|
155
|
+
relationName,
|
|
156
|
+
aliasPrefix,
|
|
157
|
+
options,
|
|
158
|
+
selectColumns: (nextState, nextHydration, columns) =>
|
|
159
|
+
this.selectColumns(nextState, nextHydration, columns)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return { state: result.state, hydration: result.hydration };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Applies relation correlation to a query AST
|
|
167
|
+
* @param relationName - Name of the relation
|
|
168
|
+
* @param ast - Query AST to modify
|
|
169
|
+
* @returns Modified query AST with relation correlation
|
|
170
|
+
*/
|
|
171
|
+
applyRelationCorrelation(
|
|
172
|
+
relationName: string,
|
|
173
|
+
ast: SelectQueryNode,
|
|
174
|
+
additionalCorrelation?: ExpressionNode
|
|
175
|
+
): SelectQueryNode {
|
|
176
|
+
const relation = this.getRelation(relationName);
|
|
177
|
+
if (relation.type === RelationKinds.MorphTo) {
|
|
178
|
+
throw new Error(`MorphTo relation '${relationName}' does not support correlation-based operations.`);
|
|
179
|
+
}
|
|
180
|
+
const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
|
|
181
|
+
let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
|
|
182
|
+
if (additionalCorrelation) {
|
|
183
|
+
correlation = and(correlation, additionalCorrelation);
|
|
184
|
+
}
|
|
185
|
+
const whereInSubquery = ast.where
|
|
186
|
+
? and(correlation, ast.where)
|
|
187
|
+
: correlation;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...ast,
|
|
191
|
+
where: whereInSubquery
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Selects columns for a relation
|
|
197
|
+
* @param state - Current query state
|
|
198
|
+
* @param hydration - Hydration manager
|
|
199
|
+
* @param columns - Columns to select
|
|
200
|
+
* @returns Relation result with updated state and hydration
|
|
201
|
+
*/
|
|
202
|
+
private selectColumns(
|
|
203
|
+
state: SelectQueryState,
|
|
204
|
+
hydration: HydrationManager,
|
|
205
|
+
columns: Record<string, ColumnDef>
|
|
206
|
+
): RelationResult {
|
|
207
|
+
const { state: nextState, addedColumns } = this.astService(state).select(columns);
|
|
208
|
+
return {
|
|
209
|
+
state: nextState,
|
|
210
|
+
hydration: hydration.onColumnsSelected(nextState, addedColumns)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
private combineWithAnd(expressions: ExpressionNode[]): ExpressionNode | undefined {
|
|
216
|
+
if (expressions.length === 0) return undefined;
|
|
217
|
+
if (expressions.length === 1) return expressions[0];
|
|
218
|
+
return {
|
|
219
|
+
type: 'LogicalExpression',
|
|
220
|
+
operator: 'AND',
|
|
221
|
+
operands: expressions
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Gets a relation definition by name
|
|
227
|
+
* @param relationName - Name of the relation
|
|
228
|
+
* @returns Relation definition
|
|
229
|
+
* @throws Error if relation is not found
|
|
230
|
+
*/
|
|
231
|
+
private getRelation(relationName: string): RelationDef {
|
|
232
|
+
const relation = this.table.relations[relationName];
|
|
233
|
+
if (!relation) {
|
|
234
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return relation;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Creates a QueryAstService instance
|
|
242
|
+
* @param state - Current query state
|
|
243
|
+
* @returns QueryAstService instance
|
|
244
|
+
*/
|
|
245
|
+
private astService(state: SelectQueryState = this.state): QueryAstService {
|
|
246
|
+
return this.createQueryAstService(this.table, state);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private rootTableName(): string {
|
|
250
|
+
const from = this.state.ast.from;
|
|
251
|
+
if (from.type === 'Table' && from.alias) return from.alias;
|
|
252
|
+
return this.table.name;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export type { RelationResult } from './relation-projection-helper.js';
|
|
257
|
+
|