metal-orm 1.0.14 → 1.0.15
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 +40 -45
- package/dist/decorators/index.cjs +1600 -27
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -2
- package/dist/decorators/index.d.ts +6 -2
- package/dist/decorators/index.js +1599 -27
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +4608 -3429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +511 -159
- package/dist/index.d.ts +511 -159
- package/dist/index.js +4526 -3415
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
- package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
- package/package.json +1 -1
- package/src/codegen/typescript.ts +38 -35
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +16 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +144 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +30 -19
- package/src/orm/entity-metadata.ts +7 -0
- package/src/orm/entity.ts +58 -27
- package/src/orm/hydration.ts +25 -17
- package/src/orm/lazy-batch.ts +46 -2
- package/src/orm/orm-context.ts +60 -60
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +43 -2
- package/src/orm/relations/has-one.ts +139 -0
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +60 -60
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +66 -61
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
package/src/orm/unit-of-work.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
2
|
-
import type { Dialect, CompiledQuery } from '../core/dialect/abstract.js';
|
|
1
|
+
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
2
|
+
import type { Dialect, CompiledQuery } from '../core/dialect/abstract.js';
|
|
3
3
|
import { InsertQueryBuilder } from '../query-builder/insert.js';
|
|
4
4
|
import { UpdateQueryBuilder } from '../query-builder/update.js';
|
|
5
5
|
import { DeleteQueryBuilder } from '../query-builder/delete.js';
|
|
6
6
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
7
7
|
import type { TableDef, TableHooks } from '../schema/table.js';
|
|
8
|
-
import type { DbExecutor, QueryResult } from '
|
|
8
|
+
import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
|
|
9
9
|
import { IdentityMap } from './identity-map.js';
|
|
10
10
|
import { EntityStatus } from './runtime-types.js';
|
|
11
11
|
import type { TrackedEntity } from './runtime-types.js';
|
|
@@ -120,14 +120,14 @@ export class UnitOfWork {
|
|
|
120
120
|
private async flushInsert(tracked: TrackedEntity): Promise<void> {
|
|
121
121
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
122
122
|
|
|
123
|
-
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
124
|
-
let builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
125
|
-
if (this.dialect.supportsReturning()) {
|
|
126
|
-
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
127
|
-
}
|
|
128
|
-
const compiled = builder.compile(this.dialect);
|
|
129
|
-
const results = await this.executeCompiled(compiled);
|
|
130
|
-
this.applyReturningResults(tracked, results);
|
|
123
|
+
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
124
|
+
let builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
125
|
+
if (this.dialect.supportsReturning()) {
|
|
126
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
127
|
+
}
|
|
128
|
+
const compiled = builder.compile(this.dialect);
|
|
129
|
+
const results = await this.executeCompiled(compiled);
|
|
130
|
+
this.applyReturningResults(tracked, results);
|
|
131
131
|
|
|
132
132
|
tracked.status = EntityStatus.Managed;
|
|
133
133
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
@@ -150,17 +150,17 @@ export class UnitOfWork {
|
|
|
150
150
|
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
151
151
|
if (!pkColumn) return;
|
|
152
152
|
|
|
153
|
-
let builder = new UpdateQueryBuilder(tracked.table)
|
|
154
|
-
.set(changes)
|
|
155
|
-
.where(eq(pkColumn, tracked.pk));
|
|
156
|
-
|
|
157
|
-
if (this.dialect.supportsReturning()) {
|
|
158
|
-
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const compiled = builder.compile(this.dialect);
|
|
162
|
-
const results = await this.executeCompiled(compiled);
|
|
163
|
-
this.applyReturningResults(tracked, results);
|
|
153
|
+
let builder = new UpdateQueryBuilder(tracked.table)
|
|
154
|
+
.set(changes)
|
|
155
|
+
.where(eq(pkColumn, tracked.pk));
|
|
156
|
+
|
|
157
|
+
if (this.dialect.supportsReturning()) {
|
|
158
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const compiled = builder.compile(this.dialect);
|
|
162
|
+
const results = await this.executeCompiled(compiled);
|
|
163
|
+
this.applyReturningResults(tracked, results);
|
|
164
164
|
|
|
165
165
|
tracked.status = EntityStatus.Managed;
|
|
166
166
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
@@ -215,44 +215,44 @@ export class UnitOfWork {
|
|
|
215
215
|
return payload;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
private async executeCompiled(compiled: CompiledQuery): Promise<QueryResult[]> {
|
|
219
|
-
return this.executor.executeSql(compiled.sql, compiled.params);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private getReturningColumns(table: TableDef): ColumnNode[] {
|
|
223
|
-
return Object.values(table.columns).map(column => ({
|
|
224
|
-
type: 'Column',
|
|
225
|
-
table: table.name,
|
|
226
|
-
name: column.name,
|
|
227
|
-
alias: column.name
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
232
|
-
if (!this.dialect.supportsReturning()) return;
|
|
233
|
-
const first = results[0];
|
|
234
|
-
if (!first || first.values.length === 0) return;
|
|
235
|
-
|
|
236
|
-
const row = first.values[0];
|
|
237
|
-
for (let i = 0; i < first.columns.length; i++) {
|
|
238
|
-
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
239
|
-
if (!(columnName in tracked.table.columns)) continue;
|
|
240
|
-
tracked.entity[columnName] = row[i];
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private normalizeColumnName(column: string): string {
|
|
245
|
-
const parts = column.split('.');
|
|
246
|
-
const candidate = parts[parts.length - 1];
|
|
247
|
-
return candidate.replace(/^["`[\]]+|["`[\]]+$/g, '');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private registerIdentity(tracked: TrackedEntity): void {
|
|
251
|
-
if (tracked.pk == null) return;
|
|
252
|
-
this.identityMap.register(tracked);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private createSnapshot(table: TableDef, entity: any): Record<string, any> {
|
|
218
|
+
private async executeCompiled(compiled: CompiledQuery): Promise<QueryResult[]> {
|
|
219
|
+
return this.executor.executeSql(compiled.sql, compiled.params);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private getReturningColumns(table: TableDef): ColumnNode[] {
|
|
223
|
+
return Object.values(table.columns).map(column => ({
|
|
224
|
+
type: 'Column',
|
|
225
|
+
table: table.name,
|
|
226
|
+
name: column.name,
|
|
227
|
+
alias: column.name
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
232
|
+
if (!this.dialect.supportsReturning()) return;
|
|
233
|
+
const first = results[0];
|
|
234
|
+
if (!first || first.values.length === 0) return;
|
|
235
|
+
|
|
236
|
+
const row = first.values[0];
|
|
237
|
+
for (let i = 0; i < first.columns.length; i++) {
|
|
238
|
+
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
239
|
+
if (!(columnName in tracked.table.columns)) continue;
|
|
240
|
+
tracked.entity[columnName] = row[i];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private normalizeColumnName(column: string): string {
|
|
245
|
+
const parts = column.split('.');
|
|
246
|
+
const candidate = parts[parts.length - 1];
|
|
247
|
+
return candidate.replace(/^["`[\]]+|["`[\]]+$/g, '');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private registerIdentity(tracked: TrackedEntity): void {
|
|
251
|
+
if (tracked.pk == null) return;
|
|
252
|
+
this.identityMap.register(tracked);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private createSnapshot(table: TableDef, entity: any): Record<string, any> {
|
|
256
256
|
const snapshot: Record<string, any> = {};
|
|
257
257
|
for (const column of Object.keys(table.columns)) {
|
|
258
258
|
snapshot[column] = entity[column];
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
3
|
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
4
|
-
import { CompiledQuery, DeleteCompiler } from '../core/dialect/abstract.js';
|
|
4
|
+
import { CompiledQuery, DeleteCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
|
+
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
5
6
|
import { DeleteQueryNode } from '../core/ast/query.js';
|
|
6
7
|
import { DeleteQueryState } from './delete-query-state.js';
|
|
7
8
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
8
9
|
|
|
10
|
+
type DeleteDialectInput = Dialect | DialectKey;
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Builder for DELETE queries
|
|
11
14
|
*/
|
|
@@ -32,12 +35,26 @@ export class DeleteQueryBuilder<T> {
|
|
|
32
35
|
return this.clone(this.state.withReturning(nodes));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// Existing compiler-based compile stays, but we add a new overload.
|
|
39
|
+
|
|
40
|
+
// 1) Keep the old behavior (used internally / tests, if any):
|
|
41
|
+
compile(compiler: DeleteCompiler): CompiledQuery;
|
|
42
|
+
// 2) New ergonomic overload:
|
|
43
|
+
compile(dialect: DeleteDialectInput): CompiledQuery;
|
|
44
|
+
|
|
45
|
+
compile(arg: DeleteCompiler | DeleteDialectInput): CompiledQuery {
|
|
46
|
+
if (typeof (arg as any).compileDelete === 'function') {
|
|
47
|
+
// DeleteCompiler path – old behavior
|
|
48
|
+
return (arg as DeleteCompiler).compileDelete(this.state.ast);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Dialect | string path – new behavior
|
|
52
|
+
const dialect = resolveDialectInput(arg as DeleteDialectInput);
|
|
53
|
+
return dialect.compileDelete(this.state.ast);
|
|
37
54
|
}
|
|
38
55
|
|
|
39
|
-
toSql(
|
|
40
|
-
return this.compile(
|
|
56
|
+
toSql(arg: DeleteCompiler | DeleteDialectInput): string {
|
|
57
|
+
return this.compile(arg as any).sql;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
getAST(): DeleteQueryNode {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { RelationDef, RelationKinds } from '../schema/relation.js';
|
|
3
|
-
import { CommonTableExpressionNode,
|
|
3
|
+
import { CommonTableExpressionNode, OrderByNode, SelectQueryNode } from '../core/ast/query.js';
|
|
4
|
+
import { HydrationPlan } from '../core/hydration/types.js';
|
|
4
5
|
import { HydrationPlanner } from './hydration-planner.js';
|
|
5
6
|
import { ProjectionNode, SelectQueryState } from './select-query-state.js';
|
|
6
7
|
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
|
|
3
3
|
import { ProjectionNode } from './select-query-state.js';
|
|
4
|
-
import { HydrationPlan, HydrationRelationPlan } from '../core/
|
|
4
|
+
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
5
5
|
import { isRelationAlias } from './relation-alias.js';
|
|
6
6
|
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
7
7
|
|
|
@@ -111,12 +111,13 @@ export class HydrationPlanner {
|
|
|
111
111
|
pivot?: { aliasPrefix: string; columns: string[] }
|
|
112
112
|
): HydrationRelationPlan {
|
|
113
113
|
switch (rel.type) {
|
|
114
|
-
case RelationKinds.HasMany:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
case RelationKinds.HasMany:
|
|
115
|
+
case RelationKinds.HasOne: {
|
|
116
|
+
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
117
|
+
return {
|
|
118
|
+
name: relationName,
|
|
119
|
+
aliasPrefix,
|
|
120
|
+
type: rel.type,
|
|
120
121
|
targetTable: rel.target.name,
|
|
121
122
|
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
122
123
|
foreignKey: rel.foreignKey,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
3
|
import { ColumnNode } from '../core/ast/expression.js';
|
|
4
|
-
import { CompiledQuery, InsertCompiler } from '../core/dialect/abstract.js';
|
|
4
|
+
import { CompiledQuery, InsertCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
|
+
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
5
6
|
import { InsertQueryNode } from '../core/ast/query.js';
|
|
6
7
|
import { InsertQueryState } from './insert-query-state.js';
|
|
7
8
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
8
9
|
|
|
10
|
+
type InsertDialectInput = Dialect | DialectKey;
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Builder for INSERT queries
|
|
11
14
|
*/
|
|
@@ -34,12 +37,26 @@ export class InsertQueryBuilder<T> {
|
|
|
34
37
|
return this.clone(this.state.withReturning(nodes));
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// Existing compiler-based compile stays, but we add a new overload.
|
|
41
|
+
|
|
42
|
+
// 1) Keep the old behavior (used internally / tests, if any):
|
|
43
|
+
compile(compiler: InsertCompiler): CompiledQuery;
|
|
44
|
+
// 2) New ergonomic overload:
|
|
45
|
+
compile(dialect: InsertDialectInput): CompiledQuery;
|
|
46
|
+
|
|
47
|
+
compile(arg: InsertCompiler | InsertDialectInput): CompiledQuery {
|
|
48
|
+
if (typeof (arg as any).compileInsert === 'function') {
|
|
49
|
+
// InsertCompiler path – old behavior
|
|
50
|
+
return (arg as InsertCompiler).compileInsert(this.state.ast);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Dialect | string path – new behavior
|
|
54
|
+
const dialect = resolveDialectInput(arg as InsertDialectInput);
|
|
55
|
+
return dialect.compileInsert(this.state.ast);
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
toSql(
|
|
42
|
-
return this.compile(
|
|
58
|
+
toSql(arg: InsertCompiler | InsertDialectInput): string {
|
|
59
|
+
return this.compile(arg as any).sql;
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
getAST(): InsertQueryNode {
|
|
@@ -23,17 +23,18 @@ const assertNever = (value: never): never => {
|
|
|
23
23
|
*/
|
|
24
24
|
const baseRelationCondition = (root: TableDef, relation: RelationDef): ExpressionNode => {
|
|
25
25
|
const defaultLocalKey =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
|
|
27
|
+
? findPrimaryKey(root)
|
|
28
|
+
: findPrimaryKey(relation.target);
|
|
29
29
|
const localKey = relation.localKey || defaultLocalKey;
|
|
30
30
|
|
|
31
31
|
switch (relation.type) {
|
|
32
|
-
case RelationKinds.HasMany:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{ type: 'Column', table:
|
|
36
|
-
|
|
32
|
+
case RelationKinds.HasMany:
|
|
33
|
+
case RelationKinds.HasOne:
|
|
34
|
+
return eq(
|
|
35
|
+
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
36
|
+
{ type: 'Column', table: root.name, name: localKey }
|
|
37
|
+
);
|
|
37
38
|
case RelationKinds.BelongsTo:
|
|
38
39
|
return eq(
|
|
39
40
|
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
@@ -20,7 +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';
|
|
23
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
24
|
+
import { getJoinRelationName } from '../core/ast/join-metadata.js';
|
|
24
25
|
import { makeRelationAlias } from './relation-alias.js';
|
|
25
26
|
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
26
27
|
|
|
@@ -93,7 +94,7 @@ export class RelationService {
|
|
|
93
94
|
|
|
94
95
|
const relation = this.getRelation(relationName);
|
|
95
96
|
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
96
|
-
const alreadyJoined = state.ast.joins.some(j => j
|
|
97
|
+
const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
|
|
97
98
|
|
|
98
99
|
if (!alreadyJoined) {
|
|
99
100
|
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
|
-
import { SelectQueryNode,
|
|
3
|
+
import { SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
|
|
4
|
+
import { HydrationPlan } from '../core/hydration/types.js';
|
|
4
5
|
import {
|
|
5
6
|
ColumnNode,
|
|
6
7
|
ExpressionNode,
|
|
@@ -13,6 +14,9 @@ import {
|
|
|
13
14
|
notExists
|
|
14
15
|
} from '../core/ast/expression.js';
|
|
15
16
|
import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
|
|
17
|
+
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
18
|
+
|
|
19
|
+
type SelectDialectInput = Dialect | DialectKey;
|
|
16
20
|
import { SelectQueryState } from './select-query-state.js';
|
|
17
21
|
import { HydrationManager } from './hydration-manager.js';
|
|
18
22
|
import {
|
|
@@ -96,23 +100,23 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
96
100
|
return { state: nextState, hydration: context.hydration };
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
private applyJoin(
|
|
100
|
-
context: SelectQueryBuilderContext,
|
|
101
|
-
table: TableDef,
|
|
102
|
-
condition: BinaryExpressionNode,
|
|
103
|
-
kind: JoinKind
|
|
104
|
-
): SelectQueryBuilderContext {
|
|
105
|
-
const joinNode = createJoinNode(kind, table.name, condition);
|
|
106
|
-
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private applySetOperation(
|
|
110
|
-
operator: SetOperationKind,
|
|
111
|
-
query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode
|
|
112
|
-
): SelectQueryBuilderContext {
|
|
113
|
-
const subAst = this.resolveQueryNode(query);
|
|
114
|
-
return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
|
|
115
|
-
}
|
|
103
|
+
private applyJoin(
|
|
104
|
+
context: SelectQueryBuilderContext,
|
|
105
|
+
table: TableDef,
|
|
106
|
+
condition: BinaryExpressionNode,
|
|
107
|
+
kind: JoinKind
|
|
108
|
+
): SelectQueryBuilderContext {
|
|
109
|
+
const joinNode = createJoinNode(kind, table.name, condition);
|
|
110
|
+
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private applySetOperation(
|
|
114
|
+
operator: SetOperationKind,
|
|
115
|
+
query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode
|
|
116
|
+
): SelectQueryBuilderContext {
|
|
117
|
+
const subAst = this.resolveQueryNode(query);
|
|
118
|
+
return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
|
|
119
|
+
}
|
|
116
120
|
|
|
117
121
|
/**
|
|
118
122
|
* Selects specific columns for the query
|
|
@@ -323,46 +327,46 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
323
327
|
* @param n - Number of rows to skip
|
|
324
328
|
* @returns New query builder instance with the OFFSET clause
|
|
325
329
|
*/
|
|
326
|
-
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
327
|
-
const nextContext = this.applyAst(this.context, service => service.withOffset(n));
|
|
328
|
-
return this.clone(nextContext);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Combines this query with another using UNION
|
|
333
|
-
* @param query - Query to union with
|
|
334
|
-
* @returns New query builder instance with the set operation
|
|
335
|
-
*/
|
|
336
|
-
union(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
337
|
-
return this.clone(this.applySetOperation('UNION', query));
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Combines this query with another using UNION ALL
|
|
342
|
-
* @param query - Query to union with
|
|
343
|
-
* @returns New query builder instance with the set operation
|
|
344
|
-
*/
|
|
345
|
-
unionAll(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
346
|
-
return this.clone(this.applySetOperation('UNION ALL', query));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Combines this query with another using INTERSECT
|
|
351
|
-
* @param query - Query to intersect with
|
|
352
|
-
* @returns New query builder instance with the set operation
|
|
353
|
-
*/
|
|
354
|
-
intersect(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
355
|
-
return this.clone(this.applySetOperation('INTERSECT', query));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Combines this query with another using EXCEPT
|
|
360
|
-
* @param query - Query to subtract
|
|
361
|
-
* @returns New query builder instance with the set operation
|
|
362
|
-
*/
|
|
363
|
-
except(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
364
|
-
return this.clone(this.applySetOperation('EXCEPT', query));
|
|
365
|
-
}
|
|
330
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
331
|
+
const nextContext = this.applyAst(this.context, service => service.withOffset(n));
|
|
332
|
+
return this.clone(nextContext);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Combines this query with another using UNION
|
|
337
|
+
* @param query - Query to union with
|
|
338
|
+
* @returns New query builder instance with the set operation
|
|
339
|
+
*/
|
|
340
|
+
union(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
341
|
+
return this.clone(this.applySetOperation('UNION', query));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Combines this query with another using UNION ALL
|
|
346
|
+
* @param query - Query to union with
|
|
347
|
+
* @returns New query builder instance with the set operation
|
|
348
|
+
*/
|
|
349
|
+
unionAll(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
350
|
+
return this.clone(this.applySetOperation('UNION ALL', query));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Combines this query with another using INTERSECT
|
|
355
|
+
* @param query - Query to intersect with
|
|
356
|
+
* @returns New query builder instance with the set operation
|
|
357
|
+
*/
|
|
358
|
+
intersect(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
359
|
+
return this.clone(this.applySetOperation('INTERSECT', query));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Combines this query with another using EXCEPT
|
|
364
|
+
* @param query - Query to subtract
|
|
365
|
+
* @returns New query builder instance with the set operation
|
|
366
|
+
*/
|
|
367
|
+
except(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
368
|
+
return this.clone(this.applySetOperation('EXCEPT', query));
|
|
369
|
+
}
|
|
366
370
|
|
|
367
371
|
/**
|
|
368
372
|
* Adds a WHERE EXISTS condition to the query
|
|
@@ -443,8 +447,9 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
443
447
|
* @param dialect - Database dialect to compile for
|
|
444
448
|
* @returns Compiled query with SQL and parameters
|
|
445
449
|
*/
|
|
446
|
-
compile(dialect:
|
|
447
|
-
|
|
450
|
+
compile(dialect: SelectDialectInput): CompiledQuery {
|
|
451
|
+
const resolved = resolveDialectInput(dialect);
|
|
452
|
+
return resolved.compileSelect(this.context.state.ast);
|
|
448
453
|
}
|
|
449
454
|
|
|
450
455
|
/**
|
|
@@ -452,7 +457,7 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
452
457
|
* @param dialect - Database dialect to generate SQL for
|
|
453
458
|
* @returns SQL string representation of the query
|
|
454
459
|
*/
|
|
455
|
-
toSql(dialect:
|
|
460
|
+
toSql(dialect: SelectDialectInput): string {
|
|
456
461
|
return this.compile(dialect).sql;
|
|
457
462
|
}
|
|
458
463
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
3
|
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
4
|
-
import { CompiledQuery, UpdateCompiler } from '../core/dialect/abstract.js';
|
|
4
|
+
import { CompiledQuery, UpdateCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
|
+
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
5
6
|
import { UpdateQueryNode } from '../core/ast/query.js';
|
|
6
7
|
import { UpdateQueryState } from './update-query-state.js';
|
|
7
8
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
8
9
|
|
|
10
|
+
type UpdateDialectInput = Dialect | DialectKey;
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Builder for UPDATE queries
|
|
11
14
|
*/
|
|
@@ -36,12 +39,26 @@ export class UpdateQueryBuilder<T> {
|
|
|
36
39
|
return this.clone(this.state.withReturning(nodes));
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
// Existing compiler-based compile stays, but we add a new overload.
|
|
43
|
+
|
|
44
|
+
// 1) Keep the old behavior (used internally / tests, if any):
|
|
45
|
+
compile(compiler: UpdateCompiler): CompiledQuery;
|
|
46
|
+
// 2) New ergonomic overload:
|
|
47
|
+
compile(dialect: UpdateDialectInput): CompiledQuery;
|
|
48
|
+
|
|
49
|
+
compile(arg: UpdateCompiler | UpdateDialectInput): CompiledQuery {
|
|
50
|
+
if (typeof (arg as any).compileUpdate === 'function') {
|
|
51
|
+
// UpdateCompiler path – old behavior
|
|
52
|
+
return (arg as UpdateCompiler).compileUpdate(this.state.ast);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Dialect | string path – new behavior
|
|
56
|
+
const dialect = resolveDialectInput(arg as UpdateDialectInput);
|
|
57
|
+
return dialect.compileUpdate(this.state.ast);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
toSql(
|
|
44
|
-
return this.compile(
|
|
60
|
+
toSql(arg: UpdateCompiler | UpdateDialectInput): string {
|
|
61
|
+
return this.compile(arg as any).sql;
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
getAST(): UpdateQueryNode {
|