metal-orm 1.0.14 → 1.0.16
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 +69 -67
- package/dist/decorators/index.cjs +1983 -224
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -6
- package/dist/decorators/index.d.ts +6 -6
- package/dist/decorators/index.js +1982 -224
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +5284 -3751
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -169
- package/dist/index.d.ts +524 -169
- package/dist/index.js +5197 -3736
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-BKZrMRCQ.d.cts} +555 -94
- package/dist/{select-CCp1oz9p.d.ts → select-BKZrMRCQ.d.ts} +555 -94
- package/package.json +1 -1
- package/src/codegen/naming-strategy.ts +64 -0
- package/src/codegen/typescript.ts +19 -21
- 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 +17 -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 +172 -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/column.ts +13 -4
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +37 -19
- package/src/orm/entity-context.ts +30 -0
- package/src/orm/entity-meta.ts +2 -2
- package/src/orm/entity-metadata.ts +8 -6
- package/src/orm/entity.ts +72 -41
- package/src/orm/execute.ts +42 -25
- package/src/orm/execution-context.ts +12 -0
- package/src/orm/hydration-context.ts +14 -0
- package/src/orm/hydration.ts +25 -17
- package/src/orm/identity-map.ts +4 -0
- package/src/orm/interceptor-pipeline.ts +29 -0
- package/src/orm/lazy-batch.ts +50 -6
- package/src/orm/orm-session.ts +234 -0
- package/src/orm/orm.ts +58 -0
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +48 -3
- package/src/orm/relations/belongs-to.ts +45 -44
- package/src/orm/relations/has-many.ts +44 -43
- package/src/orm/relations/has-one.ts +140 -0
- package/src/orm/relations/many-to-many.ts +46 -45
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +66 -61
- 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 +575 -64
- 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/orm-context.ts +0 -159
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { HasOneReference } from '../../schema/types.js';
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
4
|
+
import { HasOneRelation } from '../../schema/relation.js';
|
|
5
|
+
import { TableDef } from '../../schema/table.js';
|
|
6
|
+
import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
|
|
7
|
+
|
|
8
|
+
type Row = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
+
|
|
12
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
Object.defineProperty(obj, key, {
|
|
15
|
+
value: obj[key],
|
|
16
|
+
writable: false,
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class DefaultHasOneReference<TChild> implements HasOneReference<TChild> {
|
|
24
|
+
private loaded = false;
|
|
25
|
+
private current: TChild | null = null;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly ctx: EntityContext,
|
|
29
|
+
private readonly meta: EntityMeta<any>,
|
|
30
|
+
private readonly root: any,
|
|
31
|
+
private readonly relationName: string,
|
|
32
|
+
private readonly relation: HasOneRelation,
|
|
33
|
+
private readonly rootTable: TableDef,
|
|
34
|
+
private readonly loader: () => Promise<Map<string, Row>>,
|
|
35
|
+
private readonly createEntity: (row: Row) => TChild,
|
|
36
|
+
private readonly localKey: string
|
|
37
|
+
) {
|
|
38
|
+
hideInternal(this, [
|
|
39
|
+
'ctx',
|
|
40
|
+
'meta',
|
|
41
|
+
'root',
|
|
42
|
+
'relationName',
|
|
43
|
+
'relation',
|
|
44
|
+
'rootTable',
|
|
45
|
+
'loader',
|
|
46
|
+
'createEntity',
|
|
47
|
+
'localKey'
|
|
48
|
+
]);
|
|
49
|
+
this.populateFromHydrationCache();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async load(): Promise<TChild | null> {
|
|
53
|
+
if (this.loaded) return this.current;
|
|
54
|
+
const map = await this.loader();
|
|
55
|
+
const keyValue = this.root[this.localKey];
|
|
56
|
+
if (keyValue === undefined || keyValue === null) {
|
|
57
|
+
this.loaded = true;
|
|
58
|
+
return this.current;
|
|
59
|
+
}
|
|
60
|
+
const row = map.get(toKey(keyValue));
|
|
61
|
+
this.current = row ? this.createEntity(row) : null;
|
|
62
|
+
this.loaded = true;
|
|
63
|
+
return this.current;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get(): TChild | null {
|
|
67
|
+
return this.current;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
set(data: Partial<TChild> | TChild | null): TChild | null {
|
|
71
|
+
if (data === null) {
|
|
72
|
+
return this.detachCurrent();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entity = hasEntityMeta(data) ? (data as TChild) : this.createEntity(data as Row);
|
|
76
|
+
if (this.current && this.current !== entity) {
|
|
77
|
+
this.ctx.registerRelationChange(
|
|
78
|
+
this.root,
|
|
79
|
+
this.relationKey,
|
|
80
|
+
this.rootTable,
|
|
81
|
+
this.relationName,
|
|
82
|
+
this.relation,
|
|
83
|
+
{ kind: 'remove', entity: this.current }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.assignForeignKey(entity);
|
|
88
|
+
this.current = entity;
|
|
89
|
+
this.loaded = true;
|
|
90
|
+
|
|
91
|
+
this.ctx.registerRelationChange(
|
|
92
|
+
this.root,
|
|
93
|
+
this.relationKey,
|
|
94
|
+
this.rootTable,
|
|
95
|
+
this.relationName,
|
|
96
|
+
this.relation,
|
|
97
|
+
{ kind: 'attach', entity }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return entity;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
toJSON(): TChild | null {
|
|
104
|
+
return this.current;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private detachCurrent(): TChild | null {
|
|
108
|
+
const previous = this.current;
|
|
109
|
+
if (!previous) return null;
|
|
110
|
+
this.current = null;
|
|
111
|
+
this.loaded = true;
|
|
112
|
+
this.ctx.registerRelationChange(
|
|
113
|
+
this.root,
|
|
114
|
+
this.relationKey,
|
|
115
|
+
this.rootTable,
|
|
116
|
+
this.relationName,
|
|
117
|
+
this.relation,
|
|
118
|
+
{ kind: 'remove', entity: previous }
|
|
119
|
+
);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private assignForeignKey(entity: TChild): void {
|
|
124
|
+
const keyValue = this.root[this.localKey];
|
|
125
|
+
(entity as Row)[this.relation.foreignKey] = keyValue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private get relationKey(): RelationKey {
|
|
129
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private populateFromHydrationCache(): void {
|
|
133
|
+
const keyValue = this.root[this.localKey];
|
|
134
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
135
|
+
const row = getHydrationRecord(this.meta, this.relationName, keyValue);
|
|
136
|
+
if (!row) return;
|
|
137
|
+
this.current = this.createEntity(row);
|
|
138
|
+
this.loaded = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { ManyToManyCollection } from '../../schema/types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
3
4
|
import { BelongsToManyRelation } from '../../schema/relation.js';
|
|
4
5
|
import { TableDef } from '../../schema/table.js';
|
|
5
6
|
import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
|
|
6
7
|
import { EntityMeta, getHydrationRows } from '../entity-meta.js';
|
|
7
8
|
|
|
8
|
-
type Rows = Record<string, any>[];
|
|
9
|
-
|
|
10
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
-
|
|
12
|
-
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
-
for (const key of keys) {
|
|
14
|
-
Object.defineProperty(obj, key, {
|
|
15
|
-
value: obj[key],
|
|
16
|
-
writable: false,
|
|
17
|
-
configurable: false,
|
|
18
|
-
enumerable: false
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
|
|
24
|
-
private loaded = false;
|
|
25
|
-
private items: TTarget[] = [];
|
|
26
|
-
|
|
27
|
-
constructor(
|
|
28
|
-
private readonly ctx:
|
|
9
|
+
type Rows = Record<string, any>[];
|
|
10
|
+
|
|
11
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
12
|
+
|
|
13
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value: obj[key],
|
|
17
|
+
writable: false,
|
|
18
|
+
configurable: false,
|
|
19
|
+
enumerable: false
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
|
|
25
|
+
private loaded = false;
|
|
26
|
+
private items: TTarget[] = [];
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly ctx: EntityContext,
|
|
29
30
|
private readonly meta: EntityMeta<any>,
|
|
30
31
|
private readonly root: any,
|
|
31
32
|
private readonly relationName: string,
|
|
32
33
|
private readonly relation: BelongsToManyRelation,
|
|
33
34
|
private readonly rootTable: TableDef,
|
|
34
35
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
35
|
-
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
36
|
-
private readonly localKey: string
|
|
37
|
-
) {
|
|
38
|
-
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
39
|
-
this.hydrateFromCache();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async load(): Promise<TTarget[]> {
|
|
43
|
-
if (this.loaded) return this.items;
|
|
36
|
+
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
37
|
+
private readonly localKey: string
|
|
38
|
+
) {
|
|
39
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
40
|
+
this.hydrateFromCache();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async load(): Promise<TTarget[]> {
|
|
44
|
+
if (this.loaded) return this.items;
|
|
44
45
|
const map = await this.loader();
|
|
45
46
|
const key = toKey(this.root[this.localKey]);
|
|
46
47
|
const rows = map.get(key) ?? [];
|
|
@@ -144,22 +145,22 @@ export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollectio
|
|
|
144
145
|
return this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
private hydrateFromCache(): void {
|
|
148
|
-
const keyValue = this.root[this.localKey];
|
|
149
|
-
if (keyValue === undefined || keyValue === null) return;
|
|
150
|
-
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
151
|
-
if (!rows?.length) return;
|
|
148
|
+
private hydrateFromCache(): void {
|
|
149
|
+
const keyValue = this.root[this.localKey];
|
|
150
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
151
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
152
|
+
if (!rows?.length) return;
|
|
152
153
|
this.items = rows.map(row => {
|
|
153
154
|
const entity = this.createEntity(row);
|
|
154
155
|
if ((row as any)._pivot) {
|
|
155
156
|
(entity as any)._pivot = (row as any)._pivot;
|
|
156
157
|
}
|
|
157
|
-
return entity;
|
|
158
|
-
});
|
|
159
|
-
this.loaded = true;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
toJSON(): TTarget[] {
|
|
163
|
-
return this.items;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
158
|
+
return entity;
|
|
159
|
+
});
|
|
160
|
+
this.loaded = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
toJSON(): TTarget[] {
|
|
164
|
+
return this.items;
|
|
165
|
+
}
|
|
166
|
+
}
|
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';
|
|
@@ -18,7 +18,7 @@ export class UnitOfWork {
|
|
|
18
18
|
private readonly executor: DbExecutor,
|
|
19
19
|
private readonly identityMap: IdentityMap,
|
|
20
20
|
private readonly hookContext: () => unknown
|
|
21
|
-
) {}
|
|
21
|
+
) { }
|
|
22
22
|
|
|
23
23
|
get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
|
|
24
24
|
return this.identityMap.bucketsMap;
|
|
@@ -117,17 +117,22 @@ export class UnitOfWork {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
reset(): void {
|
|
121
|
+
this.trackedEntities.clear();
|
|
122
|
+
this.identityMap.clear();
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
private async flushInsert(tracked: TrackedEntity): Promise<void> {
|
|
121
126
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
122
127
|
|
|
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);
|
|
128
|
+
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
129
|
+
let builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
130
|
+
if (this.dialect.supportsReturning()) {
|
|
131
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
132
|
+
}
|
|
133
|
+
const compiled = builder.compile(this.dialect);
|
|
134
|
+
const results = await this.executeCompiled(compiled);
|
|
135
|
+
this.applyReturningResults(tracked, results);
|
|
131
136
|
|
|
132
137
|
tracked.status = EntityStatus.Managed;
|
|
133
138
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
@@ -150,17 +155,17 @@ export class UnitOfWork {
|
|
|
150
155
|
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
151
156
|
if (!pkColumn) return;
|
|
152
157
|
|
|
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);
|
|
158
|
+
let builder = new UpdateQueryBuilder(tracked.table)
|
|
159
|
+
.set(changes)
|
|
160
|
+
.where(eq(pkColumn, tracked.pk));
|
|
161
|
+
|
|
162
|
+
if (this.dialect.supportsReturning()) {
|
|
163
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const compiled = builder.compile(this.dialect);
|
|
167
|
+
const results = await this.executeCompiled(compiled);
|
|
168
|
+
this.applyReturningResults(tracked, results);
|
|
164
169
|
|
|
165
170
|
tracked.status = EntityStatus.Managed;
|
|
166
171
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
@@ -215,44 +220,44 @@ export class UnitOfWork {
|
|
|
215
220
|
return payload;
|
|
216
221
|
}
|
|
217
222
|
|
|
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> {
|
|
223
|
+
private async executeCompiled(compiled: CompiledQuery): Promise<QueryResult[]> {
|
|
224
|
+
return this.executor.executeSql(compiled.sql, compiled.params);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private getReturningColumns(table: TableDef): ColumnNode[] {
|
|
228
|
+
return Object.values(table.columns).map(column => ({
|
|
229
|
+
type: 'Column',
|
|
230
|
+
table: table.name,
|
|
231
|
+
name: column.name,
|
|
232
|
+
alias: column.name
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
237
|
+
if (!this.dialect.supportsReturning()) return;
|
|
238
|
+
const first = results[0];
|
|
239
|
+
if (!first || first.values.length === 0) return;
|
|
240
|
+
|
|
241
|
+
const row = first.values[0];
|
|
242
|
+
for (let i = 0; i < first.columns.length; i++) {
|
|
243
|
+
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
244
|
+
if (!(columnName in tracked.table.columns)) continue;
|
|
245
|
+
tracked.entity[columnName] = row[i];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private normalizeColumnName(column: string): string {
|
|
250
|
+
const parts = column.split('.');
|
|
251
|
+
const candidate = parts[parts.length - 1];
|
|
252
|
+
return candidate.replace(/^["`[\]]+|["`[\]]+$/g, '');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private registerIdentity(tracked: TrackedEntity): void {
|
|
256
|
+
if (tracked.pk == null) return;
|
|
257
|
+
this.identityMap.register(tracked);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private createSnapshot(table: TableDef, entity: any): Record<string, any> {
|
|
256
261
|
const snapshot: Record<string, any> = {};
|
|
257
262
|
for (const column of Object.keys(table.columns)) {
|
|
258
263
|
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);
|