metal-orm 1.0.43 → 1.0.45
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 +700 -557
- package/dist/index.cjs +896 -476
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1146 -275
- package/dist/index.d.ts +1146 -275
- package/dist/index.js +896 -474
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/adapters.ts +8 -2
- package/src/core/ast/builders.ts +105 -81
- package/src/core/ast/expression-builders.ts +430 -390
- package/src/core/ast/expression-visitor.ts +47 -8
- package/src/core/ast/helpers.ts +23 -0
- package/src/core/ast/join-node.ts +17 -1
- package/src/core/ddl/dialects/base-schema-dialect.ts +7 -1
- package/src/core/ddl/dialects/index.ts +1 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -0
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
- package/src/core/ddl/introspect/context.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +11 -0
- package/src/core/ddl/introspect/mysql.ts +2 -0
- package/src/core/ddl/introspect/postgres.ts +14 -0
- package/src/core/ddl/introspect/registry.ts +14 -0
- package/src/core/ddl/introspect/run-select.ts +13 -0
- package/src/core/ddl/introspect/sqlite.ts +22 -0
- package/src/core/ddl/introspect/utils.ts +18 -0
- package/src/core/ddl/naming-strategy.ts +6 -0
- package/src/core/ddl/schema-dialect.ts +19 -6
- package/src/core/ddl/schema-diff.ts +22 -0
- package/src/core/ddl/schema-generator.ts +22 -0
- package/src/core/ddl/schema-plan-executor.ts +6 -0
- package/src/core/ddl/schema-types.ts +6 -0
- package/src/core/dialect/abstract.ts +2 -2
- package/src/core/execution/pooling/pool.ts +12 -7
- package/src/core/functions/datetime.ts +57 -33
- package/src/core/functions/numeric.ts +95 -30
- package/src/core/functions/standard-strategy.ts +35 -0
- package/src/core/functions/text.ts +83 -22
- package/src/core/functions/types.ts +23 -8
- package/src/decorators/bootstrap.ts +16 -4
- package/src/decorators/column.ts +17 -0
- package/src/decorators/decorator-metadata.ts +27 -0
- package/src/decorators/entity.ts +8 -0
- package/src/decorators/index.ts +3 -0
- package/src/decorators/relations.ts +32 -0
- package/src/orm/als.ts +34 -9
- package/src/orm/entity-context.ts +54 -0
- package/src/orm/entity-metadata.ts +122 -9
- package/src/orm/execute.ts +15 -0
- package/src/orm/lazy-batch.ts +158 -98
- package/src/orm/relations/has-many.ts +44 -0
- package/src/orm/save-graph.ts +45 -0
- package/src/query/index.ts +74 -0
- package/src/query/target.ts +46 -0
- package/src/query-builder/delete-query-state.ts +30 -0
- package/src/query-builder/delete.ts +64 -19
- package/src/query-builder/hydration-manager.ts +46 -0
- package/src/query-builder/insert-query-state.ts +30 -0
- package/src/query-builder/insert.ts +46 -2
- package/src/query-builder/query-ast-service.ts +5 -0
- package/src/query-builder/query-resolution.ts +78 -0
- package/src/query-builder/raw-column-parser.ts +5 -0
- package/src/query-builder/relation-alias.ts +7 -0
- package/src/query-builder/relation-conditions.ts +61 -48
- package/src/query-builder/relation-service.ts +68 -63
- package/src/query-builder/relation-utils.ts +3 -0
- package/src/query-builder/select/cte-facet.ts +40 -0
- package/src/query-builder/select/from-facet.ts +80 -0
- package/src/query-builder/select/join-facet.ts +62 -0
- package/src/query-builder/select/predicate-facet.ts +103 -0
- package/src/query-builder/select/projection-facet.ts +69 -0
- package/src/query-builder/select/relation-facet.ts +81 -0
- package/src/query-builder/select/setop-facet.ts +36 -0
- package/src/query-builder/select-helpers.ts +13 -0
- package/src/query-builder/select-query-builder-deps.ts +19 -1
- package/src/query-builder/select-query-state.ts +2 -1
- package/src/query-builder/select.ts +795 -1163
- package/src/query-builder/update-query-state.ts +52 -0
- package/src/query-builder/update.ts +69 -19
- package/src/schema/table-guards.ts +31 -0
|
@@ -21,26 +21,26 @@ const assertNever = (value: never): never => {
|
|
|
21
21
|
* @param relation - Relation definition
|
|
22
22
|
* @returns Expression node representing the join condition
|
|
23
23
|
*/
|
|
24
|
-
const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
25
|
-
const rootTable = rootAlias || root.name;
|
|
26
|
-
const defaultLocalKey =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const localKey = relation.localKey || defaultLocalKey;
|
|
24
|
+
const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
25
|
+
const rootTable = rootAlias || root.name;
|
|
26
|
+
const defaultLocalKey =
|
|
27
|
+
relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
|
|
28
|
+
? findPrimaryKey(root)
|
|
29
|
+
: findPrimaryKey(relation.target);
|
|
30
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
31
31
|
|
|
32
32
|
switch (relation.type) {
|
|
33
|
-
case RelationKinds.HasMany:
|
|
34
|
-
case RelationKinds.HasOne:
|
|
35
|
-
return eq(
|
|
36
|
-
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
37
|
-
{ type: 'Column', table: rootTable, name: localKey }
|
|
38
|
-
);
|
|
39
|
-
case RelationKinds.BelongsTo:
|
|
40
|
-
return eq(
|
|
41
|
-
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
42
|
-
{ type: 'Column', table: rootTable, name: relation.foreignKey }
|
|
43
|
-
);
|
|
33
|
+
case RelationKinds.HasMany:
|
|
34
|
+
case RelationKinds.HasOne:
|
|
35
|
+
return eq(
|
|
36
|
+
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
37
|
+
{ type: 'Column', table: rootTable, name: localKey }
|
|
38
|
+
);
|
|
39
|
+
case RelationKinds.BelongsTo:
|
|
40
|
+
return eq(
|
|
41
|
+
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
42
|
+
{ type: 'Column', table: rootTable, name: relation.foreignKey }
|
|
43
|
+
);
|
|
44
44
|
case RelationKinds.BelongsToMany:
|
|
45
45
|
throw new Error('BelongsToMany relations do not support the standard join condition builder');
|
|
46
46
|
default:
|
|
@@ -50,25 +50,36 @@ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Builds the join nodes required to include a BelongsToMany relation.
|
|
53
|
+
* @param root - The root table definition
|
|
54
|
+
* @param relationName - Name of the relation being joined
|
|
55
|
+
* @param relation - The BelongsToMany relation definition
|
|
56
|
+
* @param joinKind - The type of join to perform
|
|
57
|
+
* @param extra - Optional additional conditions for the target join
|
|
58
|
+
* @param rootAlias - Optional alias for the root table
|
|
59
|
+
* @returns Array of join nodes for the pivot and target tables
|
|
53
60
|
*/
|
|
54
|
-
export const buildBelongsToManyJoins = (
|
|
55
|
-
root: TableDef,
|
|
56
|
-
relationName: string,
|
|
57
|
-
relation: BelongsToManyRelation,
|
|
58
|
-
joinKind: JoinKind,
|
|
59
|
-
extra?: ExpressionNode,
|
|
60
|
-
rootAlias?: string
|
|
61
|
-
): JoinNode[] => {
|
|
62
|
-
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
63
|
-
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
64
|
-
const rootTable = rootAlias || root.name;
|
|
65
|
-
|
|
66
|
-
const pivotCondition = eq(
|
|
67
|
-
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
68
|
-
{ type: 'Column', table: rootTable, name: rootKey }
|
|
69
|
-
);
|
|
61
|
+
export const buildBelongsToManyJoins = (
|
|
62
|
+
root: TableDef,
|
|
63
|
+
relationName: string,
|
|
64
|
+
relation: BelongsToManyRelation,
|
|
65
|
+
joinKind: JoinKind,
|
|
66
|
+
extra?: ExpressionNode,
|
|
67
|
+
rootAlias?: string
|
|
68
|
+
): JoinNode[] => {
|
|
69
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
70
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
71
|
+
const rootTable = rootAlias || root.name;
|
|
70
72
|
|
|
71
|
-
const
|
|
73
|
+
const pivotCondition = eq(
|
|
74
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
75
|
+
{ type: 'Column', table: rootTable, name: rootKey }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const pivotJoin = createJoinNode(
|
|
79
|
+
joinKind,
|
|
80
|
+
{ type: 'Table', name: relation.pivotTable.name, schema: relation.pivotTable.schema },
|
|
81
|
+
pivotCondition
|
|
82
|
+
);
|
|
72
83
|
|
|
73
84
|
let targetCondition: ExpressionNode = eq(
|
|
74
85
|
{ type: 'Column', table: relation.target.name, name: targetKey },
|
|
@@ -81,7 +92,7 @@ export const buildBelongsToManyJoins = (
|
|
|
81
92
|
|
|
82
93
|
const targetJoin = createJoinNode(
|
|
83
94
|
joinKind,
|
|
84
|
-
relation.target.name,
|
|
95
|
+
{ type: 'Table', name: relation.target.name, schema: relation.target.schema },
|
|
85
96
|
targetCondition,
|
|
86
97
|
relationName
|
|
87
98
|
);
|
|
@@ -94,24 +105,26 @@ export const buildBelongsToManyJoins = (
|
|
|
94
105
|
* @param root - Root table definition
|
|
95
106
|
* @param relation - Relation definition
|
|
96
107
|
* @param extra - Optional additional expression to combine with AND
|
|
108
|
+
* @param rootAlias - Optional alias for the root table
|
|
97
109
|
* @returns Expression node representing the complete join condition
|
|
98
110
|
*/
|
|
99
|
-
export const buildRelationJoinCondition = (
|
|
100
|
-
root: TableDef,
|
|
101
|
-
relation: RelationDef,
|
|
102
|
-
extra?: ExpressionNode,
|
|
103
|
-
rootAlias?: string
|
|
104
|
-
): ExpressionNode => {
|
|
105
|
-
const base = baseRelationCondition(root, relation, rootAlias);
|
|
106
|
-
return extra ? and(base, extra) : base;
|
|
107
|
-
};
|
|
111
|
+
export const buildRelationJoinCondition = (
|
|
112
|
+
root: TableDef,
|
|
113
|
+
relation: RelationDef,
|
|
114
|
+
extra?: ExpressionNode,
|
|
115
|
+
rootAlias?: string
|
|
116
|
+
): ExpressionNode => {
|
|
117
|
+
const base = baseRelationCondition(root, relation, rootAlias);
|
|
118
|
+
return extra ? and(base, extra) : base;
|
|
119
|
+
};
|
|
108
120
|
|
|
109
121
|
/**
|
|
110
122
|
* Builds a relation correlation condition for subqueries
|
|
111
123
|
* @param root - Root table definition
|
|
112
124
|
* @param relation - Relation definition
|
|
125
|
+
* @param rootAlias - Optional alias for the root table
|
|
113
126
|
* @returns Expression node representing the correlation condition
|
|
114
127
|
*/
|
|
115
|
-
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
116
|
-
return baseRelationCondition(root, relation, rootAlias);
|
|
117
|
-
};
|
|
128
|
+
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
129
|
+
return baseRelationCondition(root, relation, rootAlias);
|
|
130
|
+
};
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
} from './relation-conditions.js';
|
|
21
21
|
import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
|
|
22
22
|
import { RelationIncludeOptions } from './relation-types.js';
|
|
23
|
-
import { createJoinNode } from '../core/ast/join-node.js';
|
|
24
|
-
import { getJoinRelationName } from '../core/ast/join-metadata.js';
|
|
23
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
24
|
+
import { getJoinRelationName } from '../core/ast/join-metadata.js';
|
|
25
25
|
import { makeRelationAlias } from './relation-alias.js';
|
|
26
26
|
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
27
27
|
|
|
@@ -70,17 +70,17 @@ export class RelationService {
|
|
|
70
70
|
* @param predicate - Optional predicate expression
|
|
71
71
|
* @returns Relation result with updated state and hydration
|
|
72
72
|
*/
|
|
73
|
-
match(
|
|
74
|
-
relationName: string,
|
|
75
|
-
predicate?: ExpressionNode
|
|
76
|
-
): RelationResult {
|
|
77
|
-
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
78
|
-
const pk = findPrimaryKey(this.table);
|
|
79
|
-
const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
|
|
80
|
-
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
81
|
-
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
82
|
-
return { state: nextState, hydration: joined.hydration };
|
|
83
|
-
}
|
|
73
|
+
match(
|
|
74
|
+
relationName: string,
|
|
75
|
+
predicate?: ExpressionNode
|
|
76
|
+
): RelationResult {
|
|
77
|
+
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
78
|
+
const pk = findPrimaryKey(this.table);
|
|
79
|
+
const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
|
|
80
|
+
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
81
|
+
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
82
|
+
return { state: nextState, hydration: joined.hydration };
|
|
83
|
+
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Includes a relation in the query result
|
|
@@ -94,7 +94,7 @@ export class RelationService {
|
|
|
94
94
|
|
|
95
95
|
const relation = this.getRelation(relationName);
|
|
96
96
|
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
97
|
-
const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
|
|
97
|
+
const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
|
|
98
98
|
|
|
99
99
|
if (!alreadyJoined) {
|
|
100
100
|
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
@@ -114,7 +114,7 @@ export class RelationService {
|
|
|
114
114
|
prefix: string,
|
|
115
115
|
keys: string[],
|
|
116
116
|
missingMsg: (col: string) => string
|
|
117
|
-
)
|
|
117
|
+
): Record<string, ColumnDef> => {
|
|
118
118
|
return keys.reduce((acc, key) => {
|
|
119
119
|
const def = columns[key];
|
|
120
120
|
if (!def) {
|
|
@@ -190,22 +190,22 @@ export class RelationService {
|
|
|
190
190
|
* @param ast - Query AST to modify
|
|
191
191
|
* @returns Modified query AST with relation correlation
|
|
192
192
|
*/
|
|
193
|
-
applyRelationCorrelation(
|
|
194
|
-
relationName: string,
|
|
195
|
-
ast: SelectQueryNode,
|
|
196
|
-
additionalCorrelation?: ExpressionNode
|
|
197
|
-
): SelectQueryNode {
|
|
198
|
-
const relation = this.getRelation(relationName);
|
|
199
|
-
const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
|
|
200
|
-
let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
|
|
201
|
-
if (additionalCorrelation) {
|
|
202
|
-
correlation = and(correlation, additionalCorrelation);
|
|
203
|
-
}
|
|
204
|
-
const whereInSubquery = ast.where
|
|
205
|
-
? and(correlation, ast.where)
|
|
206
|
-
: correlation;
|
|
207
|
-
|
|
208
|
-
return {
|
|
193
|
+
applyRelationCorrelation(
|
|
194
|
+
relationName: string,
|
|
195
|
+
ast: SelectQueryNode,
|
|
196
|
+
additionalCorrelation?: ExpressionNode
|
|
197
|
+
): SelectQueryNode {
|
|
198
|
+
const relation = this.getRelation(relationName);
|
|
199
|
+
const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
|
|
200
|
+
let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
|
|
201
|
+
if (additionalCorrelation) {
|
|
202
|
+
correlation = and(correlation, additionalCorrelation);
|
|
203
|
+
}
|
|
204
|
+
const whereInSubquery = ast.where
|
|
205
|
+
? and(correlation, ast.where)
|
|
206
|
+
: correlation;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
209
|
...ast,
|
|
210
210
|
where: whereInSubquery
|
|
211
211
|
};
|
|
@@ -219,28 +219,33 @@ export class RelationService {
|
|
|
219
219
|
* @param extraCondition - Additional join condition
|
|
220
220
|
* @returns Updated query state with join
|
|
221
221
|
*/
|
|
222
|
-
private withJoin(
|
|
223
|
-
state: SelectQueryState,
|
|
224
|
-
relationName: string,
|
|
225
|
-
joinKind: JoinKind,
|
|
226
|
-
extraCondition?: ExpressionNode
|
|
227
|
-
): SelectQueryState {
|
|
228
|
-
const relation = this.getRelation(relationName);
|
|
229
|
-
const rootAlias = state.ast.from.type === 'Table' ? state.ast.from.alias : undefined;
|
|
230
|
-
if (relation.type === RelationKinds.BelongsToMany) {
|
|
231
|
-
const joins = buildBelongsToManyJoins(
|
|
232
|
-
this.table,
|
|
233
|
-
relationName,
|
|
234
|
-
relation as BelongsToManyRelation,
|
|
235
|
-
joinKind,
|
|
236
|
-
extraCondition,
|
|
237
|
-
rootAlias
|
|
238
|
-
);
|
|
239
|
-
return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
|
|
243
|
-
const joinNode = createJoinNode(
|
|
222
|
+
private withJoin(
|
|
223
|
+
state: SelectQueryState,
|
|
224
|
+
relationName: string,
|
|
225
|
+
joinKind: JoinKind,
|
|
226
|
+
extraCondition?: ExpressionNode
|
|
227
|
+
): SelectQueryState {
|
|
228
|
+
const relation = this.getRelation(relationName);
|
|
229
|
+
const rootAlias = state.ast.from.type === 'Table' ? state.ast.from.alias : undefined;
|
|
230
|
+
if (relation.type === RelationKinds.BelongsToMany) {
|
|
231
|
+
const joins = buildBelongsToManyJoins(
|
|
232
|
+
this.table,
|
|
233
|
+
relationName,
|
|
234
|
+
relation as BelongsToManyRelation,
|
|
235
|
+
joinKind,
|
|
236
|
+
extraCondition,
|
|
237
|
+
rootAlias
|
|
238
|
+
);
|
|
239
|
+
return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
|
|
243
|
+
const joinNode = createJoinNode(
|
|
244
|
+
joinKind,
|
|
245
|
+
{ type: 'Table', name: relation.target.name, schema: relation.target.schema },
|
|
246
|
+
condition,
|
|
247
|
+
relationName
|
|
248
|
+
);
|
|
244
249
|
|
|
245
250
|
return this.astService(state).withJoin(joinNode);
|
|
246
251
|
}
|
|
@@ -284,15 +289,15 @@ export class RelationService {
|
|
|
284
289
|
* @param state - Current query state
|
|
285
290
|
* @returns QueryAstService instance
|
|
286
291
|
*/
|
|
287
|
-
private astService(state: SelectQueryState = this.state): QueryAstService {
|
|
288
|
-
return this.createQueryAstService(this.table, state);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private rootTableName(): string {
|
|
292
|
-
const from = this.state.ast.from;
|
|
293
|
-
if (from.type === 'Table' && from.alias) return from.alias;
|
|
294
|
-
return this.table.name;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
292
|
+
private astService(state: SelectQueryState = this.state): QueryAstService {
|
|
293
|
+
return this.createQueryAstService(this.table, state);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private rootTableName(): string {
|
|
297
|
+
const from = this.state.ast.from;
|
|
298
|
+
if (from.type === 'Table' && from.alias) return from.alias;
|
|
299
|
+
return this.table.name;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
297
302
|
|
|
298
303
|
export type { RelationResult } from './relation-projection-helper.js';
|
|
@@ -2,6 +2,9 @@ import { BelongsToManyRelation } from '../schema/relation.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Builds a default set of pivot columns, excluding keys used for joins.
|
|
5
|
+
* @param rel - The BelongsToMany relation definition
|
|
6
|
+
* @param pivotPk - The primary key column name of the pivot table
|
|
7
|
+
* @returns Array of column names that can be included in pivot table selections
|
|
5
8
|
*/
|
|
6
9
|
export const buildDefaultPivotColumns = (
|
|
7
10
|
rel: BelongsToManyRelation,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SelectQueryNode } from '../../core/ast/query.js';
|
|
2
|
+
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
|
|
3
|
+
import { QueryAstService } from '../query-ast-service.js';
|
|
4
|
+
import { SelectQueryState } from '../select-query-state.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Facet responsible for Common Table Expressions (WITH clauses)
|
|
8
|
+
*/
|
|
9
|
+
export class SelectCTEFacet {
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new SelectCTEFacet instance
|
|
12
|
+
* @param env - Query builder environment
|
|
13
|
+
* @param createAstService - Function to create AST service
|
|
14
|
+
*/
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly env: SelectQueryBuilderEnvironment,
|
|
17
|
+
private readonly createAstService: (state: SelectQueryState) => QueryAstService
|
|
18
|
+
) { }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Adds a Common Table Expression to the query
|
|
22
|
+
* @param context - Current query context
|
|
23
|
+
* @param name - CTE name
|
|
24
|
+
* @param subAst - CTE query AST
|
|
25
|
+
* @param columns - Optional column names
|
|
26
|
+
* @param recursive - Whether the CTE is recursive
|
|
27
|
+
* @returns Updated query context with CTE
|
|
28
|
+
*/
|
|
29
|
+
withCTE(
|
|
30
|
+
context: SelectQueryBuilderContext,
|
|
31
|
+
name: string,
|
|
32
|
+
subAst: SelectQueryNode,
|
|
33
|
+
columns: string[] | undefined,
|
|
34
|
+
recursive: boolean
|
|
35
|
+
): SelectQueryBuilderContext {
|
|
36
|
+
const astService = this.createAstService(context.state);
|
|
37
|
+
const nextState = astService.withCte(name, subAst, columns, recursive);
|
|
38
|
+
return { state: nextState, hydration: context.hydration };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SelectQueryNode } from '../../core/ast/query.js';
|
|
2
|
+
import { OperandNode } from '../../core/ast/expression.js';
|
|
3
|
+
import { derivedTable, fnTable } from '../../core/ast/builders.js';
|
|
4
|
+
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
|
|
5
|
+
import { QueryAstService } from '../query-ast-service.js';
|
|
6
|
+
import { SelectQueryState } from '../select-query-state.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Facet responsible for FROM clause operations
|
|
10
|
+
*/
|
|
11
|
+
export class SelectFromFacet {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new SelectFromFacet instance
|
|
14
|
+
* @param env - Query builder environment
|
|
15
|
+
* @param createAstService - Function to create AST service
|
|
16
|
+
*/
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly env: SelectQueryBuilderEnvironment,
|
|
19
|
+
private readonly createAstService: (state: SelectQueryState) => QueryAstService
|
|
20
|
+
) { }
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Applies an alias to the FROM table
|
|
24
|
+
* @param context - Current query context
|
|
25
|
+
* @param alias - Alias to apply
|
|
26
|
+
* @returns Updated query context with aliased FROM
|
|
27
|
+
*/
|
|
28
|
+
as(context: SelectQueryBuilderContext, alias: string): SelectQueryBuilderContext {
|
|
29
|
+
const from = context.state.ast.from;
|
|
30
|
+
if (from.type !== 'Table') {
|
|
31
|
+
throw new Error('Cannot alias non-table FROM sources');
|
|
32
|
+
}
|
|
33
|
+
const nextFrom = { ...from, alias };
|
|
34
|
+
const astService = this.createAstService(context.state);
|
|
35
|
+
const nextState = astService.withFrom(nextFrom);
|
|
36
|
+
return { state: nextState, hydration: context.hydration };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sets the FROM clause to a subquery
|
|
41
|
+
* @param context - Current query context
|
|
42
|
+
* @param subAst - Subquery AST
|
|
43
|
+
* @param alias - Alias for the subquery
|
|
44
|
+
* @param columnAliases - Optional column aliases
|
|
45
|
+
* @returns Updated query context with subquery FROM
|
|
46
|
+
*/
|
|
47
|
+
fromSubquery(
|
|
48
|
+
context: SelectQueryBuilderContext,
|
|
49
|
+
subAst: SelectQueryNode,
|
|
50
|
+
alias: string,
|
|
51
|
+
columnAliases?: string[]
|
|
52
|
+
): SelectQueryBuilderContext {
|
|
53
|
+
const fromNode = derivedTable(subAst, alias, columnAliases);
|
|
54
|
+
const astService = this.createAstService(context.state);
|
|
55
|
+
const nextState = astService.withFrom(fromNode);
|
|
56
|
+
return { state: nextState, hydration: context.hydration };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sets the FROM clause to a function table
|
|
61
|
+
* @param context - Current query context
|
|
62
|
+
* @param name - Function name
|
|
63
|
+
* @param args - Function arguments
|
|
64
|
+
* @param alias - Optional alias for the function table
|
|
65
|
+
* @param options - Optional function table options
|
|
66
|
+
* @returns Updated query context with function table FROM
|
|
67
|
+
*/
|
|
68
|
+
fromFunctionTable(
|
|
69
|
+
context: SelectQueryBuilderContext,
|
|
70
|
+
name: string,
|
|
71
|
+
args: OperandNode[],
|
|
72
|
+
alias?: string,
|
|
73
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
74
|
+
): SelectQueryBuilderContext {
|
|
75
|
+
const functionTable = fnTable(name, args, alias, options);
|
|
76
|
+
const astService = this.createAstService(context.state);
|
|
77
|
+
const nextState = astService.withFrom(functionTable);
|
|
78
|
+
return { state: nextState, hydration: context.hydration };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TableDef } from '../../schema/table.js';
|
|
2
|
+
import { BinaryExpressionNode } from '../../core/ast/expression.js';
|
|
3
|
+
import { SelectQueryNode } from '../../core/ast/query.js';
|
|
4
|
+
import { JoinKind } from '../../core/sql/sql.js';
|
|
5
|
+
import { derivedTable, fnTable } from '../../core/ast/builders.js';
|
|
6
|
+
import { createJoinNode } from '../../core/ast/join-node.js';
|
|
7
|
+
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
|
|
8
|
+
import { QueryAstService } from '../query-ast-service.js';
|
|
9
|
+
import { OperandNode } from '../../core/ast/expression.js';
|
|
10
|
+
import { SelectQueryState } from '../select-query-state.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Facet responsible for JOIN operations
|
|
14
|
+
*/
|
|
15
|
+
export class SelectJoinFacet {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly env: SelectQueryBuilderEnvironment,
|
|
18
|
+
private readonly createAstService: (state: SelectQueryState) => QueryAstService
|
|
19
|
+
) { }
|
|
20
|
+
|
|
21
|
+
applyJoin(
|
|
22
|
+
context: SelectQueryBuilderContext,
|
|
23
|
+
table: TableDef,
|
|
24
|
+
condition: BinaryExpressionNode,
|
|
25
|
+
kind: JoinKind
|
|
26
|
+
): SelectQueryBuilderContext {
|
|
27
|
+
const joinNode = createJoinNode(kind, { type: 'Table', name: table.name, schema: table.schema }, condition);
|
|
28
|
+
const astService = this.createAstService(context.state);
|
|
29
|
+
const nextState = astService.withJoin(joinNode);
|
|
30
|
+
return { state: nextState, hydration: context.hydration };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
joinSubquery(
|
|
34
|
+
context: SelectQueryBuilderContext,
|
|
35
|
+
subAst: SelectQueryNode,
|
|
36
|
+
alias: string,
|
|
37
|
+
condition: BinaryExpressionNode,
|
|
38
|
+
joinKind: JoinKind,
|
|
39
|
+
columnAliases?: string[]
|
|
40
|
+
): SelectQueryBuilderContext {
|
|
41
|
+
const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
|
|
42
|
+
const astService = this.createAstService(context.state);
|
|
43
|
+
const nextState = astService.withJoin(joinNode);
|
|
44
|
+
return { state: nextState, hydration: context.hydration };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
joinFunctionTable(
|
|
48
|
+
context: SelectQueryBuilderContext,
|
|
49
|
+
name: string,
|
|
50
|
+
args: OperandNode[],
|
|
51
|
+
alias: string,
|
|
52
|
+
condition: BinaryExpressionNode,
|
|
53
|
+
joinKind: JoinKind,
|
|
54
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
55
|
+
): SelectQueryBuilderContext {
|
|
56
|
+
const functionTable = fnTable(name, args, alias, options);
|
|
57
|
+
const joinNode = createJoinNode(joinKind, functionTable, condition);
|
|
58
|
+
const astService = this.createAstService(context.state);
|
|
59
|
+
const nextState = astService.withJoin(joinNode);
|
|
60
|
+
return { state: nextState, hydration: context.hydration };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ColumnDef } from '../../schema/column.js';
|
|
2
|
+
import { ExpressionNode } from '../../core/ast/expression.js';
|
|
3
|
+
import { OrderingTerm } from '../../core/ast/query.js';
|
|
4
|
+
import { OrderDirection } from '../../core/sql/sql.js';
|
|
5
|
+
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
|
|
6
|
+
import { QueryAstService } from '../query-ast-service.js';
|
|
7
|
+
import { SelectQueryState } from '../select-query-state.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Facet responsible for filtering and ordering operations
|
|
11
|
+
*/
|
|
12
|
+
export class SelectPredicateFacet {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new SelectPredicateFacet instance
|
|
15
|
+
* @param env - Query builder environment
|
|
16
|
+
* @param createAstService - Function to create AST service
|
|
17
|
+
*/
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly env: SelectQueryBuilderEnvironment,
|
|
20
|
+
private readonly createAstService: (state: SelectQueryState) => QueryAstService
|
|
21
|
+
) { }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Adds a WHERE condition to the query
|
|
25
|
+
* @param context - Current query context
|
|
26
|
+
* @param expr - WHERE expression
|
|
27
|
+
* @returns Updated query context with WHERE condition
|
|
28
|
+
*/
|
|
29
|
+
where(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
|
|
30
|
+
const astService = this.createAstService(context.state);
|
|
31
|
+
const nextState = astService.withWhere(expr);
|
|
32
|
+
return { state: nextState, hydration: context.hydration };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Adds a GROUP BY clause to the query
|
|
37
|
+
* @param context - Current query context
|
|
38
|
+
* @param term - Column or ordering term to group by
|
|
39
|
+
* @returns Updated query context with GROUP BY clause
|
|
40
|
+
*/
|
|
41
|
+
groupBy(context: SelectQueryBuilderContext, term: ColumnDef | OrderingTerm): SelectQueryBuilderContext {
|
|
42
|
+
const astService = this.createAstService(context.state);
|
|
43
|
+
const nextState = astService.withGroupBy(term);
|
|
44
|
+
return { state: nextState, hydration: context.hydration };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Adds a HAVING condition to the query
|
|
49
|
+
* @param context - Current query context
|
|
50
|
+
* @param expr - HAVING expression
|
|
51
|
+
* @returns Updated query context with HAVING condition
|
|
52
|
+
*/
|
|
53
|
+
having(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
|
|
54
|
+
const astService = this.createAstService(context.state);
|
|
55
|
+
const nextState = astService.withHaving(expr);
|
|
56
|
+
return { state: nextState, hydration: context.hydration };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds an ORDER BY clause to the query
|
|
61
|
+
* @param context - Current query context
|
|
62
|
+
* @param term - Column or ordering term to order by
|
|
63
|
+
* @param direction - Order direction
|
|
64
|
+
* @param nulls - Nulls ordering
|
|
65
|
+
* @param collation - Collation
|
|
66
|
+
* @returns Updated query context with ORDER BY clause
|
|
67
|
+
*/
|
|
68
|
+
orderBy(
|
|
69
|
+
context: SelectQueryBuilderContext,
|
|
70
|
+
term: ColumnDef | OrderingTerm,
|
|
71
|
+
direction: OrderDirection,
|
|
72
|
+
nulls?: 'FIRST' | 'LAST',
|
|
73
|
+
collation?: string
|
|
74
|
+
): SelectQueryBuilderContext {
|
|
75
|
+
const astService = this.createAstService(context.state);
|
|
76
|
+
const nextState = astService.withOrderBy(term, direction, nulls, collation);
|
|
77
|
+
return { state: nextState, hydration: context.hydration };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adds a LIMIT clause to the query
|
|
82
|
+
* @param context - Current query context
|
|
83
|
+
* @param n - Maximum number of rows
|
|
84
|
+
* @returns Updated query context with LIMIT clause
|
|
85
|
+
*/
|
|
86
|
+
limit(context: SelectQueryBuilderContext, n: number): SelectQueryBuilderContext {
|
|
87
|
+
const astService = this.createAstService(context.state);
|
|
88
|
+
const nextState = astService.withLimit(n);
|
|
89
|
+
return { state: nextState, hydration: context.hydration };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Adds an OFFSET clause to the query
|
|
94
|
+
* @param context - Current query context
|
|
95
|
+
* @param n - Number of rows to skip
|
|
96
|
+
* @returns Updated query context with OFFSET clause
|
|
97
|
+
*/
|
|
98
|
+
offset(context: SelectQueryBuilderContext, n: number): SelectQueryBuilderContext {
|
|
99
|
+
const astService = this.createAstService(context.state);
|
|
100
|
+
const nextState = astService.withOffset(n);
|
|
101
|
+
return { state: nextState, hydration: context.hydration };
|
|
102
|
+
}
|
|
103
|
+
}
|