metal-orm 1.0.8 → 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 +12 -1
- 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,87 +1,87 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { RelationDef } from '../schema/relation';
|
|
3
|
-
import { SelectQueryNode, HydrationPlan } from '../ast/query';
|
|
4
|
-
import { HydrationPlanner } from './hydration-planner';
|
|
5
|
-
import { SelectQueryState, ProjectionNode } from './select-query-state';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Manages hydration planning for query results
|
|
9
|
-
*/
|
|
10
|
-
export class HydrationManager {
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new HydrationManager instance
|
|
13
|
-
* @param table - Table definition
|
|
14
|
-
* @param planner - Hydration planner
|
|
15
|
-
*/
|
|
16
|
-
constructor(
|
|
17
|
-
private readonly table: TableDef,
|
|
18
|
-
private readonly planner: HydrationPlanner
|
|
19
|
-
) {}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Creates a new HydrationManager with updated planner
|
|
23
|
-
* @param nextPlanner - Updated hydration planner
|
|
24
|
-
* @returns New HydrationManager instance
|
|
25
|
-
*/
|
|
26
|
-
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
27
|
-
return new HydrationManager(this.table, nextPlanner);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Handles column selection for hydration planning
|
|
32
|
-
* @param state - Current query state
|
|
33
|
-
* @param newColumns - Newly selected columns
|
|
34
|
-
* @returns Updated HydrationManager with captured columns
|
|
35
|
-
*/
|
|
36
|
-
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
37
|
-
const updated = this.planner.captureRootColumns(newColumns);
|
|
38
|
-
return this.clone(updated);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Handles relation inclusion for hydration planning
|
|
43
|
-
* @param state - Current query state
|
|
44
|
-
* @param relation - Relation definition
|
|
45
|
-
* @param relationName - Name of the relation
|
|
46
|
-
* @param aliasPrefix - Alias prefix for the relation
|
|
47
|
-
* @param targetColumns - Target columns to include
|
|
48
|
-
* @returns Updated HydrationManager with included relation
|
|
49
|
-
*/
|
|
50
|
-
onRelationIncluded(
|
|
51
|
-
state: SelectQueryState,
|
|
52
|
-
relation: RelationDef,
|
|
53
|
-
relationName: string,
|
|
54
|
-
aliasPrefix: string,
|
|
55
|
-
targetColumns: string[],
|
|
56
|
-
pivot?: { aliasPrefix: string; columns: string[] }
|
|
57
|
-
): HydrationManager {
|
|
58
|
-
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
59
|
-
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
60
|
-
return this.clone(next);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Applies hydration plan to the AST
|
|
65
|
-
* @param ast - Query AST to modify
|
|
66
|
-
* @returns AST with hydration metadata
|
|
67
|
-
*/
|
|
68
|
-
applyToAst(ast: SelectQueryNode): SelectQueryNode {
|
|
69
|
-
const plan = this.planner.getPlan();
|
|
70
|
-
if (!plan) return ast;
|
|
71
|
-
return {
|
|
72
|
-
...ast,
|
|
73
|
-
meta: {
|
|
74
|
-
...(ast.meta || {}),
|
|
75
|
-
hydration: plan
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Gets the current hydration plan
|
|
82
|
-
* @returns Hydration plan or undefined if none exists
|
|
83
|
-
*/
|
|
84
|
-
getPlan(): HydrationPlan | undefined {
|
|
85
|
-
return this.planner.getPlan();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationDef } from '../schema/relation.js';
|
|
3
|
+
import { SelectQueryNode, HydrationPlan } from '../core/ast/query.js';
|
|
4
|
+
import { HydrationPlanner } from './hydration-planner.js';
|
|
5
|
+
import { SelectQueryState, ProjectionNode } from './select-query-state.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages hydration planning for query results
|
|
9
|
+
*/
|
|
10
|
+
export class HydrationManager {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new HydrationManager instance
|
|
13
|
+
* @param table - Table definition
|
|
14
|
+
* @param planner - Hydration planner
|
|
15
|
+
*/
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly table: TableDef,
|
|
18
|
+
private readonly planner: HydrationPlanner
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new HydrationManager with updated planner
|
|
23
|
+
* @param nextPlanner - Updated hydration planner
|
|
24
|
+
* @returns New HydrationManager instance
|
|
25
|
+
*/
|
|
26
|
+
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
27
|
+
return new HydrationManager(this.table, nextPlanner);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handles column selection for hydration planning
|
|
32
|
+
* @param state - Current query state
|
|
33
|
+
* @param newColumns - Newly selected columns
|
|
34
|
+
* @returns Updated HydrationManager with captured columns
|
|
35
|
+
*/
|
|
36
|
+
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
37
|
+
const updated = this.planner.captureRootColumns(newColumns);
|
|
38
|
+
return this.clone(updated);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Handles relation inclusion for hydration planning
|
|
43
|
+
* @param state - Current query state
|
|
44
|
+
* @param relation - Relation definition
|
|
45
|
+
* @param relationName - Name of the relation
|
|
46
|
+
* @param aliasPrefix - Alias prefix for the relation
|
|
47
|
+
* @param targetColumns - Target columns to include
|
|
48
|
+
* @returns Updated HydrationManager with included relation
|
|
49
|
+
*/
|
|
50
|
+
onRelationIncluded(
|
|
51
|
+
state: SelectQueryState,
|
|
52
|
+
relation: RelationDef,
|
|
53
|
+
relationName: string,
|
|
54
|
+
aliasPrefix: string,
|
|
55
|
+
targetColumns: string[],
|
|
56
|
+
pivot?: { aliasPrefix: string; columns: string[] }
|
|
57
|
+
): HydrationManager {
|
|
58
|
+
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
59
|
+
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
60
|
+
return this.clone(next);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Applies hydration plan to the AST
|
|
65
|
+
* @param ast - Query AST to modify
|
|
66
|
+
* @returns AST with hydration metadata
|
|
67
|
+
*/
|
|
68
|
+
applyToAst(ast: SelectQueryNode): SelectQueryNode {
|
|
69
|
+
const plan = this.planner.getPlan();
|
|
70
|
+
if (!plan) return ast;
|
|
71
|
+
return {
|
|
72
|
+
...ast,
|
|
73
|
+
meta: {
|
|
74
|
+
...(ast.meta || {}),
|
|
75
|
+
hydration: plan
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the current hydration plan
|
|
82
|
+
* @returns Hydration plan or undefined if none exists
|
|
83
|
+
*/
|
|
84
|
+
getPlan(): HydrationPlan | undefined {
|
|
85
|
+
return this.planner.getPlan();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation';
|
|
3
|
-
import { ProjectionNode } from './select-query-state';
|
|
4
|
-
import { HydrationPlan, HydrationRelationPlan } from '../ast/query';
|
|
5
|
-
import { isRelationAlias } from '
|
|
6
|
-
import { buildDefaultPivotColumns } from './relation-utils';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Finds the primary key column name for a table
|
|
10
|
-
* @param table - Table definition
|
|
11
|
-
* @returns Name of the primary key column, defaults to 'id'
|
|
12
|
-
*/
|
|
13
|
-
export const findPrimaryKey = (table: TableDef): string => {
|
|
14
|
-
const pk = Object.values(table.columns).find(c => c.primary);
|
|
15
|
-
return pk?.name || 'id';
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Manages hydration planning for query results
|
|
20
|
-
*/
|
|
21
|
-
export class HydrationPlanner {
|
|
22
|
-
/**
|
|
23
|
-
* Creates a new HydrationPlanner instance
|
|
24
|
-
* @param table - Table definition
|
|
25
|
-
* @param plan - Optional existing hydration plan
|
|
26
|
-
*/
|
|
27
|
-
constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Captures root table columns for hydration planning
|
|
31
|
-
* @param columns - Columns to capture
|
|
32
|
-
* @returns Updated HydrationPlanner with captured columns
|
|
33
|
-
*/
|
|
34
|
-
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
-
const currentPlan = this.getPlanOrDefault();
|
|
36
|
-
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
-
let changed = false;
|
|
38
|
-
|
|
39
|
-
columns.forEach(node => {
|
|
40
|
-
if (node.type !== 'Column') return;
|
|
41
|
-
if (node.table !== this.table.name) return;
|
|
42
|
-
|
|
43
|
-
const alias = node.alias || node.name;
|
|
44
|
-
if (isRelationAlias(alias)) return;
|
|
45
|
-
if (!rootCols.has(alias)) {
|
|
46
|
-
rootCols.add(alias);
|
|
47
|
-
changed = true;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (!changed) return this;
|
|
52
|
-
return new HydrationPlanner(this.table, {
|
|
53
|
-
...currentPlan,
|
|
54
|
-
rootColumns: Array.from(rootCols)
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Includes a relation in the hydration plan
|
|
60
|
-
* @param rel - Relation definition
|
|
61
|
-
* @param relationName - Name of the relation
|
|
62
|
-
* @param aliasPrefix - Alias prefix for relation columns
|
|
63
|
-
* @param columns - Columns to include from the relation
|
|
64
|
-
* @returns Updated HydrationPlanner with included relation
|
|
65
|
-
*/
|
|
66
|
-
includeRelation(
|
|
67
|
-
rel: RelationDef,
|
|
68
|
-
relationName: string,
|
|
69
|
-
aliasPrefix: string,
|
|
70
|
-
columns: string[],
|
|
71
|
-
pivot?: { aliasPrefix: string; columns: string[] }
|
|
72
|
-
): HydrationPlanner {
|
|
73
|
-
const currentPlan = this.getPlanOrDefault();
|
|
74
|
-
const relations = currentPlan.relations.filter(r => r.name !== relationName);
|
|
75
|
-
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
76
|
-
return new HydrationPlanner(this.table, {
|
|
77
|
-
...currentPlan,
|
|
78
|
-
relations
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Gets the current hydration plan
|
|
84
|
-
* @returns Current hydration plan or undefined
|
|
85
|
-
*/
|
|
86
|
-
getPlan(): HydrationPlan | undefined {
|
|
87
|
-
return this.plan;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Gets the current hydration plan or creates a default one
|
|
92
|
-
* @returns Current hydration plan or default plan
|
|
93
|
-
*/
|
|
94
|
-
private getPlanOrDefault(): HydrationPlan {
|
|
95
|
-
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Builds a relation plan for hydration
|
|
100
|
-
* @param rel - Relation definition
|
|
101
|
-
* @param relationName - Name of the relation
|
|
102
|
-
* @param aliasPrefix - Alias prefix for relation columns
|
|
103
|
-
* @param columns - Columns to include from the relation
|
|
104
|
-
* @returns Hydration relation plan
|
|
105
|
-
*/
|
|
106
|
-
private buildRelationPlan(
|
|
107
|
-
rel: RelationDef,
|
|
108
|
-
relationName: string,
|
|
109
|
-
aliasPrefix: string,
|
|
110
|
-
columns: string[],
|
|
111
|
-
pivot?: { aliasPrefix: string; columns: string[] }
|
|
112
|
-
): HydrationRelationPlan {
|
|
113
|
-
switch (rel.type) {
|
|
114
|
-
case RelationKinds.HasMany: {
|
|
115
|
-
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
116
|
-
return {
|
|
117
|
-
name: relationName,
|
|
118
|
-
aliasPrefix,
|
|
119
|
-
type: rel.type,
|
|
120
|
-
targetTable: rel.target.name,
|
|
121
|
-
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
122
|
-
foreignKey: rel.foreignKey,
|
|
123
|
-
localKey,
|
|
124
|
-
columns
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
case RelationKinds.BelongsTo: {
|
|
128
|
-
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
129
|
-
return {
|
|
130
|
-
name: relationName,
|
|
131
|
-
aliasPrefix,
|
|
132
|
-
type: rel.type,
|
|
133
|
-
targetTable: rel.target.name,
|
|
134
|
-
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
135
|
-
foreignKey: rel.foreignKey,
|
|
136
|
-
localKey,
|
|
137
|
-
columns
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
case RelationKinds.BelongsToMany: {
|
|
141
|
-
const many = rel as BelongsToManyRelation;
|
|
142
|
-
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
143
|
-
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
144
|
-
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
145
|
-
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
146
|
-
const pivotColumns =
|
|
147
|
-
pivot?.columns ??
|
|
148
|
-
many.defaultPivotColumns ??
|
|
149
|
-
buildDefaultPivotColumns(many, pivotPk);
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
name: relationName,
|
|
153
|
-
aliasPrefix,
|
|
154
|
-
type: rel.type,
|
|
155
|
-
targetTable: many.target.name,
|
|
156
|
-
targetPrimaryKey: targetPk,
|
|
157
|
-
foreignKey: many.pivotForeignKeyToRoot,
|
|
158
|
-
localKey,
|
|
159
|
-
columns,
|
|
160
|
-
pivot: {
|
|
161
|
-
table: many.pivotTable.name,
|
|
162
|
-
primaryKey: pivotPk,
|
|
163
|
-
aliasPrefix: pivotAliasPrefix,
|
|
164
|
-
columns: pivotColumns
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Builds a default hydration plan for a table
|
|
174
|
-
* @param table - Table definition
|
|
175
|
-
* @returns Default hydration plan
|
|
176
|
-
*/
|
|
177
|
-
const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
|
|
178
|
-
rootTable: table.name,
|
|
179
|
-
rootPrimaryKey: findPrimaryKey(table),
|
|
180
|
-
rootColumns: [],
|
|
181
|
-
relations: []
|
|
182
|
-
});
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
|
|
3
|
+
import { ProjectionNode } from './select-query-state.js';
|
|
4
|
+
import { HydrationPlan, HydrationRelationPlan } from '../core/ast/query.js';
|
|
5
|
+
import { isRelationAlias } from './relation-alias.js';
|
|
6
|
+
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Finds the primary key column name for a table
|
|
10
|
+
* @param table - Table definition
|
|
11
|
+
* @returns Name of the primary key column, defaults to 'id'
|
|
12
|
+
*/
|
|
13
|
+
export const findPrimaryKey = (table: TableDef): string => {
|
|
14
|
+
const pk = Object.values(table.columns).find(c => c.primary);
|
|
15
|
+
return pk?.name || 'id';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manages hydration planning for query results
|
|
20
|
+
*/
|
|
21
|
+
export class HydrationPlanner {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new HydrationPlanner instance
|
|
24
|
+
* @param table - Table definition
|
|
25
|
+
* @param plan - Optional existing hydration plan
|
|
26
|
+
*/
|
|
27
|
+
constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Captures root table columns for hydration planning
|
|
31
|
+
* @param columns - Columns to capture
|
|
32
|
+
* @returns Updated HydrationPlanner with captured columns
|
|
33
|
+
*/
|
|
34
|
+
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
+
const currentPlan = this.getPlanOrDefault();
|
|
36
|
+
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
+
let changed = false;
|
|
38
|
+
|
|
39
|
+
columns.forEach(node => {
|
|
40
|
+
if (node.type !== 'Column') return;
|
|
41
|
+
if (node.table !== this.table.name) return;
|
|
42
|
+
|
|
43
|
+
const alias = node.alias || node.name;
|
|
44
|
+
if (isRelationAlias(alias)) return;
|
|
45
|
+
if (!rootCols.has(alias)) {
|
|
46
|
+
rootCols.add(alias);
|
|
47
|
+
changed = true;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!changed) return this;
|
|
52
|
+
return new HydrationPlanner(this.table, {
|
|
53
|
+
...currentPlan,
|
|
54
|
+
rootColumns: Array.from(rootCols)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Includes a relation in the hydration plan
|
|
60
|
+
* @param rel - Relation definition
|
|
61
|
+
* @param relationName - Name of the relation
|
|
62
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
63
|
+
* @param columns - Columns to include from the relation
|
|
64
|
+
* @returns Updated HydrationPlanner with included relation
|
|
65
|
+
*/
|
|
66
|
+
includeRelation(
|
|
67
|
+
rel: RelationDef,
|
|
68
|
+
relationName: string,
|
|
69
|
+
aliasPrefix: string,
|
|
70
|
+
columns: string[],
|
|
71
|
+
pivot?: { aliasPrefix: string; columns: string[] }
|
|
72
|
+
): HydrationPlanner {
|
|
73
|
+
const currentPlan = this.getPlanOrDefault();
|
|
74
|
+
const relations = currentPlan.relations.filter(r => r.name !== relationName);
|
|
75
|
+
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
76
|
+
return new HydrationPlanner(this.table, {
|
|
77
|
+
...currentPlan,
|
|
78
|
+
relations
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Gets the current hydration plan
|
|
84
|
+
* @returns Current hydration plan or undefined
|
|
85
|
+
*/
|
|
86
|
+
getPlan(): HydrationPlan | undefined {
|
|
87
|
+
return this.plan;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the current hydration plan or creates a default one
|
|
92
|
+
* @returns Current hydration plan or default plan
|
|
93
|
+
*/
|
|
94
|
+
private getPlanOrDefault(): HydrationPlan {
|
|
95
|
+
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Builds a relation plan for hydration
|
|
100
|
+
* @param rel - Relation definition
|
|
101
|
+
* @param relationName - Name of the relation
|
|
102
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
103
|
+
* @param columns - Columns to include from the relation
|
|
104
|
+
* @returns Hydration relation plan
|
|
105
|
+
*/
|
|
106
|
+
private buildRelationPlan(
|
|
107
|
+
rel: RelationDef,
|
|
108
|
+
relationName: string,
|
|
109
|
+
aliasPrefix: string,
|
|
110
|
+
columns: string[],
|
|
111
|
+
pivot?: { aliasPrefix: string; columns: string[] }
|
|
112
|
+
): HydrationRelationPlan {
|
|
113
|
+
switch (rel.type) {
|
|
114
|
+
case RelationKinds.HasMany: {
|
|
115
|
+
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
116
|
+
return {
|
|
117
|
+
name: relationName,
|
|
118
|
+
aliasPrefix,
|
|
119
|
+
type: rel.type,
|
|
120
|
+
targetTable: rel.target.name,
|
|
121
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
122
|
+
foreignKey: rel.foreignKey,
|
|
123
|
+
localKey,
|
|
124
|
+
columns
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case RelationKinds.BelongsTo: {
|
|
128
|
+
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
129
|
+
return {
|
|
130
|
+
name: relationName,
|
|
131
|
+
aliasPrefix,
|
|
132
|
+
type: rel.type,
|
|
133
|
+
targetTable: rel.target.name,
|
|
134
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
135
|
+
foreignKey: rel.foreignKey,
|
|
136
|
+
localKey,
|
|
137
|
+
columns
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
case RelationKinds.BelongsToMany: {
|
|
141
|
+
const many = rel as BelongsToManyRelation;
|
|
142
|
+
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
143
|
+
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
144
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
145
|
+
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
146
|
+
const pivotColumns =
|
|
147
|
+
pivot?.columns ??
|
|
148
|
+
many.defaultPivotColumns ??
|
|
149
|
+
buildDefaultPivotColumns(many, pivotPk);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
name: relationName,
|
|
153
|
+
aliasPrefix,
|
|
154
|
+
type: rel.type,
|
|
155
|
+
targetTable: many.target.name,
|
|
156
|
+
targetPrimaryKey: targetPk,
|
|
157
|
+
foreignKey: many.pivotForeignKeyToRoot,
|
|
158
|
+
localKey,
|
|
159
|
+
columns,
|
|
160
|
+
pivot: {
|
|
161
|
+
table: many.pivotTable.name,
|
|
162
|
+
primaryKey: pivotPk,
|
|
163
|
+
aliasPrefix: pivotAliasPrefix,
|
|
164
|
+
columns: pivotColumns
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Builds a default hydration plan for a table
|
|
174
|
+
* @param table - Table definition
|
|
175
|
+
* @returns Default hydration plan
|
|
176
|
+
*/
|
|
177
|
+
const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
|
|
178
|
+
rootTable: table.name,
|
|
179
|
+
rootPrimaryKey: findPrimaryKey(table),
|
|
180
|
+
rootColumns: [],
|
|
181
|
+
relations: []
|
|
182
|
+
});
|
|
@@ -1,62 +1,51 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { InsertQueryNode, TableNode } from '../ast/query';
|
|
3
|
-
import { ColumnNode, OperandNode, valueToOperand } from '../ast/expression';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
values: [...this.ast.values, ...newRows]
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
withReturning(columns: ColumnNode[]): InsertQueryState {
|
|
57
|
-
return this.clone({
|
|
58
|
-
...this.ast,
|
|
59
|
-
returning: [...columns]
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { InsertQueryNode, TableNode } from '../core/ast/query.js';
|
|
3
|
+
import { ColumnNode, OperandNode, valueToOperand } from '../core/ast/expression.js';
|
|
4
|
+
import { buildColumnNodes, createTableNode } from '../core/ast/builders.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Maintains immutable state for building INSERT queries
|
|
8
|
+
*/
|
|
9
|
+
export class InsertQueryState {
|
|
10
|
+
public readonly table: TableDef;
|
|
11
|
+
public readonly ast: InsertQueryNode;
|
|
12
|
+
|
|
13
|
+
constructor(table: TableDef, ast?: InsertQueryNode) {
|
|
14
|
+
this.table = table;
|
|
15
|
+
this.ast = ast ?? {
|
|
16
|
+
type: 'InsertQuery',
|
|
17
|
+
into: createTableNode(table),
|
|
18
|
+
columns: [],
|
|
19
|
+
values: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private clone(nextAst: InsertQueryNode): InsertQueryState {
|
|
24
|
+
return new InsertQueryState(this.table, nextAst);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withValues(rows: Record<string, unknown>[]): InsertQueryState {
|
|
28
|
+
if (!rows.length) return this;
|
|
29
|
+
|
|
30
|
+
const definedColumns = this.ast.columns.length
|
|
31
|
+
? this.ast.columns
|
|
32
|
+
: buildColumnNodes(this.table, Object.keys(rows[0]));
|
|
33
|
+
|
|
34
|
+
const newRows: OperandNode[][] = rows.map(row =>
|
|
35
|
+
definedColumns.map(column => valueToOperand(row[column.name]))
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return this.clone({
|
|
39
|
+
...this.ast,
|
|
40
|
+
columns: definedColumns,
|
|
41
|
+
values: [...this.ast.values, ...newRows]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
withReturning(columns: ColumnNode[]): InsertQueryState {
|
|
46
|
+
return this.clone({
|
|
47
|
+
...this.ast,
|
|
48
|
+
returning: [...columns]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|