metal-orm 1.0.7 → 1.0.9
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 +133 -121
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation';
|
|
3
|
-
import { ExpressionNode, eq, and } from '../ast/expression';
|
|
4
|
-
import { findPrimaryKey } from './hydration-planner';
|
|
5
|
-
import { JoinNode } from '../ast/join';
|
|
6
|
-
import { JoinKind } from '../
|
|
7
|
-
import { createJoinNode } from '../
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Utility function to handle unreachable code paths
|
|
11
|
-
* @param value - Value that should never occur
|
|
12
|
-
* @throws Error indicating unhandled relation type
|
|
13
|
-
*/
|
|
14
|
-
const assertNever = (value: never): never => {
|
|
15
|
-
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Builds the base condition for a relation join
|
|
20
|
-
* @param root - Root table definition
|
|
21
|
-
* @param relation - Relation definition
|
|
22
|
-
* @returns Expression node representing the join condition
|
|
23
|
-
*/
|
|
24
|
-
const baseRelationCondition = (root: TableDef, relation: RelationDef): ExpressionNode => {
|
|
25
|
-
const defaultLocalKey =
|
|
26
|
-
relation.type === RelationKinds.HasMany
|
|
27
|
-
? findPrimaryKey(root)
|
|
28
|
-
: findPrimaryKey(relation.target);
|
|
29
|
-
const localKey = relation.localKey || defaultLocalKey;
|
|
30
|
-
|
|
31
|
-
switch (relation.type) {
|
|
32
|
-
case RelationKinds.HasMany:
|
|
33
|
-
return eq(
|
|
34
|
-
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
35
|
-
{ type: 'Column', table: root.name, name: localKey }
|
|
36
|
-
);
|
|
37
|
-
case RelationKinds.BelongsTo:
|
|
38
|
-
return eq(
|
|
39
|
-
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
40
|
-
{ type: 'Column', table: root.name, name: relation.foreignKey }
|
|
41
|
-
);
|
|
42
|
-
case RelationKinds.BelongsToMany:
|
|
43
|
-
throw new Error('BelongsToMany relations do not support the standard join condition builder');
|
|
44
|
-
default:
|
|
45
|
-
return assertNever(relation);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Builds the join nodes required to include a BelongsToMany relation.
|
|
51
|
-
*/
|
|
52
|
-
export const buildBelongsToManyJoins = (
|
|
53
|
-
root: TableDef,
|
|
54
|
-
relationName: string,
|
|
55
|
-
relation: BelongsToManyRelation,
|
|
56
|
-
joinKind: JoinKind,
|
|
57
|
-
extra?: ExpressionNode
|
|
58
|
-
): JoinNode[] => {
|
|
59
|
-
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
60
|
-
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
61
|
-
|
|
62
|
-
const pivotCondition = eq(
|
|
63
|
-
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
64
|
-
{ type: 'Column', table: root.name, name: rootKey }
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
|
|
68
|
-
|
|
69
|
-
let targetCondition: ExpressionNode = eq(
|
|
70
|
-
{ type: 'Column', table: relation.target.name, name: targetKey },
|
|
71
|
-
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (extra) {
|
|
75
|
-
targetCondition = and(targetCondition, extra);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const targetJoin = createJoinNode(
|
|
79
|
-
joinKind,
|
|
80
|
-
relation.target.name,
|
|
81
|
-
targetCondition,
|
|
82
|
-
relationName
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return [pivotJoin, targetJoin];
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Builds a relation join condition with optional extra conditions
|
|
90
|
-
* @param root - Root table definition
|
|
91
|
-
* @param relation - Relation definition
|
|
92
|
-
* @param extra - Optional additional expression to combine with AND
|
|
93
|
-
* @returns Expression node representing the complete join condition
|
|
94
|
-
*/
|
|
95
|
-
export const buildRelationJoinCondition = (
|
|
96
|
-
root: TableDef,
|
|
97
|
-
relation: RelationDef,
|
|
98
|
-
extra?: ExpressionNode
|
|
99
|
-
): ExpressionNode => {
|
|
100
|
-
const base = baseRelationCondition(root, relation);
|
|
101
|
-
return extra ? and(base, extra) : base;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Builds a relation correlation condition for subqueries
|
|
106
|
-
* @param root - Root table definition
|
|
107
|
-
* @param relation - Relation definition
|
|
108
|
-
* @returns Expression node representing the correlation condition
|
|
109
|
-
*/
|
|
110
|
-
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef): ExpressionNode => {
|
|
111
|
-
return baseRelationCondition(root, relation);
|
|
112
|
-
};
|
|
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 { findPrimaryKey } from './hydration-planner.js';
|
|
5
|
+
import { JoinNode } from '../core/ast/join.js';
|
|
6
|
+
import { JoinKind } from '../core/sql/sql.js';
|
|
7
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Utility function to handle unreachable code paths
|
|
11
|
+
* @param value - Value that should never occur
|
|
12
|
+
* @throws Error indicating unhandled relation type
|
|
13
|
+
*/
|
|
14
|
+
const assertNever = (value: never): never => {
|
|
15
|
+
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Builds the base condition for a relation join
|
|
20
|
+
* @param root - Root table definition
|
|
21
|
+
* @param relation - Relation definition
|
|
22
|
+
* @returns Expression node representing the join condition
|
|
23
|
+
*/
|
|
24
|
+
const baseRelationCondition = (root: TableDef, relation: RelationDef): ExpressionNode => {
|
|
25
|
+
const defaultLocalKey =
|
|
26
|
+
relation.type === RelationKinds.HasMany
|
|
27
|
+
? findPrimaryKey(root)
|
|
28
|
+
: findPrimaryKey(relation.target);
|
|
29
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
30
|
+
|
|
31
|
+
switch (relation.type) {
|
|
32
|
+
case RelationKinds.HasMany:
|
|
33
|
+
return eq(
|
|
34
|
+
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
35
|
+
{ type: 'Column', table: root.name, name: localKey }
|
|
36
|
+
);
|
|
37
|
+
case RelationKinds.BelongsTo:
|
|
38
|
+
return eq(
|
|
39
|
+
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
40
|
+
{ type: 'Column', table: root.name, name: relation.foreignKey }
|
|
41
|
+
);
|
|
42
|
+
case RelationKinds.BelongsToMany:
|
|
43
|
+
throw new Error('BelongsToMany relations do not support the standard join condition builder');
|
|
44
|
+
default:
|
|
45
|
+
return assertNever(relation);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Builds the join nodes required to include a BelongsToMany relation.
|
|
51
|
+
*/
|
|
52
|
+
export const buildBelongsToManyJoins = (
|
|
53
|
+
root: TableDef,
|
|
54
|
+
relationName: string,
|
|
55
|
+
relation: BelongsToManyRelation,
|
|
56
|
+
joinKind: JoinKind,
|
|
57
|
+
extra?: ExpressionNode
|
|
58
|
+
): JoinNode[] => {
|
|
59
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
60
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
61
|
+
|
|
62
|
+
const pivotCondition = eq(
|
|
63
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
64
|
+
{ type: 'Column', table: root.name, name: rootKey }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
|
|
68
|
+
|
|
69
|
+
let targetCondition: ExpressionNode = eq(
|
|
70
|
+
{ type: 'Column', table: relation.target.name, name: targetKey },
|
|
71
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (extra) {
|
|
75
|
+
targetCondition = and(targetCondition, extra);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const targetJoin = createJoinNode(
|
|
79
|
+
joinKind,
|
|
80
|
+
relation.target.name,
|
|
81
|
+
targetCondition,
|
|
82
|
+
relationName
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return [pivotJoin, targetJoin];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builds a relation join condition with optional extra conditions
|
|
90
|
+
* @param root - Root table definition
|
|
91
|
+
* @param relation - Relation definition
|
|
92
|
+
* @param extra - Optional additional expression to combine with AND
|
|
93
|
+
* @returns Expression node representing the complete join condition
|
|
94
|
+
*/
|
|
95
|
+
export const buildRelationJoinCondition = (
|
|
96
|
+
root: TableDef,
|
|
97
|
+
relation: RelationDef,
|
|
98
|
+
extra?: ExpressionNode
|
|
99
|
+
): ExpressionNode => {
|
|
100
|
+
const base = baseRelationCondition(root, relation);
|
|
101
|
+
return extra ? and(base, extra) : base;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Builds a relation correlation condition for subqueries
|
|
106
|
+
* @param root - Root table definition
|
|
107
|
+
* @param relation - Relation definition
|
|
108
|
+
* @returns Expression node representing the correlation condition
|
|
109
|
+
*/
|
|
110
|
+
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef): ExpressionNode => {
|
|
111
|
+
return baseRelationCondition(root, relation);
|
|
112
|
+
};
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import { ExpressionNode } from '
|
|
2
|
-
import { SelectQueryNode } from '
|
|
3
|
-
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '
|
|
4
|
-
import { JoinKind } from '
|
|
5
|
-
import { RelationIncludeOptions } from '
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Manages relation operations (joins, includes, etc.) for query building
|
|
9
|
-
*/
|
|
10
|
-
export class RelationManager {
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new RelationManager instance
|
|
13
|
-
* @param env - Query builder environment
|
|
14
|
-
*/
|
|
15
|
-
constructor(private readonly env: SelectQueryBuilderEnvironment) {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Matches records based on a relation with an optional predicate
|
|
19
|
-
* @param context - Current query context
|
|
20
|
-
* @param relationName - Name of the relation to match
|
|
21
|
-
* @param predicate - Optional predicate expression
|
|
22
|
-
* @returns Updated query context with relation match
|
|
23
|
-
*/
|
|
24
|
-
match(context: SelectQueryBuilderContext, relationName: string, predicate?: ExpressionNode): SelectQueryBuilderContext {
|
|
25
|
-
const result = this.createService(context).match(relationName, predicate);
|
|
26
|
-
return { state: result.state, hydration: result.hydration };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Joins a relation to the query
|
|
31
|
-
* @param context - Current query context
|
|
32
|
-
* @param relationName - Name of the relation to join
|
|
33
|
-
* @param joinKind - Type of join to use
|
|
34
|
-
* @param extraCondition - Additional join condition
|
|
35
|
-
* @returns Updated query context with relation join
|
|
36
|
-
*/
|
|
37
|
-
joinRelation(
|
|
38
|
-
context: SelectQueryBuilderContext,
|
|
39
|
-
relationName: string,
|
|
40
|
-
joinKind: JoinKind,
|
|
41
|
-
extraCondition?: ExpressionNode
|
|
42
|
-
): SelectQueryBuilderContext {
|
|
43
|
-
const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
|
|
44
|
-
return { state: result.state, hydration: result.hydration };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Includes a relation in the query result
|
|
49
|
-
* @param context - Current query context
|
|
50
|
-
* @param relationName - Name of the relation to include
|
|
51
|
-
* @param options - Options for relation inclusion
|
|
52
|
-
* @returns Updated query context with included relation
|
|
53
|
-
*/
|
|
54
|
-
include(
|
|
55
|
-
context: SelectQueryBuilderContext,
|
|
56
|
-
relationName: string,
|
|
57
|
-
options?: RelationIncludeOptions
|
|
58
|
-
): SelectQueryBuilderContext {
|
|
59
|
-
const result = this.createService(context).include(relationName, options);
|
|
60
|
-
return { state: result.state, hydration: result.hydration };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Applies relation correlation to a query AST
|
|
65
|
-
* @param context - Current query context
|
|
66
|
-
* @param relationName - Name of the relation
|
|
67
|
-
* @param ast - Query AST to modify
|
|
68
|
-
* @returns Modified query AST with relation correlation
|
|
69
|
-
*/
|
|
70
|
-
applyRelationCorrelation(context: SelectQueryBuilderContext, relationName: string, ast: SelectQueryNode): SelectQueryNode {
|
|
71
|
-
return this.createService(context).applyRelationCorrelation(relationName, ast);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Creates a relation service instance
|
|
76
|
-
* @param context - Current query context
|
|
77
|
-
* @returns Relation service instance
|
|
78
|
-
*/
|
|
79
|
-
private createService(context: SelectQueryBuilderContext) {
|
|
80
|
-
return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
import { ExpressionNode } from '../core/ast/expression.js';
|
|
2
|
+
import { SelectQueryNode } from '../core/ast/query.js';
|
|
3
|
+
import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from './select-query-builder-deps.js';
|
|
4
|
+
import { JoinKind } from '../core/sql/sql.js';
|
|
5
|
+
import { RelationIncludeOptions } from './relation-types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages relation operations (joins, includes, etc.) for query building
|
|
9
|
+
*/
|
|
10
|
+
export class RelationManager {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new RelationManager instance
|
|
13
|
+
* @param env - Query builder environment
|
|
14
|
+
*/
|
|
15
|
+
constructor(private readonly env: SelectQueryBuilderEnvironment) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches records based on a relation with an optional predicate
|
|
19
|
+
* @param context - Current query context
|
|
20
|
+
* @param relationName - Name of the relation to match
|
|
21
|
+
* @param predicate - Optional predicate expression
|
|
22
|
+
* @returns Updated query context with relation match
|
|
23
|
+
*/
|
|
24
|
+
match(context: SelectQueryBuilderContext, relationName: string, predicate?: ExpressionNode): SelectQueryBuilderContext {
|
|
25
|
+
const result = this.createService(context).match(relationName, predicate);
|
|
26
|
+
return { state: result.state, hydration: result.hydration };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Joins a relation to the query
|
|
31
|
+
* @param context - Current query context
|
|
32
|
+
* @param relationName - Name of the relation to join
|
|
33
|
+
* @param joinKind - Type of join to use
|
|
34
|
+
* @param extraCondition - Additional join condition
|
|
35
|
+
* @returns Updated query context with relation join
|
|
36
|
+
*/
|
|
37
|
+
joinRelation(
|
|
38
|
+
context: SelectQueryBuilderContext,
|
|
39
|
+
relationName: string,
|
|
40
|
+
joinKind: JoinKind,
|
|
41
|
+
extraCondition?: ExpressionNode
|
|
42
|
+
): SelectQueryBuilderContext {
|
|
43
|
+
const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
|
|
44
|
+
return { state: result.state, hydration: result.hydration };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Includes a relation in the query result
|
|
49
|
+
* @param context - Current query context
|
|
50
|
+
* @param relationName - Name of the relation to include
|
|
51
|
+
* @param options - Options for relation inclusion
|
|
52
|
+
* @returns Updated query context with included relation
|
|
53
|
+
*/
|
|
54
|
+
include(
|
|
55
|
+
context: SelectQueryBuilderContext,
|
|
56
|
+
relationName: string,
|
|
57
|
+
options?: RelationIncludeOptions
|
|
58
|
+
): SelectQueryBuilderContext {
|
|
59
|
+
const result = this.createService(context).include(relationName, options);
|
|
60
|
+
return { state: result.state, hydration: result.hydration };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Applies relation correlation to a query AST
|
|
65
|
+
* @param context - Current query context
|
|
66
|
+
* @param relationName - Name of the relation
|
|
67
|
+
* @param ast - Query AST to modify
|
|
68
|
+
* @returns Modified query AST with relation correlation
|
|
69
|
+
*/
|
|
70
|
+
applyRelationCorrelation(context: SelectQueryBuilderContext, relationName: string, ast: SelectQueryNode): SelectQueryNode {
|
|
71
|
+
return this.createService(context).applyRelationCorrelation(relationName, ast);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a relation service instance
|
|
76
|
+
* @param context - Current query context
|
|
77
|
+
* @returns Relation service instance
|
|
78
|
+
*/
|
|
79
|
+
private createService(context: SelectQueryBuilderContext) {
|
|
80
|
+
return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { ColumnDef } from '../schema/column';
|
|
3
|
-
import { SelectQueryState } from './select-query-state';
|
|
4
|
-
import { HydrationManager } from './hydration-manager';
|
|
5
|
-
import { ColumnNode } from '../ast/expression';
|
|
6
|
-
import { findPrimaryKey } from './hydration-planner';
|
|
7
|
-
import { isRelationAlias } from '
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Result of a relation operation
|
|
11
|
-
*/
|
|
12
|
-
export interface RelationResult {
|
|
13
|
-
/**
|
|
14
|
-
* Updated query state
|
|
15
|
-
*/
|
|
16
|
-
state: SelectQueryState;
|
|
17
|
-
/**
|
|
18
|
-
* Updated hydration manager
|
|
19
|
-
*/
|
|
20
|
-
hydration: HydrationManager;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Callback function for selecting columns
|
|
25
|
-
*/
|
|
26
|
-
type SelectColumnsCallback = (
|
|
27
|
-
state: SelectQueryState,
|
|
28
|
-
hydration: HydrationManager,
|
|
29
|
-
columns: Record<string, ColumnDef>
|
|
30
|
-
) => RelationResult;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Helper class for managing relation projections in queries
|
|
34
|
-
*/
|
|
35
|
-
export class RelationProjectionHelper {
|
|
36
|
-
/**
|
|
37
|
-
* Creates a new RelationProjectionHelper instance
|
|
38
|
-
* @param table - Table definition
|
|
39
|
-
* @param selectColumns - Callback for selecting columns
|
|
40
|
-
*/
|
|
41
|
-
constructor(
|
|
42
|
-
private readonly table: TableDef,
|
|
43
|
-
private readonly selectColumns: SelectColumnsCallback
|
|
44
|
-
) {}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Ensures base projection is included in the query
|
|
48
|
-
* @param state - Current query state
|
|
49
|
-
* @param hydration - Hydration manager
|
|
50
|
-
* @returns Relation result with updated state and hydration
|
|
51
|
-
*/
|
|
52
|
-
ensureBaseProjection(state: SelectQueryState, hydration: HydrationManager): RelationResult {
|
|
53
|
-
const primaryKey = findPrimaryKey(this.table);
|
|
54
|
-
|
|
55
|
-
if (!this.hasBaseProjection(state)) {
|
|
56
|
-
return this.selectColumns(state, hydration, this.getBaseColumns());
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
|
|
60
|
-
return this.selectColumns(state, hydration, {
|
|
61
|
-
[primaryKey]: this.table.columns[primaryKey]
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return { state, hydration };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Checks if base projection exists in the query
|
|
70
|
-
* @param state - Current query state
|
|
71
|
-
* @returns True if base projection exists
|
|
72
|
-
*/
|
|
73
|
-
private hasBaseProjection(state: SelectQueryState): boolean {
|
|
74
|
-
return state.ast.columns.some(col => !isRelationAlias((col as ColumnNode).alias));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Checks if primary key is selected in the query
|
|
79
|
-
* @param state - Current query state
|
|
80
|
-
* @param primaryKey - Primary key name
|
|
81
|
-
* @returns True if primary key is selected
|
|
82
|
-
*/
|
|
83
|
-
private hasPrimarySelected(state: SelectQueryState, primaryKey: string): boolean {
|
|
84
|
-
return state.ast.columns.some(col => {
|
|
85
|
-
const alias = (col as ColumnNode).alias;
|
|
86
|
-
const name = alias || (col as ColumnNode).name;
|
|
87
|
-
return !isRelationAlias(alias) && name === primaryKey;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Gets all base columns for the table
|
|
93
|
-
* @returns Record of all table columns
|
|
94
|
-
*/
|
|
95
|
-
private getBaseColumns(): Record<string, ColumnDef> {
|
|
96
|
-
return Object.keys(this.table.columns).reduce((acc, key) => {
|
|
97
|
-
acc[key] = (this.table.columns as Record<string, ColumnDef>)[key];
|
|
98
|
-
return acc;
|
|
99
|
-
}, {} as Record<string, ColumnDef>);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { ColumnDef } from '../schema/column.js';
|
|
3
|
+
import { SelectQueryState } from './select-query-state.js';
|
|
4
|
+
import { HydrationManager } from './hydration-manager.js';
|
|
5
|
+
import { ColumnNode } from '../core/ast/expression.js';
|
|
6
|
+
import { findPrimaryKey } from './hydration-planner.js';
|
|
7
|
+
import { isRelationAlias } from './relation-alias.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of a relation operation
|
|
11
|
+
*/
|
|
12
|
+
export interface RelationResult {
|
|
13
|
+
/**
|
|
14
|
+
* Updated query state
|
|
15
|
+
*/
|
|
16
|
+
state: SelectQueryState;
|
|
17
|
+
/**
|
|
18
|
+
* Updated hydration manager
|
|
19
|
+
*/
|
|
20
|
+
hydration: HydrationManager;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Callback function for selecting columns
|
|
25
|
+
*/
|
|
26
|
+
type SelectColumnsCallback = (
|
|
27
|
+
state: SelectQueryState,
|
|
28
|
+
hydration: HydrationManager,
|
|
29
|
+
columns: Record<string, ColumnDef>
|
|
30
|
+
) => RelationResult;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Helper class for managing relation projections in queries
|
|
34
|
+
*/
|
|
35
|
+
export class RelationProjectionHelper {
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new RelationProjectionHelper instance
|
|
38
|
+
* @param table - Table definition
|
|
39
|
+
* @param selectColumns - Callback for selecting columns
|
|
40
|
+
*/
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly table: TableDef,
|
|
43
|
+
private readonly selectColumns: SelectColumnsCallback
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Ensures base projection is included in the query
|
|
48
|
+
* @param state - Current query state
|
|
49
|
+
* @param hydration - Hydration manager
|
|
50
|
+
* @returns Relation result with updated state and hydration
|
|
51
|
+
*/
|
|
52
|
+
ensureBaseProjection(state: SelectQueryState, hydration: HydrationManager): RelationResult {
|
|
53
|
+
const primaryKey = findPrimaryKey(this.table);
|
|
54
|
+
|
|
55
|
+
if (!this.hasBaseProjection(state)) {
|
|
56
|
+
return this.selectColumns(state, hydration, this.getBaseColumns());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
|
|
60
|
+
return this.selectColumns(state, hydration, {
|
|
61
|
+
[primaryKey]: this.table.columns[primaryKey]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { state, hydration };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Checks if base projection exists in the query
|
|
70
|
+
* @param state - Current query state
|
|
71
|
+
* @returns True if base projection exists
|
|
72
|
+
*/
|
|
73
|
+
private hasBaseProjection(state: SelectQueryState): boolean {
|
|
74
|
+
return state.ast.columns.some(col => !isRelationAlias((col as ColumnNode).alias));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Checks if primary key is selected in the query
|
|
79
|
+
* @param state - Current query state
|
|
80
|
+
* @param primaryKey - Primary key name
|
|
81
|
+
* @returns True if primary key is selected
|
|
82
|
+
*/
|
|
83
|
+
private hasPrimarySelected(state: SelectQueryState, primaryKey: string): boolean {
|
|
84
|
+
return state.ast.columns.some(col => {
|
|
85
|
+
const alias = (col as ColumnNode).alias;
|
|
86
|
+
const name = alias || (col as ColumnNode).name;
|
|
87
|
+
return !isRelationAlias(alias) && name === primaryKey;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets all base columns for the table
|
|
93
|
+
* @returns Record of all table columns
|
|
94
|
+
*/
|
|
95
|
+
private getBaseColumns(): Record<string, ColumnDef> {
|
|
96
|
+
return Object.keys(this.table.columns).reduce((acc, key) => {
|
|
97
|
+
acc[key] = (this.table.columns as Record<string, ColumnDef>)[key];
|
|
98
|
+
return acc;
|
|
99
|
+
}, {} as Record<string, ColumnDef>);
|
|
100
|
+
}
|
|
101
|
+
}
|