metal-orm 1.0.43 → 1.0.44
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 +173 -30
- 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 +68 -98
- package/src/orm/relations/has-many.ts +44 -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
|
@@ -251,6 +251,11 @@ export class QueryAstService {
|
|
|
251
251
|
return existing ? and(existing, next) : next;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Normalizes an ordering term to a standard OrderingTerm
|
|
256
|
+
* @param term - Column definition or ordering term to normalize
|
|
257
|
+
* @returns Normalized ordering term
|
|
258
|
+
*/
|
|
254
259
|
private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
|
|
255
260
|
const from = this.state.ast.from;
|
|
256
261
|
const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SelectQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode } from '../core/ast/query.js';
|
|
2
|
+
import { TableDef } from '../schema/table.js';
|
|
3
|
+
import type { SelectQueryBuilder } from './select.js';
|
|
4
|
+
import type { UpdateQueryBuilder } from './update.js';
|
|
5
|
+
import type { DeleteQueryBuilder } from './delete.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves a SelectQueryBuilder or SelectQueryNode to a SelectQueryNode AST
|
|
9
|
+
* @param query - Query builder or AST node
|
|
10
|
+
* @returns SelectQueryNode AST
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSelectQuery<TSub extends TableDef>(
|
|
13
|
+
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
14
|
+
): SelectQueryNode {
|
|
15
|
+
const candidate = query as { getAST?: () => SelectQueryNode };
|
|
16
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
17
|
+
? candidate.getAST()
|
|
18
|
+
: (query as SelectQueryNode);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves a UpdateQueryBuilder or UpdateQueryNode to a UpdateQueryNode AST
|
|
23
|
+
* @param query - Query builder or AST node
|
|
24
|
+
* @returns UpdateQueryNode AST
|
|
25
|
+
*/
|
|
26
|
+
export function resolveUpdateQuery<T>(
|
|
27
|
+
query: UpdateQueryBuilder<T> | UpdateQueryNode
|
|
28
|
+
): UpdateQueryNode {
|
|
29
|
+
const candidate = query as { getAST?: () => UpdateQueryNode };
|
|
30
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
31
|
+
? candidate.getAST()
|
|
32
|
+
: (query as UpdateQueryNode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves a DeleteQueryBuilder or DeleteQueryNode to a DeleteQueryNode AST
|
|
37
|
+
* @param query - Query builder or AST node
|
|
38
|
+
* @returns DeleteQueryNode AST
|
|
39
|
+
*/
|
|
40
|
+
export function resolveDeleteQuery<T>(
|
|
41
|
+
query: DeleteQueryBuilder<T> | DeleteQueryNode
|
|
42
|
+
): DeleteQueryNode {
|
|
43
|
+
const candidate = query as { getAST?: () => DeleteQueryNode };
|
|
44
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
45
|
+
? candidate.getAST()
|
|
46
|
+
: (query as DeleteQueryNode);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolves a TableDef or TableSourceNode to a TableSourceNode
|
|
51
|
+
* @param source - Table definition or source node
|
|
52
|
+
* @returns TableSourceNode
|
|
53
|
+
*/
|
|
54
|
+
export function resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
|
|
55
|
+
if (isTableSourceNode(source)) {
|
|
56
|
+
return source;
|
|
57
|
+
}
|
|
58
|
+
return { type: 'Table', name: source.name, schema: source.schema };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolves a join target (TableDef, TableSourceNode, or string relation name)
|
|
63
|
+
* @param table - Join target
|
|
64
|
+
* @returns TableSourceNode or string
|
|
65
|
+
*/
|
|
66
|
+
export function resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
|
|
67
|
+
if (typeof table === 'string') return table;
|
|
68
|
+
return resolveTableSource(table);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if a value is a TableSourceNode
|
|
73
|
+
* @param source - Value to check
|
|
74
|
+
* @returns True if value is a TableSourceNode
|
|
75
|
+
*/
|
|
76
|
+
function isTableSourceNode(source: TableDef | TableSourceNode): source is TableSourceNode {
|
|
77
|
+
return typeof (source as TableSourceNode).type === 'string';
|
|
78
|
+
}
|
|
@@ -4,6 +4,11 @@ import { CommonTableExpressionNode } from '../core/ast/query.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Best-effort helper that tries to convert a raw column expression into a `ColumnNode`.
|
|
6
6
|
* This parser is intentionally limited; use it only for simple references or function calls.
|
|
7
|
+
*
|
|
8
|
+
* @param col - Raw column expression string (e.g., "column", "table.column", "COUNT(column)")
|
|
9
|
+
* @param tableName - Default table name to use when no table is specified
|
|
10
|
+
* @param ctes - Optional array of CTEs for context when parsing column references
|
|
11
|
+
* @returns A ColumnNode representing the parsed column expression
|
|
7
12
|
*/
|
|
8
13
|
export const parseRawColumn = (
|
|
9
14
|
col: string,
|
|
@@ -19,6 +19,9 @@ export interface RelationAliasParts {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Builds a relation alias from the relation name and column name components.
|
|
22
|
+
* @param relationName - The name of the relation
|
|
23
|
+
* @param columnName - The name of the column within the relation
|
|
24
|
+
* @returns A relation alias string in the format "relationName__columnName"
|
|
22
25
|
*/
|
|
23
26
|
export const makeRelationAlias = (relationName: string, columnName: string): string =>
|
|
24
27
|
`${relationName}${RELATION_SEPARATOR}${columnName}`;
|
|
@@ -26,6 +29,8 @@ export const makeRelationAlias = (relationName: string, columnName: string): str
|
|
|
26
29
|
/**
|
|
27
30
|
* Parses a relation alias into its relation/column components.
|
|
28
31
|
* Returns `null` when the alias does not follow the `relation__column` pattern.
|
|
32
|
+
* @param alias - The relation alias string to parse
|
|
33
|
+
* @returns Parsed relation alias parts or null if not a valid relation alias
|
|
29
34
|
*/
|
|
30
35
|
export const parseRelationAlias = (alias: string): RelationAliasParts | null => {
|
|
31
36
|
const idx = alias.indexOf(RELATION_SEPARATOR);
|
|
@@ -38,6 +43,8 @@ export const parseRelationAlias = (alias: string): RelationAliasParts | null =>
|
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Determines whether an alias represents a relation column by checking the `__` convention.
|
|
46
|
+
* @param alias - The alias string to check
|
|
47
|
+
* @returns True if the alias follows the relation alias pattern
|
|
41
48
|
*/
|
|
42
49
|
export const isRelationAlias = (alias?: string): boolean =>
|
|
43
50
|
!!alias && alias.includes(RELATION_SEPARATOR);
|
|
@@ -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
|
+
}
|