metal-orm 1.0.43 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -30
- package/dist/index.cjs +896 -476
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1146 -275
- package/dist/index.d.ts +1146 -275
- package/dist/index.js +896 -474
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/adapters.ts +8 -2
- package/src/core/ast/builders.ts +105 -81
- package/src/core/ast/expression-builders.ts +430 -390
- package/src/core/ast/expression-visitor.ts +47 -8
- package/src/core/ast/helpers.ts +23 -0
- package/src/core/ast/join-node.ts +17 -1
- package/src/core/ddl/dialects/base-schema-dialect.ts +7 -1
- package/src/core/ddl/dialects/index.ts +1 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -0
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
- package/src/core/ddl/introspect/context.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +11 -0
- package/src/core/ddl/introspect/mysql.ts +2 -0
- package/src/core/ddl/introspect/postgres.ts +14 -0
- package/src/core/ddl/introspect/registry.ts +14 -0
- package/src/core/ddl/introspect/run-select.ts +13 -0
- package/src/core/ddl/introspect/sqlite.ts +22 -0
- package/src/core/ddl/introspect/utils.ts +18 -0
- package/src/core/ddl/naming-strategy.ts +6 -0
- package/src/core/ddl/schema-dialect.ts +19 -6
- package/src/core/ddl/schema-diff.ts +22 -0
- package/src/core/ddl/schema-generator.ts +22 -0
- package/src/core/ddl/schema-plan-executor.ts +6 -0
- package/src/core/ddl/schema-types.ts +6 -0
- package/src/core/dialect/abstract.ts +2 -2
- package/src/core/execution/pooling/pool.ts +12 -7
- package/src/core/functions/datetime.ts +57 -33
- package/src/core/functions/numeric.ts +95 -30
- package/src/core/functions/standard-strategy.ts +35 -0
- package/src/core/functions/text.ts +83 -22
- package/src/core/functions/types.ts +23 -8
- package/src/decorators/bootstrap.ts +16 -4
- package/src/decorators/column.ts +17 -0
- package/src/decorators/decorator-metadata.ts +27 -0
- package/src/decorators/entity.ts +8 -0
- package/src/decorators/index.ts +3 -0
- package/src/decorators/relations.ts +32 -0
- package/src/orm/als.ts +34 -9
- package/src/orm/entity-context.ts +54 -0
- package/src/orm/entity-metadata.ts +122 -9
- package/src/orm/execute.ts +15 -0
- package/src/orm/lazy-batch.ts +68 -98
- package/src/orm/relations/has-many.ts +44 -0
- package/src/query/index.ts +74 -0
- package/src/query/target.ts +46 -0
- package/src/query-builder/delete-query-state.ts +30 -0
- package/src/query-builder/delete.ts +64 -19
- package/src/query-builder/hydration-manager.ts +46 -0
- package/src/query-builder/insert-query-state.ts +30 -0
- package/src/query-builder/insert.ts +46 -2
- package/src/query-builder/query-ast-service.ts +5 -0
- package/src/query-builder/query-resolution.ts +78 -0
- package/src/query-builder/raw-column-parser.ts +5 -0
- package/src/query-builder/relation-alias.ts +7 -0
- package/src/query-builder/relation-conditions.ts +61 -48
- package/src/query-builder/relation-service.ts +68 -63
- package/src/query-builder/relation-utils.ts +3 -0
- package/src/query-builder/select/cte-facet.ts +40 -0
- package/src/query-builder/select/from-facet.ts +80 -0
- package/src/query-builder/select/join-facet.ts +62 -0
- package/src/query-builder/select/predicate-facet.ts +103 -0
- package/src/query-builder/select/projection-facet.ts +69 -0
- package/src/query-builder/select/relation-facet.ts +81 -0
- package/src/query-builder/select/setop-facet.ts +36 -0
- package/src/query-builder/select-helpers.ts +13 -0
- package/src/query-builder/select-query-builder-deps.ts +19 -1
- package/src/query-builder/select-query-state.ts +2 -1
- package/src/query-builder/select.ts +795 -1163
- package/src/query-builder/update-query-state.ts +52 -0
- package/src/query-builder/update.ts +69 -19
- package/src/schema/table-guards.ts +31 -0
|
@@ -4,21 +4,75 @@ import { TableDef } from '../schema/table.js';
|
|
|
4
4
|
import { RelationDef } from '../schema/relation.js';
|
|
5
5
|
import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Interface for entity context providing entity tracking and management.
|
|
9
|
+
*/
|
|
7
10
|
export interface EntityContext {
|
|
11
|
+
/** The database dialect */
|
|
8
12
|
dialect: Dialect;
|
|
13
|
+
/** The database executor */
|
|
9
14
|
executor: DbExecutor;
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Gets an entity by table and primary key.
|
|
18
|
+
* @param table - The table definition
|
|
19
|
+
* @param pk - The primary key value
|
|
20
|
+
* @returns The entity or undefined
|
|
21
|
+
*/
|
|
11
22
|
getEntity(table: TableDef, pk: unknown): unknown;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sets an entity in the context.
|
|
26
|
+
* @param table - The table definition
|
|
27
|
+
* @param pk - The primary key value
|
|
28
|
+
* @param entity - The entity to set
|
|
29
|
+
*/
|
|
12
30
|
setEntity(table: TableDef, pk: unknown, entity: unknown): void;
|
|
13
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Tracks a new entity.
|
|
34
|
+
* @param table - The table definition
|
|
35
|
+
* @param entity - The new entity
|
|
36
|
+
* @param pk - Optional primary key
|
|
37
|
+
*/
|
|
14
38
|
trackNew(table: TableDef, entity: unknown, pk?: unknown): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tracks a managed entity.
|
|
42
|
+
* @param table - The table definition
|
|
43
|
+
* @param pk - The primary key
|
|
44
|
+
* @param entity - The managed entity
|
|
45
|
+
*/
|
|
15
46
|
trackManaged(table: TableDef, pk: unknown, entity: unknown): void;
|
|
16
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Marks an entity as dirty.
|
|
50
|
+
* @param entity - The entity to mark
|
|
51
|
+
*/
|
|
17
52
|
markDirty(entity: unknown): void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Marks an entity as removed.
|
|
56
|
+
* @param entity - The entity to mark
|
|
57
|
+
*/
|
|
18
58
|
markRemoved(entity: unknown): void;
|
|
19
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Gets all tracked entities for a table.
|
|
62
|
+
* @param table - The table definition
|
|
63
|
+
* @returns Array of tracked entities
|
|
64
|
+
*/
|
|
20
65
|
getEntitiesForTable(table: TableDef): TrackedEntity[];
|
|
21
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Registers a relation change.
|
|
69
|
+
* @param root - The root entity
|
|
70
|
+
* @param relationKey - The relation key
|
|
71
|
+
* @param rootTable - The root table definition
|
|
72
|
+
* @param relationName - The relation name
|
|
73
|
+
* @param relation - The relation definition
|
|
74
|
+
* @param change - The relation change
|
|
75
|
+
*/
|
|
22
76
|
registerRelationChange(
|
|
23
77
|
root: unknown,
|
|
24
78
|
relationKey: RelationKey,
|
|
@@ -2,13 +2,33 @@ import { ColumnDef } from '../schema/column.js';
|
|
|
2
2
|
import { defineTable, TableDef, TableHooks } from '../schema/table.js';
|
|
3
3
|
import { CascadeMode, RelationKinds } from '../schema/relation.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Constructor type for entities.
|
|
7
|
+
* Supports any constructor signature for maximum flexibility with decorator-based entities.
|
|
8
|
+
* @template T - The entity type
|
|
9
|
+
*/
|
|
10
|
+
export type EntityConstructor<T = object> = new (...args: never[]) => T;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Target that can be an entity constructor or table definition.
|
|
14
|
+
*/
|
|
7
15
|
export type EntityOrTableTarget = EntityConstructor | TableDef;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolver for entity or table target, can be direct or function.
|
|
19
|
+
*/
|
|
8
20
|
export type EntityOrTableTargetResolver = EntityOrTableTarget | (() => EntityOrTableTarget);
|
|
9
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Simplified column definition structure used during metadata registration.
|
|
24
|
+
* @template T - Concrete column definition type being extended
|
|
25
|
+
*/
|
|
10
26
|
export type ColumnDefLike<T extends ColumnDef = ColumnDef> = Omit<T, 'name' | 'table'>;
|
|
11
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Transforms simplified column metadata into full ColumnDef objects during table building.
|
|
30
|
+
* @template TColumns - Mapping of column names to simplified definitions
|
|
31
|
+
*/
|
|
12
32
|
type MaterializeColumns<TColumns extends Record<string, ColumnDefLike>> = {
|
|
13
33
|
[K in keyof TColumns]: ColumnDef<TColumns[K]['type'], TColumns[K]['tsType']> & Omit<
|
|
14
34
|
TColumns[K],
|
|
@@ -16,62 +36,119 @@ type MaterializeColumns<TColumns extends Record<string, ColumnDefLike>> = {
|
|
|
16
36
|
> & { name: string; table: string };
|
|
17
37
|
};
|
|
18
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Common properties shared by all relation metadata types.
|
|
41
|
+
*/
|
|
19
42
|
interface BaseRelationMetadata {
|
|
43
|
+
/** The property key for the relation */
|
|
20
44
|
propertyKey: string;
|
|
45
|
+
/** The target entity or table */
|
|
21
46
|
target: EntityOrTableTargetResolver;
|
|
47
|
+
/** Optional cascade mode */
|
|
22
48
|
cascade?: CascadeMode;
|
|
23
49
|
}
|
|
24
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Metadata for has many relations.
|
|
53
|
+
*/
|
|
25
54
|
export interface HasManyRelationMetadata extends BaseRelationMetadata {
|
|
55
|
+
/** The relation kind */
|
|
26
56
|
kind: typeof RelationKinds.HasMany;
|
|
57
|
+
/** The foreign key */
|
|
27
58
|
foreignKey: string;
|
|
59
|
+
/** Optional local key */
|
|
28
60
|
localKey?: string;
|
|
29
61
|
}
|
|
30
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Metadata for has one relations.
|
|
65
|
+
*/
|
|
31
66
|
export interface HasOneRelationMetadata extends BaseRelationMetadata {
|
|
67
|
+
/** The relation kind */
|
|
32
68
|
kind: typeof RelationKinds.HasOne;
|
|
69
|
+
/** The foreign key */
|
|
33
70
|
foreignKey: string;
|
|
71
|
+
/** Optional local key */
|
|
34
72
|
localKey?: string;
|
|
35
73
|
}
|
|
36
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Metadata for belongs to relations.
|
|
77
|
+
*/
|
|
37
78
|
export interface BelongsToRelationMetadata extends BaseRelationMetadata {
|
|
79
|
+
/** The relation kind */
|
|
38
80
|
kind: typeof RelationKinds.BelongsTo;
|
|
81
|
+
/** The foreign key */
|
|
39
82
|
foreignKey: string;
|
|
83
|
+
/** Optional local key */
|
|
40
84
|
localKey?: string;
|
|
41
85
|
}
|
|
42
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Metadata for belongs to many relations.
|
|
89
|
+
*/
|
|
43
90
|
export interface BelongsToManyRelationMetadata extends BaseRelationMetadata {
|
|
91
|
+
/** The relation kind */
|
|
44
92
|
kind: typeof RelationKinds.BelongsToMany;
|
|
93
|
+
/** The pivot table */
|
|
45
94
|
pivotTable: EntityOrTableTargetResolver;
|
|
95
|
+
/** The pivot foreign key to root */
|
|
46
96
|
pivotForeignKeyToRoot: string;
|
|
97
|
+
/** The pivot foreign key to target */
|
|
47
98
|
pivotForeignKeyToTarget: string;
|
|
99
|
+
/** Optional local key */
|
|
48
100
|
localKey?: string;
|
|
101
|
+
/** Optional target key */
|
|
49
102
|
targetKey?: string;
|
|
103
|
+
/** Optional pivot primary key */
|
|
50
104
|
pivotPrimaryKey?: string;
|
|
105
|
+
/** Optional default pivot columns */
|
|
51
106
|
defaultPivotColumns?: string[];
|
|
52
107
|
}
|
|
53
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Union type for all relation metadata.
|
|
111
|
+
*/
|
|
54
112
|
export type RelationMetadata =
|
|
55
113
|
| HasManyRelationMetadata
|
|
56
114
|
| HasOneRelationMetadata
|
|
57
115
|
| BelongsToRelationMetadata
|
|
58
116
|
| BelongsToManyRelationMetadata;
|
|
59
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Metadata for entities.
|
|
120
|
+
* @template TColumns - The columns type
|
|
121
|
+
*/
|
|
60
122
|
export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> = Record<string, ColumnDefLike>> {
|
|
123
|
+
/** The entity constructor */
|
|
61
124
|
target: EntityConstructor;
|
|
125
|
+
/** The table name */
|
|
62
126
|
tableName: string;
|
|
127
|
+
/** The columns */
|
|
63
128
|
columns: TColumns;
|
|
129
|
+
/** The relations */
|
|
64
130
|
relations: Record<string, RelationMetadata>;
|
|
131
|
+
/** Optional hooks */
|
|
65
132
|
hooks?: TableHooks;
|
|
133
|
+
/** Optional table definition */
|
|
66
134
|
table?: TableDef<MaterializeColumns<TColumns>>;
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
const metadataMap = new Map<EntityConstructor, EntityMetadata>();
|
|
70
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Registers entity metadata.
|
|
141
|
+
* @param meta - The entity metadata to register
|
|
142
|
+
*/
|
|
71
143
|
export const registerEntityMetadata = (meta: EntityMetadata): void => {
|
|
72
144
|
metadataMap.set(meta.target, meta);
|
|
73
145
|
};
|
|
74
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Ensures entity metadata exists for the target, creating it if necessary.
|
|
149
|
+
* @param target - The entity constructor
|
|
150
|
+
* @returns The entity metadata
|
|
151
|
+
*/
|
|
75
152
|
export const ensureEntityMetadata = (target: EntityConstructor): EntityMetadata => {
|
|
76
153
|
let meta = metadataMap.get(target);
|
|
77
154
|
if (!meta) {
|
|
@@ -86,18 +163,36 @@ export const ensureEntityMetadata = (target: EntityConstructor): EntityMetadata
|
|
|
86
163
|
return meta;
|
|
87
164
|
};
|
|
88
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Gets entity metadata for the target.
|
|
168
|
+
* @param target - The entity constructor
|
|
169
|
+
* @returns The entity metadata or undefined if not found
|
|
170
|
+
*/
|
|
89
171
|
export const getEntityMetadata = (target: EntityConstructor): EntityMetadata | undefined => {
|
|
90
172
|
return metadataMap.get(target);
|
|
91
173
|
};
|
|
92
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Gets all entity metadata.
|
|
177
|
+
* @returns Array of all entity metadata
|
|
178
|
+
*/
|
|
93
179
|
export const getAllEntityMetadata = (): EntityMetadata[] => {
|
|
94
180
|
return Array.from(metadataMap.values());
|
|
95
181
|
};
|
|
96
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Clears all entity metadata.
|
|
185
|
+
*/
|
|
97
186
|
export const clearEntityMetadata = (): void => {
|
|
98
187
|
metadataMap.clear();
|
|
99
188
|
};
|
|
100
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Adds column metadata to an entity.
|
|
192
|
+
* @param target - The entity constructor
|
|
193
|
+
* @param propertyKey - The property key
|
|
194
|
+
* @param column - The column definition
|
|
195
|
+
*/
|
|
101
196
|
export const addColumnMetadata = (
|
|
102
197
|
target: EntityConstructor,
|
|
103
198
|
propertyKey: string,
|
|
@@ -107,6 +202,12 @@ export const addColumnMetadata = (
|
|
|
107
202
|
(meta.columns as Record<string, ColumnDefLike>)[propertyKey] = { ...column };
|
|
108
203
|
};
|
|
109
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Adds relation metadata to an entity.
|
|
207
|
+
* @param target - The entity constructor
|
|
208
|
+
* @param propertyKey - The property key
|
|
209
|
+
* @param relation - The relation metadata
|
|
210
|
+
*/
|
|
110
211
|
export const addRelationMetadata = (
|
|
111
212
|
target: EntityConstructor,
|
|
112
213
|
propertyKey: string,
|
|
@@ -116,6 +217,12 @@ export const addRelationMetadata = (
|
|
|
116
217
|
meta.relations[propertyKey] = relation;
|
|
117
218
|
};
|
|
118
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Sets the table name and hooks for an entity.
|
|
222
|
+
* @param target - The entity constructor
|
|
223
|
+
* @param tableName - The table name
|
|
224
|
+
* @param hooks - Optional table hooks
|
|
225
|
+
*/
|
|
119
226
|
export const setEntityTableName = (
|
|
120
227
|
target: EntityConstructor,
|
|
121
228
|
tableName: string,
|
|
@@ -130,22 +237,28 @@ export const setEntityTableName = (
|
|
|
130
237
|
}
|
|
131
238
|
};
|
|
132
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Builds a table definition from entity metadata.
|
|
242
|
+
* @template TColumns - The columns type
|
|
243
|
+
* @param meta - The entity metadata
|
|
244
|
+
* @returns The table definition
|
|
245
|
+
*/
|
|
133
246
|
export const buildTableDef = <TColumns extends Record<string, ColumnDefLike>>(meta: EntityMetadata<TColumns>): TableDef<MaterializeColumns<TColumns>> => {
|
|
134
247
|
if (meta.table) {
|
|
135
248
|
return meta.table;
|
|
136
249
|
}
|
|
137
250
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
251
|
+
// Build columns using a simpler approach that avoids type assertion
|
|
252
|
+
const columns: Record<string, ColumnDef> = {};
|
|
253
|
+
for (const [key, def] of Object.entries(meta.columns)) {
|
|
254
|
+
columns[key] = {
|
|
141
255
|
...def,
|
|
142
256
|
name: key,
|
|
143
257
|
table: meta.tableName
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
}, {} as MaterializeColumns<TColumns>);
|
|
258
|
+
} as ColumnDef;
|
|
259
|
+
}
|
|
147
260
|
|
|
148
|
-
const table = defineTable(meta.tableName, columns
|
|
261
|
+
const table = defineTable(meta.tableName, columns as MaterializeColumns<TColumns>, {}, meta.hooks);
|
|
149
262
|
meta.table = table;
|
|
150
263
|
return table;
|
|
151
264
|
};
|
package/src/orm/execute.ts
CHANGED
|
@@ -42,6 +42,13 @@ const executeWithEntityContext = async <TTable extends TableDef>(
|
|
|
42
42
|
return hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Executes a hydrated query using the ORM session.
|
|
47
|
+
* @template TTable - The table type
|
|
48
|
+
* @param session - The ORM session
|
|
49
|
+
* @param qb - The select query builder
|
|
50
|
+
* @returns Promise resolving to array of entity instances
|
|
51
|
+
*/
|
|
45
52
|
export async function executeHydrated<TTable extends TableDef>(
|
|
46
53
|
session: OrmSession,
|
|
47
54
|
qb: SelectQueryBuilder<unknown, TTable>
|
|
@@ -49,6 +56,14 @@ export async function executeHydrated<TTable extends TableDef>(
|
|
|
49
56
|
return executeWithEntityContext(session, qb);
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Executes a hydrated query using execution and hydration contexts.
|
|
61
|
+
* @template TTable - The table type
|
|
62
|
+
* @param _execCtx - The execution context (unused)
|
|
63
|
+
* @param hydCtx - The hydration context
|
|
64
|
+
* @param qb - The select query builder
|
|
65
|
+
* @returns Promise resolving to array of entity instances
|
|
66
|
+
*/
|
|
52
67
|
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
53
68
|
_execCtx: ExecutionContext,
|
|
54
69
|
hydCtx: HydrationContext,
|
package/src/orm/lazy-batch.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
|
9
9
|
|
|
10
10
|
type Rows = Record<string, unknown>[];
|
|
11
11
|
|
|
12
|
+
type EntityTracker = ReturnType<EntityContext['getEntitiesForTable']>[number];
|
|
13
|
+
|
|
12
14
|
const selectAllColumns = (table: TableDef): Record<string, ColumnDef> =>
|
|
13
15
|
Object.entries(table.columns).reduce((acc, [name, def]) => {
|
|
14
16
|
acc[name] = def;
|
|
@@ -38,6 +40,57 @@ const executeQuery = async (ctx: EntityContext, qb: SelectQueryBuilder<unknown,
|
|
|
38
40
|
|
|
39
41
|
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
40
42
|
|
|
43
|
+
const collectKeysFromRoots = (roots: EntityTracker[], key: string): Set<unknown> => {
|
|
44
|
+
const collected = new Set<unknown>();
|
|
45
|
+
for (const tracked of roots) {
|
|
46
|
+
const value = tracked.entity[key];
|
|
47
|
+
if (value !== null && value !== undefined) {
|
|
48
|
+
collected.add(value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return collected;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const buildInListValues = (keys: Set<unknown>): (string | number | LiteralNode)[] =>
|
|
55
|
+
Array.from(keys) as (string | number | LiteralNode)[];
|
|
56
|
+
|
|
57
|
+
const fetchRowsForKeys = async (
|
|
58
|
+
ctx: EntityContext,
|
|
59
|
+
table: TableDef,
|
|
60
|
+
column: ColumnDef,
|
|
61
|
+
keys: Set<unknown>
|
|
62
|
+
): Promise<Rows> => {
|
|
63
|
+
const qb = new SelectQueryBuilder(table).select(selectAllColumns(table));
|
|
64
|
+
qb.where(inList(column, buildInListValues(keys)));
|
|
65
|
+
return executeQuery(ctx, qb);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const groupRowsByMany = (rows: Rows, keyColumn: string): Map<string, Rows> => {
|
|
69
|
+
const grouped = new Map<string, Rows>();
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
const value = row[keyColumn];
|
|
72
|
+
if (value === null || value === undefined) continue;
|
|
73
|
+
const key = toKey(value);
|
|
74
|
+
const bucket = grouped.get(key) ?? [];
|
|
75
|
+
bucket.push(row);
|
|
76
|
+
grouped.set(key, bucket);
|
|
77
|
+
}
|
|
78
|
+
return grouped;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const groupRowsByUnique = (rows: Rows, keyColumn: string): Map<string, Record<string, unknown>> => {
|
|
82
|
+
const lookup = new Map<string, Record<string, unknown>>();
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
const value = row[keyColumn];
|
|
85
|
+
if (value === null || value === undefined) continue;
|
|
86
|
+
const key = toKey(value);
|
|
87
|
+
if (!lookup.has(key)) {
|
|
88
|
+
lookup.set(key, row);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lookup;
|
|
92
|
+
};
|
|
93
|
+
|
|
41
94
|
export const loadHasManyRelation = async (
|
|
42
95
|
ctx: EntityContext,
|
|
43
96
|
rootTable: TableDef,
|
|
@@ -46,39 +99,17 @@ export const loadHasManyRelation = async (
|
|
|
46
99
|
): Promise<Map<string, Rows>> => {
|
|
47
100
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
48
101
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
49
|
-
const keys =
|
|
50
|
-
|
|
51
|
-
for (const tracked of roots) {
|
|
52
|
-
const value = tracked.entity[localKey];
|
|
53
|
-
if (value !== null && value !== undefined) {
|
|
54
|
-
keys.add(value);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
102
|
+
const keys = collectKeysFromRoots(roots, localKey);
|
|
57
103
|
|
|
58
104
|
if (!keys.size) {
|
|
59
105
|
return new Map();
|
|
60
106
|
}
|
|
61
107
|
|
|
62
|
-
const selectMap = selectAllColumns(relation.target);
|
|
63
|
-
const fb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
64
108
|
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
65
109
|
if (!fkColumn) return new Map();
|
|
66
110
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const rows = await executeQuery(ctx, fb);
|
|
70
|
-
const grouped = new Map<string, Rows>();
|
|
71
|
-
|
|
72
|
-
for (const row of rows) {
|
|
73
|
-
const fkValue = row[relation.foreignKey];
|
|
74
|
-
if (fkValue === null || fkValue === undefined) continue;
|
|
75
|
-
const key = toKey(fkValue);
|
|
76
|
-
const bucket = grouped.get(key) ?? [];
|
|
77
|
-
bucket.push(row);
|
|
78
|
-
grouped.set(key, bucket);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return grouped;
|
|
111
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
|
|
112
|
+
return groupRowsByMany(rows, relation.foreignKey);
|
|
82
113
|
};
|
|
83
114
|
|
|
84
115
|
export const loadHasOneRelation = async (
|
|
@@ -89,39 +120,17 @@ export const loadHasOneRelation = async (
|
|
|
89
120
|
): Promise<Map<string, Record<string, unknown>>> => {
|
|
90
121
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
91
122
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
92
|
-
const keys =
|
|
93
|
-
|
|
94
|
-
for (const tracked of roots) {
|
|
95
|
-
const value = tracked.entity[localKey];
|
|
96
|
-
if (value !== null && value !== undefined) {
|
|
97
|
-
keys.add(value);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
123
|
+
const keys = collectKeysFromRoots(roots, localKey);
|
|
100
124
|
|
|
101
125
|
if (!keys.size) {
|
|
102
126
|
return new Map();
|
|
103
127
|
}
|
|
104
128
|
|
|
105
|
-
const selectMap = selectAllColumns(relation.target);
|
|
106
|
-
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
107
129
|
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
108
130
|
if (!fkColumn) return new Map();
|
|
109
131
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const rows = await executeQuery(ctx, qb);
|
|
113
|
-
const lookup = new Map<string, Record<string, unknown>>();
|
|
114
|
-
|
|
115
|
-
for (const row of rows) {
|
|
116
|
-
const fkValue = row[relation.foreignKey];
|
|
117
|
-
if (fkValue === null || fkValue === undefined) continue;
|
|
118
|
-
const key = toKey(fkValue);
|
|
119
|
-
if (!lookup.has(key)) {
|
|
120
|
-
lookup.set(key, row);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return lookup;
|
|
132
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
|
|
133
|
+
return groupRowsByUnique(rows, relation.foreignKey);
|
|
125
134
|
};
|
|
126
135
|
|
|
127
136
|
export const loadBelongsToRelation = async (
|
|
@@ -131,36 +140,18 @@ export const loadBelongsToRelation = async (
|
|
|
131
140
|
relation: BelongsToRelation
|
|
132
141
|
): Promise<Map<string, Record<string, unknown>>> => {
|
|
133
142
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
134
|
-
const foreignKeys =
|
|
135
|
-
|
|
136
|
-
for (const tracked of roots) {
|
|
137
|
-
const value = tracked.entity[relation.foreignKey];
|
|
138
|
-
if (value !== null && value !== undefined) {
|
|
139
|
-
foreignKeys.add(value);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
143
|
+
const foreignKeys = collectKeysFromRoots(roots, relation.foreignKey);
|
|
142
144
|
|
|
143
145
|
if (!foreignKeys.size) {
|
|
144
146
|
return new Map();
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
const selectMap = selectAllColumns(relation.target);
|
|
148
|
-
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
149
149
|
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
150
150
|
const pkColumn = relation.target.columns[targetKey];
|
|
151
151
|
if (!pkColumn) return new Map();
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const map = new Map<string, Record<string, unknown>>();
|
|
156
|
-
|
|
157
|
-
for (const row of rows) {
|
|
158
|
-
const keyValue = row[targetKey];
|
|
159
|
-
if (keyValue === null || keyValue === undefined) continue;
|
|
160
|
-
map.set(toKey(keyValue), row);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return map;
|
|
153
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys);
|
|
154
|
+
return groupRowsByUnique(rows, targetKey);
|
|
164
155
|
};
|
|
165
156
|
|
|
166
157
|
export const loadBelongsToManyRelation = async (
|
|
@@ -171,27 +162,16 @@ export const loadBelongsToManyRelation = async (
|
|
|
171
162
|
): Promise<Map<string, Rows>> => {
|
|
172
163
|
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
173
164
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
174
|
-
const rootIds =
|
|
175
|
-
|
|
176
|
-
for (const tracked of roots) {
|
|
177
|
-
const value = tracked.entity[rootKey];
|
|
178
|
-
if (value !== null && value !== undefined) {
|
|
179
|
-
rootIds.add(value);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
165
|
+
const rootIds = collectKeysFromRoots(roots, rootKey);
|
|
182
166
|
|
|
183
167
|
if (!rootIds.size) {
|
|
184
168
|
return new Map();
|
|
185
169
|
}
|
|
186
170
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
190
|
-
if (!pivotFkCol) return new Map();
|
|
191
|
-
|
|
192
|
-
pivotQb.where(inList(pivotFkCol, Array.from(rootIds) as (string | number | LiteralNode)[]));
|
|
193
|
-
const pivotRows = await executeQuery(ctx, pivotQb);
|
|
171
|
+
const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
172
|
+
if (!pivotColumn) return new Map();
|
|
194
173
|
|
|
174
|
+
const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds);
|
|
195
175
|
const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
|
|
196
176
|
const targetIds = new Set<unknown>();
|
|
197
177
|
|
|
@@ -214,22 +194,12 @@ export const loadBelongsToManyRelation = async (
|
|
|
214
194
|
return new Map();
|
|
215
195
|
}
|
|
216
196
|
|
|
217
|
-
const targetSelect = selectAllColumns(relation.target);
|
|
218
197
|
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
219
198
|
const targetPkColumn = relation.target.columns[targetKey];
|
|
220
199
|
if (!targetPkColumn) return new Map();
|
|
221
200
|
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const targetRows = await executeQuery(ctx, targetQb);
|
|
225
|
-
const targetMap = new Map<string, Record<string, unknown>>();
|
|
226
|
-
|
|
227
|
-
for (const row of targetRows) {
|
|
228
|
-
const pkValue = row[targetKey];
|
|
229
|
-
if (pkValue === null || pkValue === undefined) continue;
|
|
230
|
-
targetMap.set(toKey(pkValue), row);
|
|
231
|
-
}
|
|
232
|
-
|
|
201
|
+
const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds);
|
|
202
|
+
const targetMap = groupRowsByUnique(targetRows, targetKey);
|
|
233
203
|
const result = new Map<string, Rows>();
|
|
234
204
|
|
|
235
205
|
for (const [rootId, entries] of rootLookup.entries()) {
|