metal-orm 1.1.9 → 1.1.11
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 +2255 -284
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +559 -39
- package/dist/index.d.ts +559 -39
- package/dist/index.js +2227 -284
- package/dist/index.js.map +1 -1
- package/package.json +17 -12
- package/scripts/generate-entities/render.mjs +21 -12
- package/scripts/generate-entities/schema.mjs +87 -73
- package/scripts/generate-entities/tree-detection.mjs +67 -61
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +87 -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 +93 -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/ddl/introspect/mysql.ts +113 -36
- 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/select-operations.ts +110 -105
- 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,151 +1,171 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
|
|
3
|
-
import { ExpressionNode, eq, and } from '../core/ast/expression.js';
|
|
4
|
-
import { TableSourceNode } from '../core/ast/query.js';
|
|
5
|
-
import { findPrimaryKey } from './hydration-planner.js';
|
|
6
|
-
import { JoinNode } from '../core/ast/join.js';
|
|
7
|
-
import { JoinKind } from '../core/sql/sql.js';
|
|
8
|
-
import { createJoinNode } from '../core/ast/join-node.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Utility function to handle unreachable code paths
|
|
12
|
-
* @param value - Value that should never occur
|
|
13
|
-
* @throws Error indicating unhandled relation type
|
|
14
|
-
*/
|
|
15
|
-
const assertNever = (value: never): never => {
|
|
16
|
-
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Builds the base condition for a relation join
|
|
21
|
-
* @param root - Root table definition
|
|
22
|
-
* @param relation - Relation definition
|
|
23
|
-
* @returns Expression node representing the join condition
|
|
24
|
-
*/
|
|
25
|
-
const baseRelationCondition = (
|
|
26
|
-
root: TableDef,
|
|
27
|
-
relation: RelationDef,
|
|
28
|
-
rootAlias?: string,
|
|
29
|
-
targetTableName?: string
|
|
30
|
-
): ExpressionNode => {
|
|
31
|
-
const rootTable = rootAlias || root.name;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
switch (relation.type) {
|
|
40
|
-
case RelationKinds.HasMany:
|
|
41
|
-
case RelationKinds.HasOne:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
const
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
*
|
|
140
|
-
* @param
|
|
141
|
-
* @param
|
|
142
|
-
* @
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationDef, RelationKinds, BelongsToManyRelation, MorphOneRelation, MorphManyRelation } from '../schema/relation.js';
|
|
3
|
+
import { ExpressionNode, eq, and } from '../core/ast/expression.js';
|
|
4
|
+
import { TableSourceNode } from '../core/ast/query.js';
|
|
5
|
+
import { findPrimaryKey } from './hydration-planner.js';
|
|
6
|
+
import { JoinNode } from '../core/ast/join.js';
|
|
7
|
+
import { JoinKind } from '../core/sql/sql.js';
|
|
8
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Utility function to handle unreachable code paths
|
|
12
|
+
* @param value - Value that should never occur
|
|
13
|
+
* @throws Error indicating unhandled relation type
|
|
14
|
+
*/
|
|
15
|
+
const assertNever = (value: never): never => {
|
|
16
|
+
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds the base condition for a relation join
|
|
21
|
+
* @param root - Root table definition
|
|
22
|
+
* @param relation - Relation definition
|
|
23
|
+
* @returns Expression node representing the join condition
|
|
24
|
+
*/
|
|
25
|
+
const baseRelationCondition = (
|
|
26
|
+
root: TableDef,
|
|
27
|
+
relation: RelationDef,
|
|
28
|
+
rootAlias?: string,
|
|
29
|
+
targetTableName?: string
|
|
30
|
+
): ExpressionNode => {
|
|
31
|
+
const rootTable = rootAlias || root.name;
|
|
32
|
+
|
|
33
|
+
if (relation.type === RelationKinds.MorphTo) {
|
|
34
|
+
throw new Error('MorphTo relations do not support the standard join condition builder');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const targetTable = targetTableName ?? relation.target.name;
|
|
38
|
+
|
|
39
|
+
switch (relation.type) {
|
|
40
|
+
case RelationKinds.HasMany:
|
|
41
|
+
case RelationKinds.HasOne: {
|
|
42
|
+
const defaultLocalKey = findPrimaryKey(root);
|
|
43
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
44
|
+
return eq(
|
|
45
|
+
{ type: 'Column', table: targetTable, name: relation.foreignKey },
|
|
46
|
+
{ type: 'Column', table: rootTable, name: localKey }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
case RelationKinds.BelongsTo: {
|
|
50
|
+
const defaultLocalKey = findPrimaryKey(relation.target);
|
|
51
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
52
|
+
return eq(
|
|
53
|
+
{ type: 'Column', table: targetTable, name: localKey },
|
|
54
|
+
{ type: 'Column', table: rootTable, name: relation.foreignKey }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
case RelationKinds.BelongsToMany:
|
|
58
|
+
throw new Error('BelongsToMany relations do not support the standard join condition builder');
|
|
59
|
+
case RelationKinds.MorphOne:
|
|
60
|
+
case RelationKinds.MorphMany: {
|
|
61
|
+
const morphRel = relation as MorphOneRelation | MorphManyRelation;
|
|
62
|
+
const morphLocalKey = morphRel.localKey || findPrimaryKey(root);
|
|
63
|
+
const baseCondition = eq(
|
|
64
|
+
{ type: 'Column', table: targetTable, name: morphRel.idField },
|
|
65
|
+
{ type: 'Column', table: rootTable, name: morphLocalKey }
|
|
66
|
+
);
|
|
67
|
+
const discriminatorCondition = eq(
|
|
68
|
+
{ type: 'Column', table: targetTable, name: morphRel.typeField },
|
|
69
|
+
{ type: 'Literal', value: morphRel.typeValue }
|
|
70
|
+
);
|
|
71
|
+
return and(baseCondition, discriminatorCondition);
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
return assertNever(relation);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds the join nodes required to include a BelongsToMany relation.
|
|
80
|
+
* @param root - The root table definition
|
|
81
|
+
* @param relationName - Name of the relation being joined
|
|
82
|
+
* @param relation - The BelongsToMany relation definition
|
|
83
|
+
* @param joinKind - The type of join to perform
|
|
84
|
+
* @param extra - Optional additional conditions for the target join
|
|
85
|
+
* @param rootAlias - Optional alias for the root table
|
|
86
|
+
* @returns Array of join nodes for the pivot and target tables
|
|
87
|
+
*/
|
|
88
|
+
export const buildBelongsToManyJoins = (
|
|
89
|
+
root: TableDef,
|
|
90
|
+
relationName: string,
|
|
91
|
+
relation: BelongsToManyRelation,
|
|
92
|
+
joinKind: JoinKind,
|
|
93
|
+
extra?: ExpressionNode,
|
|
94
|
+
rootAlias?: string,
|
|
95
|
+
targetTable?: TableSourceNode,
|
|
96
|
+
targetTableName?: string
|
|
97
|
+
): JoinNode[] => {
|
|
98
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
99
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
100
|
+
const rootTable = rootAlias || root.name;
|
|
101
|
+
|
|
102
|
+
const pivotCondition = eq(
|
|
103
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
104
|
+
{ type: 'Column', table: rootTable, name: rootKey }
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const pivotJoin = createJoinNode(
|
|
108
|
+
joinKind,
|
|
109
|
+
{ type: 'Table', name: relation.pivotTable.name, schema: relation.pivotTable.schema },
|
|
110
|
+
pivotCondition
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const targetSource: TableSourceNode = targetTable ?? {
|
|
114
|
+
type: 'Table',
|
|
115
|
+
name: relation.target.name,
|
|
116
|
+
schema: relation.target.schema
|
|
117
|
+
};
|
|
118
|
+
const effectiveTargetName = targetTableName ?? relation.target.name;
|
|
119
|
+
let targetCondition: ExpressionNode = eq(
|
|
120
|
+
{ type: 'Column', table: effectiveTargetName, name: targetKey },
|
|
121
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (extra) {
|
|
125
|
+
targetCondition = and(targetCondition, extra);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const targetJoin = createJoinNode(
|
|
129
|
+
joinKind,
|
|
130
|
+
targetSource,
|
|
131
|
+
targetCondition,
|
|
132
|
+
relationName
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return [pivotJoin, targetJoin];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Builds a relation join condition with optional extra conditions
|
|
140
|
+
* @param root - Root table definition
|
|
141
|
+
* @param relation - Relation definition
|
|
142
|
+
* @param extra - Optional additional expression to combine with AND
|
|
143
|
+
* @param rootAlias - Optional alias for the root table
|
|
144
|
+
* @returns Expression node representing the complete join condition
|
|
145
|
+
*/
|
|
146
|
+
export const buildRelationJoinCondition = (
|
|
147
|
+
root: TableDef,
|
|
148
|
+
relation: RelationDef,
|
|
149
|
+
extra?: ExpressionNode,
|
|
150
|
+
rootAlias?: string,
|
|
151
|
+
targetTableName?: string
|
|
152
|
+
): ExpressionNode => {
|
|
153
|
+
const base = baseRelationCondition(root, relation, rootAlias, targetTableName);
|
|
154
|
+
return extra ? and(base, extra) : base;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Builds a relation correlation condition for subqueries
|
|
159
|
+
* @param root - Root table definition
|
|
160
|
+
* @param relation - Relation definition
|
|
161
|
+
* @param rootAlias - Optional alias for the root table
|
|
162
|
+
* @returns Expression node representing the correlation condition
|
|
163
|
+
*/
|
|
164
|
+
export const buildRelationCorrelation = (
|
|
165
|
+
root: TableDef,
|
|
166
|
+
relation: RelationDef,
|
|
167
|
+
rootAlias?: string,
|
|
168
|
+
targetTableName?: string
|
|
169
|
+
): ExpressionNode => {
|
|
170
|
+
return baseRelationCondition(root, relation, rootAlias, targetTableName);
|
|
171
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { RelationDef } from '../schema/relation.js';
|
|
2
|
+
import { RelationDef, isSingleTargetRelation } from '../schema/relation.js';
|
|
3
3
|
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
4
4
|
import { SelectQueryNode, TableNode } from '../core/ast/query.js';
|
|
5
5
|
import { SelectQueryState } from './select-query-state.js';
|
|
@@ -22,6 +22,10 @@ export class RelationCteBuilder {
|
|
|
22
22
|
throw new Error('Unable to build filter CTE without predicates.');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
if (!isSingleTargetRelation(relation)) {
|
|
26
|
+
throw new Error('Polymorphic MorphTo relations do not support filter CTEs');
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
const columns: ColumnNode[] = Object.keys(relation.target.columns).map(name => ({
|
|
26
30
|
type: 'Column',
|
|
27
31
|
table: relation.target.name,
|
|
@@ -65,12 +65,15 @@ const collectFromExpression = (expr: ExpressionNode, collector: FilterTableColle
|
|
|
65
65
|
collectFromOperand(expr.left, collector);
|
|
66
66
|
collectFromOperand(expr.right, collector);
|
|
67
67
|
break;
|
|
68
|
-
case 'LogicalExpression':
|
|
69
|
-
expr.operands.forEach(operand => collectFromExpression(operand, collector));
|
|
70
|
-
break;
|
|
71
|
-
case '
|
|
72
|
-
|
|
73
|
-
break;
|
|
68
|
+
case 'LogicalExpression':
|
|
69
|
+
expr.operands.forEach(operand => collectFromExpression(operand, collector));
|
|
70
|
+
break;
|
|
71
|
+
case 'NotExpression':
|
|
72
|
+
collectFromExpression(expr.operand, collector);
|
|
73
|
+
break;
|
|
74
|
+
case 'NullExpression':
|
|
75
|
+
collectFromOperand(expr.left, collector);
|
|
76
|
+
break;
|
|
74
77
|
case 'InExpression':
|
|
75
78
|
collectFromOperand(expr.left, collector);
|
|
76
79
|
if (Array.isArray(expr.right)) {
|
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
BelongsToManyRelation,
|
|
7
7
|
HasManyRelation,
|
|
8
8
|
HasOneRelation,
|
|
9
|
-
BelongsToRelation
|
|
9
|
+
BelongsToRelation,
|
|
10
|
+
MorphOneRelation,
|
|
11
|
+
MorphManyRelation,
|
|
12
|
+
isSingleTargetRelation
|
|
10
13
|
} from '../schema/relation.js';
|
|
11
14
|
import { ColumnNode } from '../core/ast/expression.js';
|
|
12
15
|
import { SelectQueryState } from './select-query-state.js';
|
|
@@ -61,6 +64,9 @@ const buildTypedSelection = (
|
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
const resolveTargetColumns = (relation: RelationDef, options?: RelationIncludeOptions): string[] => {
|
|
67
|
+
if (!isSingleTargetRelation(relation)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
64
70
|
const requestedColumns = options?.columns?.length
|
|
65
71
|
? [...options.columns]
|
|
66
72
|
: Object.keys(relation.target.columns);
|
|
@@ -180,9 +186,45 @@ const belongsToManyStrategy: IncludeStrategy = context => {
|
|
|
180
186
|
return { state, hydration };
|
|
181
187
|
};
|
|
182
188
|
|
|
189
|
+
const morphIncludeStrategy: IncludeStrategy = context => {
|
|
190
|
+
let { state, hydration } = context;
|
|
191
|
+
|
|
192
|
+
const relation = context.relation as MorphOneRelation | MorphManyRelation;
|
|
193
|
+
const targetColumns = resolveTargetColumns(relation, context.options);
|
|
194
|
+
const tableOverride = getJoinCorrelationName(state, context.relationName, relation.target.name);
|
|
195
|
+
const targetSelection = buildTypedSelection(
|
|
196
|
+
relation.target.columns as Record<string, ColumnDef>,
|
|
197
|
+
context.aliasPrefix,
|
|
198
|
+
targetColumns,
|
|
199
|
+
key => `Column '${key}' not found on relation '${context.relationName}'`,
|
|
200
|
+
tableOverride
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const relationSelectionResult = context.selectColumns(state, hydration, targetSelection);
|
|
204
|
+
state = relationSelectionResult.state;
|
|
205
|
+
hydration = relationSelectionResult.hydration;
|
|
206
|
+
|
|
207
|
+
hydration = hydration.onRelationIncluded(
|
|
208
|
+
state,
|
|
209
|
+
relation,
|
|
210
|
+
context.relationName,
|
|
211
|
+
context.aliasPrefix,
|
|
212
|
+
targetColumns
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return { state, hydration };
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const morphToIncludeStrategy: IncludeStrategy = () => {
|
|
219
|
+
throw new Error('MorphTo relations do not support JOIN-based include. Use lazy loading instead.');
|
|
220
|
+
};
|
|
221
|
+
|
|
183
222
|
export const relationIncludeStrategies: Record<RelationDef['type'], IncludeStrategy> = {
|
|
184
223
|
[RelationKinds.HasMany]: standardIncludeStrategy,
|
|
185
224
|
[RelationKinds.HasOne]: standardIncludeStrategy,
|
|
186
225
|
[RelationKinds.BelongsTo]: standardIncludeStrategy,
|
|
187
|
-
[RelationKinds.BelongsToMany]: belongsToManyStrategy
|
|
226
|
+
[RelationKinds.BelongsToMany]: belongsToManyStrategy,
|
|
227
|
+
[RelationKinds.MorphOne]: morphIncludeStrategy,
|
|
228
|
+
[RelationKinds.MorphMany]: morphIncludeStrategy,
|
|
229
|
+
[RelationKinds.MorphTo]: morphToIncludeStrategy
|
|
188
230
|
};
|
|
@@ -3,7 +3,7 @@ import type { JoinNode } from '../core/ast/join.js';
|
|
|
3
3
|
import { createJoinNode } from '../core/ast/join-node.js';
|
|
4
4
|
import type { TableSourceNode } from '../core/ast/query.js';
|
|
5
5
|
import { JoinKind } from '../core/sql/sql.js';
|
|
6
|
-
import { RelationDef, RelationKinds, type BelongsToManyRelation } from '../schema/relation.js';
|
|
6
|
+
import { RelationDef, RelationKinds, type BelongsToManyRelation, isSingleTargetRelation } from '../schema/relation.js';
|
|
7
7
|
import type { TableDef } from '../schema/table.js';
|
|
8
8
|
import { findPrimaryKey } from './hydration-planner.js';
|
|
9
9
|
import { buildBelongsToManyJoins, buildRelationJoinCondition } from './relation-conditions.js';
|
|
@@ -64,6 +64,10 @@ export const addRelationJoin = (params: AddRelationJoinParams): SelectQueryState
|
|
|
64
64
|
return joins.reduce((curr, join) => curr.withJoin(join), state);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
if (!isSingleTargetRelation(relation)) {
|
|
68
|
+
throw new Error('Polymorphic MorphTo relations do not support join-based strategies');
|
|
69
|
+
}
|
|
70
|
+
|
|
67
71
|
let targetSource: TableSourceNode = tableSource ?? {
|
|
68
72
|
type: 'Table',
|
|
69
73
|
name: relation.target.name,
|
|
@@ -89,6 +93,9 @@ type UpdateRelationJoinParams = {
|
|
|
89
93
|
export const updateRelationJoin = (params: UpdateRelationJoinParams): JoinNode[] => {
|
|
90
94
|
const { joins, joinIndex, relation, currentTable, currentAlias, options } = params;
|
|
91
95
|
const join = joins[joinIndex];
|
|
96
|
+
if (!isSingleTargetRelation(relation)) {
|
|
97
|
+
throw new Error('Polymorphic MorphTo relations do not support join updates');
|
|
98
|
+
}
|
|
92
99
|
const targetName = resolveTargetTableName(join.table, relation.target.name);
|
|
93
100
|
const extra = remapExpressionTable(options.filter, relation.target.name, targetName);
|
|
94
101
|
|