@stingerloom/orm 0.2.1 → 0.3.0
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 +3 -3
- package/dist/core/AggregateQueryHandler.d.ts +17 -0
- package/dist/core/AggregateQueryHandler.d.ts.map +1 -0
- package/dist/core/AggregateQueryHandler.js +96 -0
- package/dist/core/AggregateQueryHandler.js.map +1 -0
- package/dist/core/CascadeHandler.d.ts +17 -0
- package/dist/core/CascadeHandler.d.ts.map +1 -0
- package/dist/core/CascadeHandler.js +106 -0
- package/dist/core/CascadeHandler.js.map +1 -0
- package/dist/core/Conditions.d.ts +2 -0
- package/dist/core/Conditions.d.ts.map +1 -1
- package/dist/core/Conditions.js +15 -1
- package/dist/core/Conditions.js.map +1 -1
- package/dist/core/EntityManager.d.ts +30 -45
- package/dist/core/EntityManager.d.ts.map +1 -1
- package/dist/core/EntityManager.js +483 -1501
- package/dist/core/EntityManager.js.map +1 -1
- package/dist/core/EntityManagerInternals.d.ts +28 -0
- package/dist/core/EntityManagerInternals.d.ts.map +1 -0
- package/dist/core/EntityManagerInternals.js +3 -0
- package/dist/core/EntityManagerInternals.js.map +1 -0
- package/dist/core/ExplainQueryHandler.d.ts +16 -0
- package/dist/core/ExplainQueryHandler.d.ts.map +1 -0
- package/dist/core/ExplainQueryHandler.js +236 -0
- package/dist/core/ExplainQueryHandler.js.map +1 -0
- package/dist/core/QueryTracker.d.ts +5 -1
- package/dist/core/QueryTracker.d.ts.map +1 -1
- package/dist/core/QueryTracker.js +75 -21
- package/dist/core/QueryTracker.js.map +1 -1
- package/dist/core/RelationLoader.d.ts +13 -0
- package/dist/core/RelationLoader.d.ts.map +1 -0
- package/dist/core/RelationLoader.js +206 -0
- package/dist/core/RelationLoader.js.map +1 -0
- package/dist/core/RelationMetadataResolver.d.ts +24 -0
- package/dist/core/RelationMetadataResolver.d.ts.map +1 -0
- package/dist/core/RelationMetadataResolver.js +182 -0
- package/dist/core/RelationMetadataResolver.js.map +1 -0
- package/dist/core/ReplicationManager.d.ts +11 -0
- package/dist/core/ReplicationManager.d.ts.map +1 -0
- package/dist/core/ReplicationManager.js +38 -0
- package/dist/core/ReplicationManager.js.map +1 -0
- package/dist/core/SchemaRegistrar.d.ts +15 -0
- package/dist/core/SchemaRegistrar.d.ts.map +1 -0
- package/dist/core/SchemaRegistrar.js +238 -0
- package/dist/core/SchemaRegistrar.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -1
- package/dist/decorators/Entity.d.ts.map +1 -1
- package/dist/decorators/Entity.js +9 -3
- package/dist/decorators/Entity.js.map +1 -1
- package/dist/decorators/Version.d.ts.map +1 -1
- package/dist/decorators/Version.js +3 -3
- package/dist/decorators/Version.js.map +1 -1
- package/dist/dialects/FindOption.d.ts +2 -2
- package/dist/dialects/FindOption.d.ts.map +1 -1
- package/dist/dialects/TransactionSessionManager.d.ts.map +1 -1
- package/dist/dialects/TransactionSessionManager.js +5 -2
- package/dist/dialects/TransactionSessionManager.js.map +1 -1
- package/dist/dialects/mysql/MySqlDataSource.d.ts.map +1 -1
- package/dist/dialects/mysql/MySqlDataSource.js +3 -0
- package/dist/dialects/mysql/MySqlDataSource.js.map +1 -1
- package/dist/dialects/mysql/MySqlDriver.d.ts.map +1 -1
- package/dist/dialects/mysql/MySqlDriver.js +9 -3
- package/dist/dialects/mysql/MySqlDriver.js.map +1 -1
- package/dist/dialects/postgres/PostgresDataSource.d.ts.map +1 -1
- package/dist/dialects/postgres/PostgresDataSource.js +3 -0
- package/dist/dialects/postgres/PostgresDataSource.js.map +1 -1
- package/dist/dialects/postgres/PostgresDriver.d.ts.map +1 -1
- package/dist/dialects/postgres/PostgresDriver.js +4 -0
- package/dist/dialects/postgres/PostgresDriver.js.map +1 -1
- package/dist/dialects/sqlite/SqliteDataSource.d.ts.map +1 -1
- package/dist/dialects/sqlite/SqliteDataSource.js +3 -0
- package/dist/dialects/sqlite/SqliteDataSource.js.map +1 -1
- package/dist/dialects/sqlite/SqliteDriver.d.ts.map +1 -1
- package/dist/dialects/sqlite/SqliteDriver.js +4 -0
- package/dist/dialects/sqlite/SqliteDriver.js.map +1 -1
- package/dist/errors/DatabaseConnectionFailedError.d.ts +2 -1
- package/dist/errors/DatabaseConnectionFailedError.d.ts.map +1 -1
- package/dist/errors/DatabaseConnectionFailedError.js +12 -2
- package/dist/errors/DatabaseConnectionFailedError.js.map +1 -1
- package/dist/errors/OptimisticLockError.d.ts +5 -0
- package/dist/errors/OptimisticLockError.d.ts.map +1 -0
- package/dist/errors/OptimisticLockError.js +14 -0
- package/dist/errors/OptimisticLockError.js.map +1 -0
- package/dist/errors/OrmErrorCode.d.ts +3 -1
- package/dist/errors/OrmErrorCode.d.ts.map +1 -1
- package/dist/errors/OrmErrorCode.js +2 -0
- package/dist/errors/OrmErrorCode.js.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/validateSavepointName.d.ts +2 -0
- package/dist/utils/validateSavepointName.d.ts.map +1 -0
- package/dist/utils/validateSavepointName.js +12 -0
- package/dist/utils/validateSavepointName.js.map +1 -0
- package/package.json +7 -3
|
@@ -32,46 +32,43 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.EntityManager = void 0;
|
|
40
37
|
require("reflect-metadata");
|
|
41
38
|
const utils_1 = require("../utils");
|
|
42
|
-
const scanner_1 = require("../scanner");
|
|
43
|
-
const typedi_1 = __importDefault(require("typedi"));
|
|
44
39
|
const DatabaseClient_1 = require("../DatabaseClient");
|
|
45
40
|
const MySqlDriver_1 = require("../dialects/mysql/MySqlDriver");
|
|
46
41
|
const PostgresDriver_1 = require("../dialects/postgres/PostgresDriver");
|
|
47
42
|
const SqliteDriver_1 = require("../dialects/sqlite/SqliteDriver");
|
|
48
|
-
const SchemaGenerator_1 = require("./generators/SchemaGenerator");
|
|
49
|
-
const Indexer_1 = require("../decorators/Indexer");
|
|
50
|
-
const UniqueIndex_1 = require("../decorators/UniqueIndex");
|
|
51
43
|
const TransactionSessionManager_1 = require("../dialects/TransactionSessionManager");
|
|
52
44
|
const MySqlDataSource_1 = require("../dialects/mysql/MySqlDataSource");
|
|
53
45
|
const PostgresDataSource_1 = require("../dialects/postgres/PostgresDataSource");
|
|
54
46
|
const SqliteDataSource_1 = require("../dialects/sqlite/SqliteDataSource");
|
|
55
47
|
const sql_template_tag_1 = __importStar(require("sql-template-tag"));
|
|
56
|
-
const decorators_1 = require("../decorators");
|
|
57
48
|
const BaseRepository_1 = require("./BaseRepository");
|
|
58
|
-
const EntityNotFound_1 = require("../dialects/EntityNotFound");
|
|
59
49
|
const RawQueryBuilderFactory_1 = require("./RawQueryBuilderFactory");
|
|
60
50
|
const Conditions_1 = require("./Conditions");
|
|
61
51
|
const ResultTransformerFactory_1 = require("./ResultTransformerFactory");
|
|
62
52
|
const MetadataContext_1 = require("../metadata/MetadataContext");
|
|
63
53
|
const LazyLoader_1 = require("./LazyLoader");
|
|
64
|
-
const CascadeType_1 = require("../types/CascadeType");
|
|
65
54
|
const EntityValidator_1 = require("./EntityValidator");
|
|
66
55
|
const EntityEventEmitter_1 = require("./EntityEventEmitter");
|
|
67
56
|
const EntityMetadataNotFoundError_1 = require("../errors/EntityMetadataNotFoundError");
|
|
68
57
|
const InvalidQueryError_1 = require("../errors/InvalidQueryError");
|
|
58
|
+
const OptimisticLockError_1 = require("../errors/OptimisticLockError");
|
|
69
59
|
const PrimaryKeyNotFoundError_1 = require("../errors/PrimaryKeyNotFoundError");
|
|
70
60
|
const DeleteWithoutConditionsError_1 = require("../errors/DeleteWithoutConditionsError");
|
|
71
61
|
const NotSupportedDatabaseTypeError_1 = require("../errors/NotSupportedDatabaseTypeError");
|
|
72
62
|
const QueryTracker_1 = require("./QueryTracker");
|
|
73
63
|
const CursorPagination_1 = require("./CursorPagination");
|
|
74
|
-
const
|
|
64
|
+
const Transactional_1 = require("../decorators/Transactional");
|
|
65
|
+
const RelationMetadataResolver_1 = require("./RelationMetadataResolver");
|
|
66
|
+
const ReplicationManager_1 = require("./ReplicationManager");
|
|
67
|
+
const CascadeHandler_1 = require("./CascadeHandler");
|
|
68
|
+
const RelationLoader_1 = require("./RelationLoader");
|
|
69
|
+
const SchemaRegistrar_1 = require("./SchemaRegistrar");
|
|
70
|
+
const ExplainQueryHandler_1 = require("./ExplainQueryHandler");
|
|
71
|
+
const AggregateQueryHandler_1 = require("./AggregateQueryHandler");
|
|
75
72
|
class EntityManager {
|
|
76
73
|
constructor() {
|
|
77
74
|
this._entities = [];
|
|
@@ -79,13 +76,39 @@ class EntityManager {
|
|
|
79
76
|
this.dirtyEntities = new Set();
|
|
80
77
|
this.eventEmitter = new EntityEventEmitter_1.EntityEventEmitter();
|
|
81
78
|
this.subscribers = [];
|
|
79
|
+
this.cursorPkWarned = new Set();
|
|
82
80
|
this.queryTracker = null;
|
|
83
|
-
this.replicationRouter = null;
|
|
84
81
|
this.connectionName = "default";
|
|
82
|
+
this.resolver = new RelationMetadataResolver_1.RelationMetadataResolver();
|
|
83
|
+
this.replication = new ReplicationManager_1.ReplicationManager();
|
|
84
|
+
this._ctx = {
|
|
85
|
+
wrap: (col) => this.wrap(col),
|
|
86
|
+
isMySqlFamily: () => this.isMySqlFamily(),
|
|
87
|
+
isPostgres: () => this.isPostgres(),
|
|
88
|
+
getDriver: () => this.driver,
|
|
89
|
+
getSynchronize: () => this.client.getOptions().synchronize ?? false,
|
|
90
|
+
executeInTransaction: (fn, s, r) => this.executeInTransaction(fn, s, r),
|
|
91
|
+
beginTrackQuery: () => this.beginTrackQuery(),
|
|
92
|
+
trackQuery: (e, s, m) => this.trackQuery(e, s, m),
|
|
93
|
+
getReadNode: (u) => this.getReadNode(u),
|
|
94
|
+
getNameStrategy: (c) => this.getNameStrategy(c),
|
|
95
|
+
resolveSelectColumns: (s) => this.resolveSelectColumns(s),
|
|
96
|
+
markDirty: (e) => this.dirtyEntities.add(e),
|
|
97
|
+
findInternal: (e, o, s) => this.findInternal(e, o, s),
|
|
98
|
+
findOneInternal: (e, o, s) => this.findOneInternal(e, o, s),
|
|
99
|
+
save: (e, i) => this.save(e, i),
|
|
100
|
+
find: (e, o) => this.find(e, o),
|
|
101
|
+
delete: (e, c) => this.delete(e, c),
|
|
102
|
+
};
|
|
103
|
+
this.cascadeHandler = new CascadeHandler_1.CascadeHandler(this.resolver, this._ctx);
|
|
104
|
+
this.relationLoader = new RelationLoader_1.RelationLoader(this.resolver, this._ctx);
|
|
105
|
+
this.schemaRegistrar = new SchemaRegistrar_1.SchemaRegistrar(this.resolver, this._ctx);
|
|
106
|
+
this.explainHandler = new ExplainQueryHandler_1.ExplainQueryHandler(this.resolver, this._ctx);
|
|
107
|
+
this.aggregateHandler = new AggregateQueryHandler_1.AggregateQueryHandler(this.resolver, this._ctx);
|
|
85
108
|
}
|
|
86
109
|
async register(databaseClientOptions, connectionName = "default") {
|
|
87
110
|
await this.connect(databaseClientOptions, connectionName);
|
|
88
|
-
await this.registerEntities();
|
|
111
|
+
await this.schemaRegistrar.registerEntities();
|
|
89
112
|
}
|
|
90
113
|
get client() {
|
|
91
114
|
return DatabaseClient_1.DatabaseClient.getInstance();
|
|
@@ -132,8 +155,42 @@ class EntityManager {
|
|
|
132
155
|
this.defaultQueryTimeout = queryTimeout;
|
|
133
156
|
}
|
|
134
157
|
if (replication) {
|
|
135
|
-
this.
|
|
158
|
+
this.replication.initialize(replication);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async propagateShutdown(options) {
|
|
162
|
+
const gracefulTimeoutMs = options?.gracefulTimeoutMs ?? 0;
|
|
163
|
+
const closeConnections = options?.closeConnections ?? false;
|
|
164
|
+
let allQueriesCompleted = true;
|
|
165
|
+
if (gracefulTimeoutMs > 0 && this.queryTracker) {
|
|
166
|
+
const activeCount = this.queryTracker.activeQueryCount;
|
|
167
|
+
if (activeCount > 0) {
|
|
168
|
+
this.logger.info(`[Shutdown] Waiting for ${activeCount} active queries (timeout: ${gracefulTimeoutMs}ms)...`);
|
|
169
|
+
allQueriesCompleted = await this.queryTracker.waitForQueries(gracefulTimeoutMs);
|
|
170
|
+
if (!allQueriesCompleted) {
|
|
171
|
+
this.logger.warn(`[Shutdown] Timed out waiting for active queries. Forcing shutdown.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this.removeAllListeners();
|
|
176
|
+
this.subscribers.length = 0;
|
|
177
|
+
this.dirtyEntities.clear();
|
|
178
|
+
this.cursorPkWarned.clear();
|
|
179
|
+
this.queryTracker?.reset();
|
|
180
|
+
this.queryTracker = null;
|
|
181
|
+
this.replication.shutdown();
|
|
182
|
+
if (closeConnections) {
|
|
183
|
+
try {
|
|
184
|
+
await this.client.close(this.connectionName);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
this.logger.warn(`[Shutdown] Error closing connection '${this.connectionName}': ${err}`);
|
|
188
|
+
}
|
|
136
189
|
}
|
|
190
|
+
return allQueriesCompleted;
|
|
191
|
+
}
|
|
192
|
+
getNameStrategy(clazz) {
|
|
193
|
+
return clazz.name;
|
|
137
194
|
}
|
|
138
195
|
initQueryTracker(options) {
|
|
139
196
|
const logging = options.logging;
|
|
@@ -167,22 +224,16 @@ class EntityManager {
|
|
|
167
224
|
this.queryTracker?.track(entityName, sqlText, durationMs);
|
|
168
225
|
}
|
|
169
226
|
getReadNode(useMaster) {
|
|
170
|
-
|
|
171
|
-
return null;
|
|
172
|
-
if (useMaster)
|
|
173
|
-
return this.replicationRouter.getWriteNode();
|
|
174
|
-
return this.replicationRouter.getReadNode();
|
|
227
|
+
return this.replication.getReadNode(useMaster);
|
|
175
228
|
}
|
|
176
229
|
getWriteNode() {
|
|
177
|
-
|
|
178
|
-
return null;
|
|
179
|
-
return this.replicationRouter.getWriteNode();
|
|
230
|
+
return this.replication.getWriteNode();
|
|
180
231
|
}
|
|
181
232
|
get isReplicationEnabled() {
|
|
182
|
-
return this.
|
|
233
|
+
return this.replication.isEnabled;
|
|
183
234
|
}
|
|
184
235
|
getReplicationRouter() {
|
|
185
|
-
return this.
|
|
236
|
+
return this.replication.getRouter();
|
|
186
237
|
}
|
|
187
238
|
on(event, listener) {
|
|
188
239
|
this.eventEmitter.on(event, listener);
|
|
@@ -216,614 +267,11 @@ class EntityManager {
|
|
|
216
267
|
}
|
|
217
268
|
}
|
|
218
269
|
}
|
|
219
|
-
async propagateShutdown(options) {
|
|
220
|
-
const gracefulTimeoutMs = options?.gracefulTimeoutMs ?? 0;
|
|
221
|
-
const closeConnections = options?.closeConnections ?? false;
|
|
222
|
-
let allQueriesCompleted = true;
|
|
223
|
-
if (gracefulTimeoutMs > 0 && this.queryTracker) {
|
|
224
|
-
const activeCount = this.queryTracker.activeQueryCount;
|
|
225
|
-
if (activeCount > 0) {
|
|
226
|
-
this.logger.info(`[Shutdown] Waiting for ${activeCount} active queries (timeout: ${gracefulTimeoutMs}ms)...`);
|
|
227
|
-
allQueriesCompleted = await this.queryTracker.waitForQueries(gracefulTimeoutMs);
|
|
228
|
-
if (!allQueriesCompleted) {
|
|
229
|
-
this.logger.warn(`[Shutdown] Timed out waiting for active queries. Forcing shutdown.`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
this.removeAllListeners();
|
|
234
|
-
this.subscribers.length = 0;
|
|
235
|
-
this.dirtyEntities.clear();
|
|
236
|
-
this.queryTracker?.reset();
|
|
237
|
-
this.queryTracker = null;
|
|
238
|
-
if (this.replicationRouter) {
|
|
239
|
-
this.replicationRouter.resetFailedSlaves();
|
|
240
|
-
this.replicationRouter = null;
|
|
241
|
-
}
|
|
242
|
-
if (closeConnections) {
|
|
243
|
-
try {
|
|
244
|
-
await this.client.close(this.connectionName);
|
|
245
|
-
}
|
|
246
|
-
catch (err) {
|
|
247
|
-
this.logger.warn(`[Shutdown] Error closing connection '${this.connectionName}': ${err}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return allQueriesCompleted;
|
|
251
|
-
}
|
|
252
|
-
getNameStrategy(clazz) {
|
|
253
|
-
return clazz.name;
|
|
254
|
-
}
|
|
255
|
-
resolveEntityMetadata(entity) {
|
|
256
|
-
const context = MetadataContext_1.MetadataContext.isActive()
|
|
257
|
-
? MetadataContext_1.MetadataContext.getCurrentTenant()
|
|
258
|
-
: "public";
|
|
259
|
-
const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
|
|
260
|
-
const layeredMetadata = entityScanner.scan(entity);
|
|
261
|
-
if (layeredMetadata) {
|
|
262
|
-
return layeredMetadata;
|
|
263
|
-
}
|
|
264
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
|
|
265
|
-
if (reflectMetadata) {
|
|
266
|
-
this.logger.warn(`[resolveEntityMetadata] "${entity.name}" resolved via Reflect.getMetadata fallback (context: "${context}"). ` +
|
|
267
|
-
`This entity was not found in the layered store.`);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
this.logger.error(`[resolveEntityMetadata] "${entity.name}" not found in any metadata source (context: "${context}")`);
|
|
271
|
-
}
|
|
272
|
-
return reflectMetadata ?? null;
|
|
273
|
-
}
|
|
274
|
-
getDeletedAtColumn(entity) {
|
|
275
|
-
const column = Reflect.getMetadata(decorators_1.DELETED_AT_TOKEN, entity);
|
|
276
|
-
return column ?? null;
|
|
277
|
-
}
|
|
278
|
-
getCreateTimestampColumn(entity) {
|
|
279
|
-
const column = Reflect.getMetadata(decorators_1.CREATE_TIMESTAMP_TOKEN, entity);
|
|
280
|
-
return column ?? null;
|
|
281
|
-
}
|
|
282
|
-
getUpdateTimestampColumn(entity) {
|
|
283
|
-
const column = Reflect.getMetadata(decorators_1.UPDATE_TIMESTAMP_TOKEN, entity);
|
|
284
|
-
return column ?? null;
|
|
285
|
-
}
|
|
286
|
-
async runHooks(entity, item, event) {
|
|
287
|
-
const hooks = Reflect.getMetadata(decorators_1.HOOK_TOKEN, entity);
|
|
288
|
-
if (!hooks || hooks.length === 0)
|
|
289
|
-
return;
|
|
290
|
-
for (const hook of hooks) {
|
|
291
|
-
if (hook.event !== event)
|
|
292
|
-
continue;
|
|
293
|
-
const method = item[hook.methodName];
|
|
294
|
-
if (typeof method === "function") {
|
|
295
|
-
await method.call(item);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
createProxy(entity) {
|
|
300
|
-
return new Proxy(entity, {
|
|
301
|
-
set: (target, prop, value) => {
|
|
302
|
-
target[prop] = value;
|
|
303
|
-
this.dirtyEntities.add(target);
|
|
304
|
-
return true;
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async registerEntities() {
|
|
309
|
-
const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
|
|
310
|
-
const entities = entityScanner.makeEntities();
|
|
311
|
-
let entity;
|
|
312
|
-
const { synchronize } = this.client.getOptions();
|
|
313
|
-
if (synchronize && this.isPostgres() && this.driver) {
|
|
314
|
-
const pgDriver = this.driver;
|
|
315
|
-
const hasSchema = await pgDriver.hasSchema();
|
|
316
|
-
if (!hasSchema || hasSchema.length === 0) {
|
|
317
|
-
await pgDriver.createSchema();
|
|
318
|
-
await pgDriver.setSearchPath();
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const entityList = [];
|
|
322
|
-
while ((entity = entities.next())) {
|
|
323
|
-
if (entity.done) {
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
const metadata = entity.value;
|
|
327
|
-
const TargetEntity = metadata.target;
|
|
328
|
-
let tableName = metadata.name;
|
|
329
|
-
if (!tableName) {
|
|
330
|
-
tableName = this.getNameStrategy(TargetEntity);
|
|
331
|
-
}
|
|
332
|
-
if (!utils_1.ReflectManager.isEntity(TargetEntity)) {
|
|
333
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(tableName ?? "Unknown");
|
|
334
|
-
}
|
|
335
|
-
if (synchronize) {
|
|
336
|
-
const hasTable = await this.driver?.hasTable(tableName);
|
|
337
|
-
if (!hasTable || hasTable.length === 0) {
|
|
338
|
-
await this.driver?.createTable(tableName, metadata.columns);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
entityList.push({ TargetEntity, tableName, metadata });
|
|
342
|
-
}
|
|
343
|
-
if (synchronize) {
|
|
344
|
-
for (const { TargetEntity, tableName } of entityList) {
|
|
345
|
-
await this.registerForeignKeys(TargetEntity, tableName);
|
|
346
|
-
await this.registerIndex(TargetEntity, tableName);
|
|
347
|
-
await this.registerUniqueIndexes(TargetEntity, tableName);
|
|
348
|
-
}
|
|
349
|
-
await this.registerManyToManyJoinTables(entityList.map((e) => e.TargetEntity));
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
async registerUniqueIndexes(TargetEntity, tableName) {
|
|
353
|
-
const uniqueIndexes = Reflect.getMetadata(UniqueIndex_1.UNIQUE_INDEX_TOKEN, TargetEntity);
|
|
354
|
-
if (!uniqueIndexes || uniqueIndexes.length === 0)
|
|
355
|
-
return;
|
|
356
|
-
for (const uq of uniqueIndexes) {
|
|
357
|
-
const indexName = uq.name ?? `uq_${tableName}_${uq.columns.join("_")}`;
|
|
358
|
-
const indexes = (await this.driver?.getIndexes(tableName));
|
|
359
|
-
let isExist = false;
|
|
360
|
-
for (const idx of indexes || []) {
|
|
361
|
-
const existingIndexName = idx["Key_name"] ?? idx["Field"] ?? idx["name"];
|
|
362
|
-
if (existingIndexName === indexName) {
|
|
363
|
-
isExist = true;
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (!isExist) {
|
|
368
|
-
await this.driver?.addCompositeUniqueIndex(tableName, uq.columns, indexName);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async registerManyToManyJoinTables(entities) {
|
|
373
|
-
const processedTables = new Set();
|
|
374
|
-
for (const entity of entities) {
|
|
375
|
-
const m2mMeta = (Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
|
|
376
|
-
[]);
|
|
377
|
-
for (const rel of m2mMeta) {
|
|
378
|
-
if (!rel.joinTable)
|
|
379
|
-
continue;
|
|
380
|
-
const { name: joinTableName, joinColumn, inverseJoinColumn, } = rel.joinTable;
|
|
381
|
-
if (processedTables.has(joinTableName))
|
|
382
|
-
continue;
|
|
383
|
-
processedTables.add(joinTableName);
|
|
384
|
-
const ownerEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
|
|
385
|
-
const ownerTable = ownerEntityMeta?.name ?? entity.name;
|
|
386
|
-
const relatedEntity = rel.getRelatedEntity();
|
|
387
|
-
const relatedEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, relatedEntity);
|
|
388
|
-
const relatedTable = relatedEntityMeta?.name ?? relatedEntity.name;
|
|
389
|
-
const hasTable = await this.driver?.hasTable(joinTableName);
|
|
390
|
-
if (!hasTable || hasTable.length === 0) {
|
|
391
|
-
const wJoinTable = this.wrap(joinTableName);
|
|
392
|
-
const wJoinCol = this.wrap(joinColumn);
|
|
393
|
-
const wInvCol = this.wrap(inverseJoinColumn);
|
|
394
|
-
let ddl = `CREATE TABLE IF NOT EXISTS ${wJoinTable} (${wJoinCol} INT NOT NULL, ${wInvCol} INT NOT NULL, PRIMARY KEY (${wJoinCol}, ${wInvCol}))`;
|
|
395
|
-
if (this.isMySqlFamily())
|
|
396
|
-
ddl += " ENGINE=InnoDB";
|
|
397
|
-
await this.driver?.executeRaw(ddl);
|
|
398
|
-
}
|
|
399
|
-
const ownerColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ?? []);
|
|
400
|
-
const ownerPk = ownerColumns.find((c) => c.options?.primary)?.name;
|
|
401
|
-
const relatedColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, relatedEntity.prototype) ?? []);
|
|
402
|
-
const relatedPk = relatedColumns.find((c) => c.options?.primary)?.name;
|
|
403
|
-
const ownerFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, joinColumn, ownerTable);
|
|
404
|
-
if (ownerPk &&
|
|
405
|
-
this.driver &&
|
|
406
|
-
!(await this.driver.hasForeignKey(joinTableName, ownerFkName))) {
|
|
407
|
-
const ddl = `ALTER TABLE ${this.wrap(joinTableName)} ADD CONSTRAINT ${ownerFkName} FOREIGN KEY (${this.wrap(joinColumn)}) REFERENCES ${this.wrap(ownerTable)}(${this.wrap(ownerPk)}) ON DELETE CASCADE ON UPDATE CASCADE`;
|
|
408
|
-
await this.driver.executeRaw(ddl);
|
|
409
|
-
}
|
|
410
|
-
const relatedFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, inverseJoinColumn, relatedTable);
|
|
411
|
-
if (relatedPk &&
|
|
412
|
-
this.driver &&
|
|
413
|
-
!(await this.driver.hasForeignKey(joinTableName, relatedFkName))) {
|
|
414
|
-
const ddl = `ALTER TABLE ${this.wrap(joinTableName)} ADD CONSTRAINT ${relatedFkName} FOREIGN KEY (${this.wrap(inverseJoinColumn)}) REFERENCES ${this.wrap(relatedTable)}(${this.wrap(relatedPk)}) ON DELETE CASCADE ON UPDATE CASCADE`;
|
|
415
|
-
await this.driver.executeRaw(ddl);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
resolveManyToOneMetadata(entity) {
|
|
421
|
-
const manyToOneScanner = typedi_1.default.get(scanner_1.ManyToOneScanner);
|
|
422
|
-
const allRelations = manyToOneScanner
|
|
423
|
-
.allMetadata()
|
|
424
|
-
.filter((rel) => rel.target === entity);
|
|
425
|
-
if (allRelations.length > 0) {
|
|
426
|
-
return this.resolveJoinColumnsFromColumnMeta(entity, allRelations);
|
|
427
|
-
}
|
|
428
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity) ??
|
|
429
|
-
Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity.prototype);
|
|
430
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
431
|
-
this.logger.warn(`[resolveManyToOneMetadata] "${entity.name}" ManyToOne resolved via Reflect.getMetadata fallback.`);
|
|
432
|
-
return this.resolveJoinColumnsFromColumnMeta(entity, reflectMetadata);
|
|
433
|
-
}
|
|
434
|
-
return [];
|
|
435
|
-
}
|
|
436
|
-
resolveJoinColumnsFromColumnMeta(entity, relations) {
|
|
437
|
-
const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
|
|
438
|
-
Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
|
|
439
|
-
[];
|
|
440
|
-
if (columnsMeta.length === 0) {
|
|
441
|
-
return relations;
|
|
442
|
-
}
|
|
443
|
-
return relations.map((rel) => {
|
|
444
|
-
if (rel.joinColumn)
|
|
445
|
-
return rel;
|
|
446
|
-
const fkPropertyName = `${rel.columnName}Id`;
|
|
447
|
-
const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
|
|
448
|
-
if (!matchingColumn)
|
|
449
|
-
return rel;
|
|
450
|
-
const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
|
|
451
|
-
return {
|
|
452
|
-
...rel,
|
|
453
|
-
joinColumn: resolvedJoinColumn,
|
|
454
|
-
};
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
resolveOneToManyMetadata(entity) {
|
|
458
|
-
const oneToManyScanner = typedi_1.default.get(scanner_1.OneToManyScanner);
|
|
459
|
-
const allRelations = oneToManyScanner
|
|
460
|
-
.allMetadata()
|
|
461
|
-
.filter((rel) => rel.target === entity);
|
|
462
|
-
if (allRelations.length > 0) {
|
|
463
|
-
return allRelations;
|
|
464
|
-
}
|
|
465
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity) ??
|
|
466
|
-
Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity.prototype);
|
|
467
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
468
|
-
this.logger.warn(`[resolveOneToManyMetadata] "${entity.name}" OneToMany resolved via Reflect.getMetadata fallback.`);
|
|
469
|
-
return reflectMetadata;
|
|
470
|
-
}
|
|
471
|
-
return [];
|
|
472
|
-
}
|
|
473
|
-
async loadOneToManyRelations(entity, parentResults, relations) {
|
|
474
|
-
const oneToManyMeta = this.resolveOneToManyMetadata(entity);
|
|
475
|
-
if (oneToManyMeta.length === 0)
|
|
476
|
-
return;
|
|
477
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
478
|
-
if (!parentMetadata)
|
|
479
|
-
return;
|
|
480
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
481
|
-
if (!pk)
|
|
482
|
-
return;
|
|
483
|
-
const parents = Array.isArray(parentResults)
|
|
484
|
-
? parentResults
|
|
485
|
-
: [parentResults];
|
|
486
|
-
for (const rel of oneToManyMeta) {
|
|
487
|
-
if (!relations.includes(rel.propertyKey))
|
|
488
|
-
continue;
|
|
489
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
490
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
491
|
-
if (!relatedMetadata)
|
|
492
|
-
continue;
|
|
493
|
-
const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
|
|
494
|
-
const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
|
|
495
|
-
const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
|
|
496
|
-
for (const parent of parents) {
|
|
497
|
-
const parentId = parent[pk.name];
|
|
498
|
-
if (parentId === undefined || parentId === null)
|
|
499
|
-
continue;
|
|
500
|
-
const children = await this.find(RelatedEntity, {
|
|
501
|
-
where: { [fkColumn]: parentId },
|
|
502
|
-
});
|
|
503
|
-
if (children === undefined) {
|
|
504
|
-
parent[rel.propertyKey] = [];
|
|
505
|
-
}
|
|
506
|
-
else if (Array.isArray(children)) {
|
|
507
|
-
parent[rel.propertyKey] = children;
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
parent[rel.propertyKey] = [children];
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
resolveManyToManyMetadata(entity) {
|
|
516
|
-
const manyToManyScanner = typedi_1.default.get(scanner_1.ManyToManyScanner);
|
|
517
|
-
const allRelations = manyToManyScanner
|
|
518
|
-
.allMetadata()
|
|
519
|
-
.filter((rel) => rel.target === entity);
|
|
520
|
-
if (allRelations.length > 0) {
|
|
521
|
-
return allRelations;
|
|
522
|
-
}
|
|
523
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
|
|
524
|
-
Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity.prototype);
|
|
525
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
526
|
-
this.logger.warn(`[resolveManyToManyMetadata] "${entity.name}" ManyToMany resolved via Reflect.getMetadata fallback.`);
|
|
527
|
-
return reflectMetadata;
|
|
528
|
-
}
|
|
529
|
-
return [];
|
|
530
|
-
}
|
|
531
|
-
resolveOneToOneMetadata(entity) {
|
|
532
|
-
const oneToOneScanner = typedi_1.default.get(scanner_1.OneToOneScanner);
|
|
533
|
-
const allRelations = oneToOneScanner
|
|
534
|
-
.allMetadata()
|
|
535
|
-
.filter((rel) => rel.target === entity);
|
|
536
|
-
if (allRelations.length > 0) {
|
|
537
|
-
return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, allRelations);
|
|
538
|
-
}
|
|
539
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity) ??
|
|
540
|
-
Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity.prototype);
|
|
541
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
542
|
-
this.logger.warn(`[resolveOneToOneMetadata] "${entity.name}" OneToOne resolved via Reflect.getMetadata fallback.`);
|
|
543
|
-
return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, reflectMetadata);
|
|
544
|
-
}
|
|
545
|
-
return [];
|
|
546
|
-
}
|
|
547
|
-
resolveJoinColumnsFromColumnMetaForOneToOne(entity, relations) {
|
|
548
|
-
const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
|
|
549
|
-
Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
|
|
550
|
-
[];
|
|
551
|
-
if (columnsMeta.length === 0) {
|
|
552
|
-
return relations;
|
|
553
|
-
}
|
|
554
|
-
return relations.map((rel) => {
|
|
555
|
-
if (rel.joinColumn)
|
|
556
|
-
return rel;
|
|
557
|
-
const fkPropertyName = `${rel.propertyKey}Id`;
|
|
558
|
-
const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
|
|
559
|
-
if (!matchingColumn)
|
|
560
|
-
return rel;
|
|
561
|
-
const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
|
|
562
|
-
return {
|
|
563
|
-
...rel,
|
|
564
|
-
joinColumn: resolvedJoinColumn,
|
|
565
|
-
};
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
resolveManyToManyJoinTable(rel) {
|
|
569
|
-
if (rel.joinTable) {
|
|
570
|
-
return {
|
|
571
|
-
joinTableName: rel.joinTable.name,
|
|
572
|
-
joinColumn: rel.joinTable.joinColumn,
|
|
573
|
-
inverseJoinColumn: rel.joinTable.inverseJoinColumn,
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
if (rel.mappedBy) {
|
|
577
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
578
|
-
const relatedManyToMany = this.resolveManyToManyMetadata(RelatedEntity);
|
|
579
|
-
const ownerRel = relatedManyToMany.find((r) => r.propertyKey === rel.mappedBy && r.joinTable);
|
|
580
|
-
if (ownerRel?.joinTable) {
|
|
581
|
-
return {
|
|
582
|
-
joinTableName: ownerRel.joinTable.name,
|
|
583
|
-
joinColumn: ownerRel.joinTable.inverseJoinColumn,
|
|
584
|
-
inverseJoinColumn: ownerRel.joinTable.joinColumn,
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
async loadManyToManyRelations(entity, parentResults, relations) {
|
|
591
|
-
const manyToManyMeta = this.resolveManyToManyMetadata(entity);
|
|
592
|
-
if (manyToManyMeta.length === 0)
|
|
593
|
-
return;
|
|
594
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
595
|
-
if (!parentMetadata)
|
|
596
|
-
return;
|
|
597
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
598
|
-
if (!pk)
|
|
599
|
-
return;
|
|
600
|
-
const parents = Array.isArray(parentResults)
|
|
601
|
-
? parentResults
|
|
602
|
-
: [parentResults];
|
|
603
|
-
for (const rel of manyToManyMeta) {
|
|
604
|
-
if (!relations.includes(rel.propertyKey))
|
|
605
|
-
continue;
|
|
606
|
-
const joinInfo = this.resolveManyToManyJoinTable(rel);
|
|
607
|
-
if (!joinInfo)
|
|
608
|
-
continue;
|
|
609
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
610
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
611
|
-
if (!relatedMetadata)
|
|
612
|
-
continue;
|
|
613
|
-
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
614
|
-
if (!relatedPk)
|
|
615
|
-
continue;
|
|
616
|
-
const relatedTableName = relatedMetadata.name ?? RelatedEntity.name;
|
|
617
|
-
for (const parent of parents) {
|
|
618
|
-
const parentId = parent[pk.name];
|
|
619
|
-
if (parentId === undefined || parentId === null)
|
|
620
|
-
continue;
|
|
621
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
622
|
-
const selectCols = relatedMetadata.columns.map((col) => `${this.wrap(relatedTableName)}.${this.wrap(col.name)}`);
|
|
623
|
-
const joinCondition = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(relatedTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(relatedPk.name))} = ${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.inverseJoinColumn))}`;
|
|
624
|
-
const whereCondition = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinTableName))}.${(0, sql_template_tag_1.raw)(this.wrap(joinInfo.joinColumn))} = ${parentId}`;
|
|
625
|
-
qb.select(selectCols)
|
|
626
|
-
.from(this.wrap(relatedTableName))
|
|
627
|
-
.innerJoin(this.wrap(joinInfo.joinTableName), this.wrap(joinInfo.joinTableName), joinCondition)
|
|
628
|
-
.where([whereCondition]);
|
|
629
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
630
|
-
try {
|
|
631
|
-
await transactionHolder.connect();
|
|
632
|
-
await transactionHolder.startTransaction();
|
|
633
|
-
if (this.isMySqlFamily()) {
|
|
634
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
635
|
-
}
|
|
636
|
-
const resultQuery = qb.build();
|
|
637
|
-
const subQueryStart = Date.now();
|
|
638
|
-
this.beginTrackQuery();
|
|
639
|
-
const queryResult = (await transactionHolder.query(resultQuery));
|
|
640
|
-
this.trackQuery(relatedTableName, resultQuery.text ?? String(resultQuery), Date.now() - subQueryStart);
|
|
641
|
-
await transactionHolder.commit();
|
|
642
|
-
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
643
|
-
const { results } = queryResult;
|
|
644
|
-
if (!results || results.length === 0) {
|
|
645
|
-
parent[rel.propertyKey] = [];
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
parent[rel.propertyKey] = resultTransformer.toEntities(RelatedEntity, queryResult);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
catch (e) {
|
|
652
|
-
try {
|
|
653
|
-
await transactionHolder.rollback();
|
|
654
|
-
}
|
|
655
|
-
catch (rollbackError) {
|
|
656
|
-
this.logger.error(`Failed to rollback ManyToMany transaction: ${rollbackError}`);
|
|
657
|
-
}
|
|
658
|
-
throw e;
|
|
659
|
-
}
|
|
660
|
-
finally {
|
|
661
|
-
try {
|
|
662
|
-
await transactionHolder.close();
|
|
663
|
-
}
|
|
664
|
-
catch (closeError) {
|
|
665
|
-
this.logger.error(`Failed to close ManyToMany transaction: ${closeError}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
async loadOneToOneRelations(entity, parentResults, relations) {
|
|
672
|
-
const oneToOneMeta = this.resolveOneToOneMetadata(entity);
|
|
673
|
-
if (oneToOneMeta.length === 0)
|
|
674
|
-
return;
|
|
675
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
676
|
-
if (!parentMetadata)
|
|
677
|
-
return;
|
|
678
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
679
|
-
if (!pk)
|
|
680
|
-
return;
|
|
681
|
-
const parents = Array.isArray(parentResults)
|
|
682
|
-
? parentResults
|
|
683
|
-
: [parentResults];
|
|
684
|
-
for (const rel of oneToOneMeta) {
|
|
685
|
-
if (!relations.includes(rel.propertyKey))
|
|
686
|
-
continue;
|
|
687
|
-
if (rel.joinColumn) {
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
691
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
692
|
-
if (!relatedMetadata)
|
|
693
|
-
continue;
|
|
694
|
-
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
695
|
-
if (!relatedPk)
|
|
696
|
-
continue;
|
|
697
|
-
for (const parent of parents) {
|
|
698
|
-
if (rel.joinColumn) {
|
|
699
|
-
const fkValue = parent[rel.joinColumn];
|
|
700
|
-
if (fkValue === undefined || fkValue === null) {
|
|
701
|
-
parent[rel.propertyKey] = null;
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
const related = await this.findOne(RelatedEntity, {
|
|
705
|
-
where: { [relatedPk.name]: fkValue },
|
|
706
|
-
});
|
|
707
|
-
parent[rel.propertyKey] = related ?? null;
|
|
708
|
-
}
|
|
709
|
-
else if (rel.inverseSide) {
|
|
710
|
-
const parentId = parent[pk.name];
|
|
711
|
-
if (parentId === undefined || parentId === null) {
|
|
712
|
-
parent[rel.propertyKey] = null;
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
const relatedOneToOne = this.resolveOneToOneMetadata(RelatedEntity);
|
|
716
|
-
const ownerRel = relatedOneToOne.find((r) => r.propertyKey === rel.inverseSide && r.joinColumn);
|
|
717
|
-
if (ownerRel?.joinColumn) {
|
|
718
|
-
const related = await this.findOne(RelatedEntity, {
|
|
719
|
-
where: { [ownerRel.joinColumn]: parentId },
|
|
720
|
-
});
|
|
721
|
-
parent[rel.propertyKey] = related ?? null;
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
parent[rel.propertyKey] = null;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
async registerForeignKeys(TargetEntity, tableName) {
|
|
731
|
-
const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
|
|
732
|
-
const manyToOneItems = this.resolveManyToOneMetadata(TargetEntity);
|
|
733
|
-
const isValidManyToOne = manyToOneItems && manyToOneItems.length > 0;
|
|
734
|
-
if (isValidManyToOne) {
|
|
735
|
-
for (const manyToOneItem of manyToOneItems) {
|
|
736
|
-
const { joinColumn } = manyToOneItem;
|
|
737
|
-
const mappingEntity = manyToOneItem.getMappingEntity();
|
|
738
|
-
if (!mappingEntity) {
|
|
739
|
-
throw new EntityNotFound_1.EntityNotFound(mappingEntity);
|
|
740
|
-
}
|
|
741
|
-
const mappingTableMetadata = entityScanner.scan(mappingEntity);
|
|
742
|
-
if (!mappingTableMetadata) {
|
|
743
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(mappingEntity.name);
|
|
744
|
-
}
|
|
745
|
-
if (!joinColumn) {
|
|
746
|
-
throw new InvalidQueryError_1.InvalidQueryError("JoinColumn does not exist.");
|
|
747
|
-
}
|
|
748
|
-
const mappingTablePrimaryKey = manyToOneItem.references
|
|
749
|
-
? manyToOneItem.references
|
|
750
|
-
: mappingTableMetadata.columns.find((e) => e.options?.primary)?.name;
|
|
751
|
-
if (!mappingTablePrimaryKey) {
|
|
752
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(mappingEntity.name);
|
|
753
|
-
}
|
|
754
|
-
const mappingTableName = mappingTableMetadata.name || this.getNameStrategy(mappingEntity);
|
|
755
|
-
if (this.driver) {
|
|
756
|
-
const columnExists = await this.driver.hasColumn(tableName, joinColumn);
|
|
757
|
-
if (!columnExists) {
|
|
758
|
-
const fkColumnType = this.driver.castType("int") + " NULL";
|
|
759
|
-
await this.driver.addColumn(tableName, joinColumn, fkColumnType);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
if (this.driver) {
|
|
763
|
-
const fkName = this.driver.generateForeignKeyName(tableName, mappingTableName, joinColumn);
|
|
764
|
-
const fkExists = await this.driver.hasForeignKey(tableName, fkName);
|
|
765
|
-
if (fkExists)
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
await this.driver?.addForeignKey(tableName, joinColumn, mappingTableName, mappingTablePrimaryKey);
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
const oneToOneItems = this.resolveOneToOneMetadata(TargetEntity);
|
|
772
|
-
for (const oneToOneItem of oneToOneItems) {
|
|
773
|
-
const { joinColumn } = oneToOneItem;
|
|
774
|
-
if (!joinColumn)
|
|
775
|
-
continue;
|
|
776
|
-
const RelatedEntity = oneToOneItem.getRelatedEntity();
|
|
777
|
-
if (!RelatedEntity) {
|
|
778
|
-
throw new EntityNotFound_1.EntityNotFound(RelatedEntity);
|
|
779
|
-
}
|
|
780
|
-
const relatedMetadata = entityScanner.scan(RelatedEntity);
|
|
781
|
-
if (!relatedMetadata) {
|
|
782
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(RelatedEntity.name);
|
|
783
|
-
}
|
|
784
|
-
const relatedPrimaryKey = relatedMetadata.columns.find((e) => e.options?.primary)?.name;
|
|
785
|
-
if (!relatedPrimaryKey) {
|
|
786
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(RelatedEntity.name);
|
|
787
|
-
}
|
|
788
|
-
if (this.driver) {
|
|
789
|
-
const columnExists = await this.driver.hasColumn(tableName, joinColumn);
|
|
790
|
-
if (!columnExists) {
|
|
791
|
-
const fkColumnType = this.driver.castType("int") + " NULL";
|
|
792
|
-
await this.driver.addColumn(tableName, joinColumn, fkColumnType);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
const relatedTableName = relatedMetadata.name || this.getNameStrategy(RelatedEntity);
|
|
796
|
-
if (this.driver) {
|
|
797
|
-
const fkName = this.driver.generateForeignKeyName(tableName, relatedTableName, joinColumn);
|
|
798
|
-
const fkExists = await this.driver.hasForeignKey(tableName, fkName);
|
|
799
|
-
if (fkExists)
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
await this.driver?.addForeignKey(tableName, joinColumn, relatedTableName, relatedPrimaryKey);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
async registerIndex(TargetEntity, tableName) {
|
|
806
|
-
const indexer = Reflect.getMetadata(Indexer_1.INDEX_TOKEN, TargetEntity.prototype);
|
|
807
|
-
if (indexer) {
|
|
808
|
-
for (const index of indexer) {
|
|
809
|
-
const indexName = `INDEX_${tableName}_${index.name}`;
|
|
810
|
-
const indexes = (await this.driver?.getIndexes(tableName));
|
|
811
|
-
let isExist = false;
|
|
812
|
-
for (const idx of indexes || []) {
|
|
813
|
-
const existingIndexName = idx["Key_name"] ?? idx["Field"];
|
|
814
|
-
if (existingIndexName === indexName) {
|
|
815
|
-
isExist = true;
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
if (!isExist) {
|
|
820
|
-
await this.driver?.addIndex(tableName, index.name, indexName);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
270
|
async findOne(entity, findOption) {
|
|
826
|
-
|
|
271
|
+
return this.findOneInternal(entity, findOption);
|
|
272
|
+
}
|
|
273
|
+
async findOneInternal(entity, findOption, existingSession) {
|
|
274
|
+
const result = await this.findInternal(entity, { ...findOption, limit: 1 }, existingSession);
|
|
827
275
|
if (result === undefined || result === null) {
|
|
828
276
|
return null;
|
|
829
277
|
}
|
|
@@ -832,347 +280,16 @@ class EntityManager {
|
|
|
832
280
|
}
|
|
833
281
|
return result;
|
|
834
282
|
}
|
|
835
|
-
async explain(entity, findOption = {}) {
|
|
836
|
-
if (!this.driver || !this.driver.supportsExplain()) {
|
|
837
|
-
throw new InvalidQueryError_1.InvalidQueryError("EXPLAIN is not supported by the current database driver.");
|
|
838
|
-
}
|
|
839
|
-
const { select, orderBy, where, take } = findOption;
|
|
840
|
-
const { limit } = findOption;
|
|
841
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
842
|
-
if (!metadata) {
|
|
843
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
844
|
-
}
|
|
845
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
846
|
-
const selectMap = [];
|
|
847
|
-
const whereMap = [];
|
|
848
|
-
const orderByMap = [];
|
|
849
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
850
|
-
const eagerRelations = manyToOneRelations.filter((rel) => {
|
|
851
|
-
const isEager = rel.option?.eager === true;
|
|
852
|
-
const isInRelations = findOption.relations?.includes(rel.columnName);
|
|
853
|
-
return isEager || isInRelations;
|
|
854
|
-
});
|
|
855
|
-
const oneToOneRelations = this.resolveOneToOneMetadata(entity);
|
|
856
|
-
const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
|
|
857
|
-
if (!rel.joinColumn)
|
|
858
|
-
return false;
|
|
859
|
-
const isEager = rel.option?.eager === true;
|
|
860
|
-
const isInRelations = findOption.relations?.includes(rel.propertyKey);
|
|
861
|
-
return isEager || isInRelations;
|
|
862
|
-
});
|
|
863
|
-
const hasEagerJoins = eagerRelations.length > 0 || eagerOneToOneRelations.length > 0;
|
|
864
|
-
const tableName = metadata.name;
|
|
865
|
-
if (select) {
|
|
866
|
-
const selectedColumns = this.resolveSelectColumns(select);
|
|
867
|
-
if (hasEagerJoins) {
|
|
868
|
-
selectMap.push(...selectedColumns.map((col) => `${this.wrap(tableName)}.${this.wrap(col)}`));
|
|
869
|
-
}
|
|
870
|
-
else {
|
|
871
|
-
selectMap.push(...selectedColumns.map((col) => this.wrap(col)));
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
if (hasEagerJoins) {
|
|
876
|
-
selectMap.push(...metadata.columns.map((column) => `${this.wrap(tableName)}.${this.wrap(column.name)}`));
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
selectMap.push(...metadata.columns.map((column) => this.wrap(column.name)));
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
for (const key in where) {
|
|
883
|
-
const value = where[key];
|
|
884
|
-
if (value !== undefined && value !== null) {
|
|
885
|
-
if (hasEagerJoins) {
|
|
886
|
-
whereMap.push(Conditions_1.Conditions.equals(`${this.wrap(tableName)}.${this.wrap(key)}`, value));
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
894
|
-
if (deletedAtColumn && !findOption.withDeleted) {
|
|
895
|
-
if (hasEagerJoins) {
|
|
896
|
-
whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
for (const key in orderBy) {
|
|
903
|
-
const value = orderBy[key];
|
|
904
|
-
if (value) {
|
|
905
|
-
orderByMap.push({ column: this.wrap(key), direction: value });
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
qb.select(selectMap).from(this.wrap(tableName));
|
|
909
|
-
qb.where(whereMap).orderBy(orderByMap);
|
|
910
|
-
if (Array.isArray(limit)) {
|
|
911
|
-
let [offset, count] = limit;
|
|
912
|
-
if (offset < 0)
|
|
913
|
-
offset = 0;
|
|
914
|
-
if (count < 0)
|
|
915
|
-
count = 0;
|
|
916
|
-
if (count === 0)
|
|
917
|
-
count = 1;
|
|
918
|
-
if (take && take > 0)
|
|
919
|
-
count = take;
|
|
920
|
-
if (this.isMySqlFamily())
|
|
921
|
-
qb.setDatabaseType("mysql");
|
|
922
|
-
qb.limit([offset, count]);
|
|
923
|
-
}
|
|
924
|
-
else if (limit) {
|
|
925
|
-
qb.limit(limit);
|
|
926
|
-
}
|
|
927
|
-
const selectQuery = qb.build();
|
|
928
|
-
const explainPrefix = this.driver.buildExplainSql("");
|
|
929
|
-
const explainQuery = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(explainPrefix)}${selectQuery}`;
|
|
930
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
931
|
-
try {
|
|
932
|
-
const readNode = this.getReadNode(findOption.useMaster);
|
|
933
|
-
if (readNode) {
|
|
934
|
-
await transactionHolder.connectToNode(readNode);
|
|
935
|
-
}
|
|
936
|
-
else {
|
|
937
|
-
await transactionHolder.connect();
|
|
938
|
-
}
|
|
939
|
-
await transactionHolder.startTransaction();
|
|
940
|
-
if (this.isMySqlFamily()) {
|
|
941
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
942
|
-
}
|
|
943
|
-
const result = await transactionHolder.query(explainQuery);
|
|
944
|
-
await transactionHolder.commit();
|
|
945
|
-
const rawRows = result?.results ?? [];
|
|
946
|
-
return this.parseExplainResult(rawRows);
|
|
947
|
-
}
|
|
948
|
-
catch (e) {
|
|
949
|
-
try {
|
|
950
|
-
await transactionHolder.rollback();
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
}
|
|
954
|
-
throw e;
|
|
955
|
-
}
|
|
956
|
-
finally {
|
|
957
|
-
try {
|
|
958
|
-
await transactionHolder.close();
|
|
959
|
-
}
|
|
960
|
-
catch {
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
parseExplainResult(rawRows) {
|
|
965
|
-
if (!rawRows || rawRows.length === 0) {
|
|
966
|
-
return {
|
|
967
|
-
raw: [],
|
|
968
|
-
rows: null,
|
|
969
|
-
type: null,
|
|
970
|
-
possibleKeys: null,
|
|
971
|
-
key: null,
|
|
972
|
-
cost: null,
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
const firstRow = rawRows[0];
|
|
976
|
-
if (firstRow && "QUERY PLAN" in firstRow) {
|
|
977
|
-
return this.parsePostgresExplain(firstRow["QUERY PLAN"]);
|
|
978
|
-
}
|
|
979
|
-
if ("type" in firstRow || "select_type" in firstRow) {
|
|
980
|
-
return this.parseMysqlExplain(rawRows);
|
|
981
|
-
}
|
|
982
|
-
if ("detail" in firstRow || "notused" in firstRow) {
|
|
983
|
-
return this.parseSqliteExplain(rawRows);
|
|
984
|
-
}
|
|
985
|
-
return {
|
|
986
|
-
raw: rawRows,
|
|
987
|
-
rows: null,
|
|
988
|
-
type: null,
|
|
989
|
-
possibleKeys: null,
|
|
990
|
-
key: null,
|
|
991
|
-
cost: null,
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
parseMysqlExplain(rawRows) {
|
|
995
|
-
const first = rawRows[0];
|
|
996
|
-
const rows = first.rows != null ? Number(first.rows) : null;
|
|
997
|
-
const type = first.type != null ? String(first.type) : null;
|
|
998
|
-
const possibleKeysRaw = first.possible_keys;
|
|
999
|
-
const possibleKeys = possibleKeysRaw != null
|
|
1000
|
-
? String(possibleKeysRaw)
|
|
1001
|
-
.split(",")
|
|
1002
|
-
.map((k) => k.trim())
|
|
1003
|
-
: null;
|
|
1004
|
-
const key = first.key != null ? String(first.key) : null;
|
|
1005
|
-
const cost = first.filtered != null ? Number(first.filtered) : null;
|
|
1006
|
-
return { raw: rawRows, rows, type, possibleKeys, key, cost };
|
|
1007
|
-
}
|
|
1008
|
-
parsePostgresExplain(queryPlan) {
|
|
1009
|
-
const rawArray = Array.isArray(queryPlan) ? queryPlan : [queryPlan];
|
|
1010
|
-
const plan = rawArray[0]?.Plan ?? rawArray[0]?.["Plan"] ?? null;
|
|
1011
|
-
if (!plan) {
|
|
1012
|
-
return {
|
|
1013
|
-
raw: rawArray,
|
|
1014
|
-
rows: null,
|
|
1015
|
-
type: null,
|
|
1016
|
-
possibleKeys: null,
|
|
1017
|
-
key: null,
|
|
1018
|
-
cost: null,
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
const rows = plan["Plan Rows"] != null ? Number(plan["Plan Rows"]) : null;
|
|
1022
|
-
const type = plan["Node Type"] != null ? String(plan["Node Type"]) : null;
|
|
1023
|
-
const key = plan["Index Name"] != null ? String(plan["Index Name"]) : null;
|
|
1024
|
-
const cost = plan["Total Cost"] != null ? Number(plan["Total Cost"]) : null;
|
|
1025
|
-
return { raw: rawArray, rows, type, possibleKeys: null, key, cost };
|
|
1026
|
-
}
|
|
1027
|
-
parseSqliteExplain(rawRows) {
|
|
1028
|
-
const details = rawRows.map((r) => String(r.detail ?? ""));
|
|
1029
|
-
const firstDetail = details[0] ?? "";
|
|
1030
|
-
let type = null;
|
|
1031
|
-
let key = null;
|
|
1032
|
-
if (firstDetail.startsWith("SCAN"))
|
|
1033
|
-
type = "SCAN";
|
|
1034
|
-
else if (firstDetail.startsWith("SEARCH"))
|
|
1035
|
-
type = "SEARCH";
|
|
1036
|
-
const indexMatch = firstDetail.match(/USING (?:COVERING )?INDEX (\S+)/);
|
|
1037
|
-
if (indexMatch)
|
|
1038
|
-
key = indexMatch[1];
|
|
1039
|
-
return {
|
|
1040
|
-
raw: rawRows,
|
|
1041
|
-
rows: null,
|
|
1042
|
-
type,
|
|
1043
|
-
possibleKeys: null,
|
|
1044
|
-
key,
|
|
1045
|
-
cost: null,
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
async findWithCursor(entity, option = {}) {
|
|
1049
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1050
|
-
if (!metadata) {
|
|
1051
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1052
|
-
}
|
|
1053
|
-
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
1054
|
-
const orderByColumn = option.orderBy ?? pk?.name;
|
|
1055
|
-
if (!orderByColumn) {
|
|
1056
|
-
throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
|
|
1057
|
-
}
|
|
1058
|
-
const direction = option.direction ?? "ASC";
|
|
1059
|
-
const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
|
|
1060
|
-
let cursorValue = null;
|
|
1061
|
-
if (option.cursor) {
|
|
1062
|
-
cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
|
|
1063
|
-
if (cursorValue === null) {
|
|
1064
|
-
throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
const where = { ...(option.where ?? {}) };
|
|
1068
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1069
|
-
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
1070
|
-
try {
|
|
1071
|
-
const readNode = this.getReadNode(option.useMaster);
|
|
1072
|
-
if (readNode) {
|
|
1073
|
-
await transactionHolder.connectToNode(readNode);
|
|
1074
|
-
}
|
|
1075
|
-
else {
|
|
1076
|
-
await transactionHolder.connect();
|
|
1077
|
-
}
|
|
1078
|
-
await transactionHolder.startTransaction();
|
|
1079
|
-
if (this.isMySqlFamily()) {
|
|
1080
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
1081
|
-
}
|
|
1082
|
-
const tableName = metadata.name;
|
|
1083
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
1084
|
-
const selectMap = metadata.columns.map((column) => this.wrap(column.name));
|
|
1085
|
-
const whereMap = [];
|
|
1086
|
-
for (const key in where) {
|
|
1087
|
-
const value = where[key];
|
|
1088
|
-
if (value !== undefined && value !== null) {
|
|
1089
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1093
|
-
if (deletedAtColumn) {
|
|
1094
|
-
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
1095
|
-
}
|
|
1096
|
-
if (cursorValue !== null) {
|
|
1097
|
-
if (direction === "ASC") {
|
|
1098
|
-
whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
|
|
1099
|
-
}
|
|
1100
|
-
else {
|
|
1101
|
-
whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
qb.select(selectMap)
|
|
1105
|
-
.from(this.wrap(tableName))
|
|
1106
|
-
.where(whereMap)
|
|
1107
|
-
.orderBy([{ column: this.wrap(orderByColumn), direction }]);
|
|
1108
|
-
qb.limit(pageSize + 1);
|
|
1109
|
-
const resultQuery = qb.build();
|
|
1110
|
-
const queryResult = (await transactionHolder.query(resultQuery));
|
|
1111
|
-
await transactionHolder.commit();
|
|
1112
|
-
const { results } = queryResult;
|
|
1113
|
-
if (!results || results.length === 0) {
|
|
1114
|
-
return {
|
|
1115
|
-
data: [],
|
|
1116
|
-
hasNextPage: false,
|
|
1117
|
-
nextCursor: null,
|
|
1118
|
-
count: 0,
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
const hasNextPage = results.length > pageSize;
|
|
1122
|
-
const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
|
|
1123
|
-
const entities = resultTransformer.toEntities(entity, {
|
|
1124
|
-
results: pageResults,
|
|
1125
|
-
fields: queryResult.fields,
|
|
1126
|
-
});
|
|
1127
|
-
let nextCursor = null;
|
|
1128
|
-
if (hasNextPage && pageResults.length > 0) {
|
|
1129
|
-
const lastItem = pageResults[pageResults.length - 1];
|
|
1130
|
-
const lastValue = lastItem[orderByColumn];
|
|
1131
|
-
nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
|
|
1132
|
-
}
|
|
1133
|
-
return {
|
|
1134
|
-
data: entities,
|
|
1135
|
-
hasNextPage,
|
|
1136
|
-
nextCursor,
|
|
1137
|
-
count: entities.length,
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
catch (e) {
|
|
1141
|
-
try {
|
|
1142
|
-
await transactionHolder.rollback();
|
|
1143
|
-
}
|
|
1144
|
-
catch (rollbackError) {
|
|
1145
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1146
|
-
}
|
|
1147
|
-
throw e;
|
|
1148
|
-
}
|
|
1149
|
-
finally {
|
|
1150
|
-
try {
|
|
1151
|
-
await transactionHolder.close();
|
|
1152
|
-
}
|
|
1153
|
-
catch (closeError) {
|
|
1154
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
283
|
async find(entity, findOption = {}) {
|
|
284
|
+
return this.findInternal(entity, findOption);
|
|
285
|
+
}
|
|
286
|
+
async findInternal(entity, findOption = {}, existingSession) {
|
|
1159
287
|
const { select, orderBy, where, take, groupBy, having } = findOption;
|
|
1160
288
|
const { limit } = findOption;
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const
|
|
1165
|
-
if (readNode) {
|
|
1166
|
-
await transactionHolder.connectToNode(readNode);
|
|
1167
|
-
}
|
|
1168
|
-
else {
|
|
1169
|
-
await transactionHolder.connect();
|
|
1170
|
-
}
|
|
1171
|
-
await transactionHolder.startTransaction();
|
|
1172
|
-
if (this.isMySqlFamily()) {
|
|
1173
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
1174
|
-
}
|
|
1175
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
289
|
+
const readNode = this.getReadNode(findOption.useMaster);
|
|
290
|
+
return this.executeInTransaction(async (session) => {
|
|
291
|
+
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
292
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1176
293
|
if (!metadata) {
|
|
1177
294
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1178
295
|
}
|
|
@@ -1180,13 +297,13 @@ class EntityManager {
|
|
|
1180
297
|
const selectMap = [];
|
|
1181
298
|
const whereMap = [];
|
|
1182
299
|
const orderByMap = [];
|
|
1183
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
300
|
+
const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1184
301
|
const eagerRelations = manyToOneRelations.filter((rel) => {
|
|
1185
302
|
const isEager = rel.option?.eager === true;
|
|
1186
303
|
const isInRelations = findOption.relations?.includes(rel.columnName);
|
|
1187
304
|
return isEager || isInRelations;
|
|
1188
305
|
});
|
|
1189
|
-
const oneToOneRelations = this.resolveOneToOneMetadata(entity);
|
|
306
|
+
const oneToOneRelations = this.resolver.resolveOneToOneMetadata(entity);
|
|
1190
307
|
const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
|
|
1191
308
|
if (!rel.joinColumn)
|
|
1192
309
|
return false;
|
|
@@ -1215,7 +332,7 @@ class EntityManager {
|
|
|
1215
332
|
}
|
|
1216
333
|
for (const rel of eagerRelations) {
|
|
1217
334
|
const RelatedEntity = rel.getMappingEntity();
|
|
1218
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
335
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1219
336
|
if (!relatedMetadata)
|
|
1220
337
|
continue;
|
|
1221
338
|
const relatedName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1226,7 +343,7 @@ class EntityManager {
|
|
|
1226
343
|
}
|
|
1227
344
|
for (const rel of eagerOneToOneRelations) {
|
|
1228
345
|
const RelatedEntity = rel.getRelatedEntity();
|
|
1229
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
346
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1230
347
|
if (!relatedMetadata)
|
|
1231
348
|
continue;
|
|
1232
349
|
const relatedName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1246,7 +363,7 @@ class EntityManager {
|
|
|
1246
363
|
}
|
|
1247
364
|
}
|
|
1248
365
|
}
|
|
1249
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
366
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
1250
367
|
if (deletedAtColumn && !findOption.withDeleted) {
|
|
1251
368
|
if (hasEagerJoins) {
|
|
1252
369
|
whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
|
|
@@ -1264,7 +381,7 @@ class EntityManager {
|
|
|
1264
381
|
qb.select(selectMap).from(this.wrap(tableName));
|
|
1265
382
|
for (const rel of eagerRelations) {
|
|
1266
383
|
const RelatedEntity = rel.getMappingEntity();
|
|
1267
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
384
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1268
385
|
if (!relatedMetadata)
|
|
1269
386
|
continue;
|
|
1270
387
|
const relatedTableName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1277,7 +394,7 @@ class EntityManager {
|
|
|
1277
394
|
}
|
|
1278
395
|
for (const rel of eagerOneToOneRelations) {
|
|
1279
396
|
const RelatedEntity = rel.getRelatedEntity();
|
|
1280
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
397
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1281
398
|
if (!relatedMetadata)
|
|
1282
399
|
continue;
|
|
1283
400
|
const relatedTableName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1301,21 +418,16 @@ class EntityManager {
|
|
|
1301
418
|
qb.orderBy(orderByMap);
|
|
1302
419
|
if (Array.isArray(limit)) {
|
|
1303
420
|
let [offset, count] = limit;
|
|
1304
|
-
if (offset < 0)
|
|
421
|
+
if (offset < 0)
|
|
1305
422
|
offset = 0;
|
|
1306
|
-
|
|
1307
|
-
if (count < 0) {
|
|
423
|
+
if (count < 0)
|
|
1308
424
|
count = 0;
|
|
1309
|
-
|
|
1310
|
-
if (count === 0) {
|
|
425
|
+
if (count === 0)
|
|
1311
426
|
count = 1;
|
|
1312
|
-
|
|
1313
|
-
if (take && take > 0) {
|
|
427
|
+
if (take && take > 0)
|
|
1314
428
|
count = take;
|
|
1315
|
-
|
|
1316
|
-
if (this.isMySqlFamily()) {
|
|
429
|
+
if (this.isMySqlFamily())
|
|
1317
430
|
qb.setDatabaseType("mysql");
|
|
1318
|
-
}
|
|
1319
431
|
qb.limit([offset, count]);
|
|
1320
432
|
}
|
|
1321
433
|
else {
|
|
@@ -1327,13 +439,12 @@ class EntityManager {
|
|
|
1327
439
|
const effectiveTimeout = findOption.timeout ?? this.defaultQueryTimeout;
|
|
1328
440
|
if (effectiveTimeout && effectiveTimeout > 0 && this.driver) {
|
|
1329
441
|
const timeoutSql = this.driver.setQueryTimeout(effectiveTimeout);
|
|
1330
|
-
await
|
|
442
|
+
await session.query(timeoutSql);
|
|
1331
443
|
}
|
|
1332
444
|
const queryStartTime = Date.now();
|
|
1333
445
|
this.beginTrackQuery();
|
|
1334
|
-
const queryResult = (await
|
|
446
|
+
const queryResult = (await session.query(resultQuery));
|
|
1335
447
|
this.trackQuery(entity.name, resultQuery.text ?? String(resultQuery), Date.now() - queryStartTime);
|
|
1336
|
-
await transactionHolder.commit();
|
|
1337
448
|
const { results } = queryResult;
|
|
1338
449
|
if (!results || results.length === 0) {
|
|
1339
450
|
return undefined;
|
|
@@ -1352,9 +463,9 @@ class EntityManager {
|
|
|
1352
463
|
if (findOption.relations &&
|
|
1353
464
|
findOption.relations.length > 0 &&
|
|
1354
465
|
entityResult) {
|
|
1355
|
-
await this.loadOneToManyRelations(entity, entityResult, findOption.relations);
|
|
1356
|
-
await this.loadManyToManyRelations(entity, entityResult, findOption.relations);
|
|
1357
|
-
await this.loadOneToOneRelations(entity, entityResult, findOption.relations);
|
|
466
|
+
await this.relationLoader.loadOneToManyRelations(entity, entityResult, findOption.relations, session);
|
|
467
|
+
await this.relationLoader.loadManyToManyRelations(entity, entityResult, findOption.relations, session);
|
|
468
|
+
await this.relationLoader.loadOneToOneRelations(entity, entityResult, findOption.relations, session);
|
|
1358
469
|
}
|
|
1359
470
|
const lazyRelations = manyToOneRelations.filter((rel) => {
|
|
1360
471
|
return rel.option?.lazy === true && rel.option?.eager !== true;
|
|
@@ -1370,7 +481,7 @@ class EntityManager {
|
|
|
1370
481
|
const fkValue = item[joinColumn];
|
|
1371
482
|
if (fkValue === undefined || fkValue === null)
|
|
1372
483
|
continue;
|
|
1373
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
484
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1374
485
|
if (!relatedMetadata)
|
|
1375
486
|
continue;
|
|
1376
487
|
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
@@ -1386,69 +497,111 @@ class EntityManager {
|
|
|
1386
497
|
}
|
|
1387
498
|
}
|
|
1388
499
|
}
|
|
1389
|
-
return entityResult;
|
|
500
|
+
return entityResult;
|
|
501
|
+
}, existingSession, readNode);
|
|
502
|
+
}
|
|
503
|
+
async findWithCursor(entity, option = {}) {
|
|
504
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
505
|
+
if (!metadata) {
|
|
506
|
+
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
507
|
+
}
|
|
508
|
+
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
509
|
+
const orderByColumn = option.orderBy ?? pk?.name;
|
|
510
|
+
if (!orderByColumn) {
|
|
511
|
+
throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
|
|
512
|
+
}
|
|
513
|
+
if (!option.orderBy && pk) {
|
|
514
|
+
this.warnIfNonSortablePk(entity.name, pk);
|
|
515
|
+
}
|
|
516
|
+
const direction = option.direction ?? "ASC";
|
|
517
|
+
const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
|
|
518
|
+
let cursorValue = null;
|
|
519
|
+
if (option.cursor) {
|
|
520
|
+
cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
|
|
521
|
+
if (cursorValue === null) {
|
|
522
|
+
throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
|
|
523
|
+
}
|
|
1390
524
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
525
|
+
const where = { ...(option.where ?? {}) };
|
|
526
|
+
const readNode = this.getReadNode(option.useMaster);
|
|
527
|
+
return this.executeInTransaction(async (session) => {
|
|
528
|
+
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
529
|
+
const tableName = metadata.name;
|
|
530
|
+
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
531
|
+
const selectMap = metadata.columns.map((column) => this.wrap(column.name));
|
|
532
|
+
const whereMap = [];
|
|
533
|
+
for (const key in where) {
|
|
534
|
+
const value = where[key];
|
|
535
|
+
if (value !== undefined && value !== null) {
|
|
536
|
+
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
537
|
+
}
|
|
1394
538
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
539
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
540
|
+
if (deletedAtColumn) {
|
|
541
|
+
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
1397
542
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
543
|
+
if (cursorValue !== null) {
|
|
544
|
+
if (direction === "ASC") {
|
|
545
|
+
whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
|
|
549
|
+
}
|
|
1403
550
|
}
|
|
1404
|
-
|
|
1405
|
-
this.
|
|
551
|
+
qb.select(selectMap)
|
|
552
|
+
.from(this.wrap(tableName))
|
|
553
|
+
.where(whereMap)
|
|
554
|
+
.orderBy([{ column: this.wrap(orderByColumn), direction }]);
|
|
555
|
+
qb.limit(pageSize + 1);
|
|
556
|
+
const resultQuery = qb.build();
|
|
557
|
+
const queryResult = (await session.query(resultQuery));
|
|
558
|
+
const { results } = queryResult;
|
|
559
|
+
if (!results || results.length === 0) {
|
|
560
|
+
return {
|
|
561
|
+
data: [],
|
|
562
|
+
hasNextPage: false,
|
|
563
|
+
nextCursor: null,
|
|
564
|
+
count: 0,
|
|
565
|
+
};
|
|
1406
566
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
567
|
+
const hasNextPage = results.length > pageSize;
|
|
568
|
+
const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
|
|
569
|
+
const entities = resultTransformer.toEntities(entity, {
|
|
570
|
+
results: pageResults,
|
|
571
|
+
fields: queryResult.fields,
|
|
572
|
+
});
|
|
573
|
+
let nextCursor = null;
|
|
574
|
+
if (hasNextPage && pageResults.length > 0) {
|
|
575
|
+
const lastItem = pageResults[pageResults.length - 1];
|
|
576
|
+
const lastValue = lastItem[orderByColumn];
|
|
577
|
+
nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
|
|
1417
578
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
if (this.isPostgres()) {
|
|
1426
|
-
return `"${columnName.replace(/"/g, '""')}"`;
|
|
1427
|
-
}
|
|
1428
|
-
return `\`${columnName.replace(/`/g, "``")}\``;
|
|
1429
|
-
}
|
|
1430
|
-
isMySqlFamily() {
|
|
1431
|
-
const t = this.dbType ?? this.client.type;
|
|
1432
|
-
return ["mysql", "mariadb"].includes(t);
|
|
579
|
+
return {
|
|
580
|
+
data: entities,
|
|
581
|
+
hasNextPage,
|
|
582
|
+
nextCursor,
|
|
583
|
+
count: entities.length,
|
|
584
|
+
};
|
|
585
|
+
}, undefined, readNode);
|
|
1433
586
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
587
|
+
async findAndCount(entity, findOption = {}) {
|
|
588
|
+
return this.executeInTransaction(async (session) => {
|
|
589
|
+
const entities = await this.findInternal(entity, findOption, session);
|
|
590
|
+
const totalCount = await this.aggregateHandler.aggregate(entity, "COUNT", "*", findOption.where, session);
|
|
591
|
+
return [entities, totalCount];
|
|
592
|
+
});
|
|
1437
593
|
}
|
|
1438
594
|
async save(entity, item) {
|
|
1439
|
-
|
|
595
|
+
return this.saveInternal(entity, item);
|
|
596
|
+
}
|
|
597
|
+
async saveInternal(entity, item, existingSession) {
|
|
598
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1440
599
|
if (!metadata) {
|
|
1441
600
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1442
601
|
}
|
|
1443
602
|
EntityValidator_1.EntityValidator.validate(entity, item);
|
|
1444
|
-
await this.cascadeSaveManyToOne(entity, item);
|
|
1445
|
-
|
|
1446
|
-
try {
|
|
1447
|
-
await transactionManager.connect();
|
|
1448
|
-
await transactionManager.startTransaction();
|
|
1449
|
-
if (this.isMySqlFamily()) {
|
|
1450
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1451
|
-
}
|
|
603
|
+
await this.cascadeHandler.cascadeSaveManyToOne(entity, item);
|
|
604
|
+
return this.executeInTransaction(async (session) => {
|
|
1452
605
|
const pkColumns = metadata.columns.filter((column) => column.options?.primary);
|
|
1453
606
|
const pk = pkColumns[0];
|
|
1454
607
|
const hasAutoIncrementPk = pkColumns.some((col) => col.options?.autoIncrement);
|
|
@@ -1474,7 +627,7 @@ class EntityManager {
|
|
|
1474
627
|
return where;
|
|
1475
628
|
};
|
|
1476
629
|
if (isInsert) {
|
|
1477
|
-
await this.runHooks(entity, item, "beforeInsert");
|
|
630
|
+
await this.cascadeHandler.runHooks(entity, item, "beforeInsert");
|
|
1478
631
|
await this.eventEmitter.emit("beforeInsert", { entity, data: item });
|
|
1479
632
|
await this.notifySubscribers(entity, "beforeInsert", {
|
|
1480
633
|
entity: item,
|
|
@@ -1495,21 +648,28 @@ class EntityManager {
|
|
|
1495
648
|
return item[column.name];
|
|
1496
649
|
});
|
|
1497
650
|
const now = new Date().toISOString();
|
|
1498
|
-
const createTsCol = this.getCreateTimestampColumn(entity);
|
|
651
|
+
const createTsCol = this.resolver.getCreateTimestampColumn(entity);
|
|
1499
652
|
if (createTsCol) {
|
|
1500
653
|
const idx = insertableColumns.findIndex((col) => col.name === createTsCol);
|
|
1501
654
|
if (idx >= 0) {
|
|
1502
655
|
values[idx] = item[createTsCol]?.toISOString?.() ?? now;
|
|
1503
656
|
}
|
|
1504
657
|
}
|
|
1505
|
-
const updateTsCol = this.getUpdateTimestampColumn(entity);
|
|
658
|
+
const updateTsCol = this.resolver.getUpdateTimestampColumn(entity);
|
|
1506
659
|
if (updateTsCol) {
|
|
1507
660
|
const idx = insertableColumns.findIndex((col) => col.name === updateTsCol);
|
|
1508
661
|
if (idx >= 0) {
|
|
1509
662
|
values[idx] = item[updateTsCol]?.toISOString?.() ?? now;
|
|
1510
663
|
}
|
|
1511
664
|
}
|
|
1512
|
-
const
|
|
665
|
+
const versionCol = this.resolver.getVersionColumn(entity);
|
|
666
|
+
if (versionCol) {
|
|
667
|
+
const versionIdx = insertableColumns.findIndex((col) => col.name === versionCol);
|
|
668
|
+
if (versionIdx >= 0) {
|
|
669
|
+
values[versionIdx] = 1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1513
673
|
for (const rel of manyToOneRelations) {
|
|
1514
674
|
if (!rel.joinColumn)
|
|
1515
675
|
continue;
|
|
@@ -1522,7 +682,7 @@ class EntityManager {
|
|
|
1522
682
|
}
|
|
1523
683
|
else if (relatedValue && typeof relatedValue === "object") {
|
|
1524
684
|
const RelatedEntity = rel.getMappingEntity();
|
|
1525
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
685
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1526
686
|
if (relatedMeta) {
|
|
1527
687
|
const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
|
|
1528
688
|
if (relatedPk) {
|
|
@@ -1557,21 +717,20 @@ class EntityManager {
|
|
|
1557
717
|
`;
|
|
1558
718
|
const saveQueryStart = Date.now();
|
|
1559
719
|
this.beginTrackQuery();
|
|
1560
|
-
const queryResult = (await
|
|
720
|
+
const queryResult = (await session.query(insertSql));
|
|
1561
721
|
this.trackQuery(entity.name, insertSql.text ?? String(insertSql), Date.now() - saveQueryStart);
|
|
1562
|
-
await transactionManager.commit();
|
|
1563
722
|
if (this.isMySqlFamily()) {
|
|
1564
723
|
const findWhere = hasAutoIncrementPk
|
|
1565
724
|
? { [pk.name]: queryResult?.results?.insertId }
|
|
1566
725
|
: buildPkFindWhere();
|
|
1567
|
-
const result = await this.
|
|
726
|
+
const result = await this.findOneInternal(entity, {
|
|
1568
727
|
where: findWhere,
|
|
1569
|
-
});
|
|
728
|
+
}, session);
|
|
1570
729
|
const cascadeId = hasAutoIncrementPk
|
|
1571
730
|
? queryResult?.results?.insertId
|
|
1572
731
|
: primaryKeyValue;
|
|
1573
|
-
await this.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
1574
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
732
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
733
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1575
734
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1576
735
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1577
736
|
entity: item,
|
|
@@ -1582,12 +741,12 @@ class EntityManager {
|
|
|
1582
741
|
if (isPostgres && queryResult?.results?.length > 0) {
|
|
1583
742
|
const returnedRow = queryResult.results[0];
|
|
1584
743
|
const findWhere = buildPkFindWhere(returnedRow);
|
|
1585
|
-
const result = await this.
|
|
744
|
+
const result = await this.findOneInternal(entity, {
|
|
1586
745
|
where: findWhere,
|
|
1587
|
-
});
|
|
746
|
+
}, session);
|
|
1588
747
|
const cascadeId = returnedRow[pk.name];
|
|
1589
|
-
await this.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
1590
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
748
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
749
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1591
750
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1592
751
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1593
752
|
entity: item,
|
|
@@ -1595,7 +754,7 @@ class EntityManager {
|
|
|
1595
754
|
});
|
|
1596
755
|
return result;
|
|
1597
756
|
}
|
|
1598
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
757
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1599
758
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1600
759
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1601
760
|
entity: item,
|
|
@@ -1603,22 +762,25 @@ class EntityManager {
|
|
|
1603
762
|
});
|
|
1604
763
|
return queryResult;
|
|
1605
764
|
}
|
|
1606
|
-
await this.runHooks(entity, item, "beforeUpdate");
|
|
765
|
+
await this.cascadeHandler.runHooks(entity, item, "beforeUpdate");
|
|
1607
766
|
await this.eventEmitter.emit("beforeUpdate", { entity, data: item });
|
|
1608
767
|
await this.notifySubscribers(entity, "beforeUpdate", {
|
|
1609
768
|
entity: item,
|
|
1610
769
|
manager: this,
|
|
1611
770
|
});
|
|
771
|
+
const versionColName = this.resolver.getVersionColumn(entity);
|
|
1612
772
|
const pkColumnNames = new Set(pkColumns.map((col) => col.name));
|
|
1613
773
|
const updatableColumns = metadata.columns.filter((column) => {
|
|
1614
774
|
if (pkColumnNames.has(column.name))
|
|
1615
775
|
return false;
|
|
776
|
+
if (versionColName && column.name === versionColName)
|
|
777
|
+
return false;
|
|
1616
778
|
return item[column.name] !== undefined;
|
|
1617
779
|
});
|
|
1618
780
|
const updateMap = updatableColumns.map((column) => {
|
|
1619
781
|
return (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(column.name))} = ${item[column.name]}`;
|
|
1620
782
|
});
|
|
1621
|
-
const updateTsColName = this.getUpdateTimestampColumn(entity);
|
|
783
|
+
const updateTsColName = this.resolver.getUpdateTimestampColumn(entity);
|
|
1622
784
|
if (updateTsColName) {
|
|
1623
785
|
const existingIdx = updatableColumns.findIndex((col) => col.name === updateTsColName);
|
|
1624
786
|
const updateNow = new Date().toISOString();
|
|
@@ -1631,7 +793,7 @@ class EntityManager {
|
|
|
1631
793
|
}
|
|
1632
794
|
}
|
|
1633
795
|
const updatedColumnNames = new Set(updatableColumns.map((col) => col.name));
|
|
1634
|
-
const updateManyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
796
|
+
const updateManyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1635
797
|
for (const rel of updateManyToOneRelations) {
|
|
1636
798
|
if (!rel.joinColumn)
|
|
1637
799
|
continue;
|
|
@@ -1652,7 +814,7 @@ class EntityManager {
|
|
|
1652
814
|
}
|
|
1653
815
|
else if (typeof relatedValue === "object") {
|
|
1654
816
|
const RelatedEntity = rel.getMappingEntity();
|
|
1655
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
817
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1656
818
|
if (relatedMeta) {
|
|
1657
819
|
const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
|
|
1658
820
|
if (relatedPk) {
|
|
@@ -1673,6 +835,15 @@ class EntityManager {
|
|
|
1673
835
|
}
|
|
1674
836
|
}
|
|
1675
837
|
const pkWhereClauses = buildPkWhere();
|
|
838
|
+
const currentVersion = versionColName
|
|
839
|
+
? item[versionColName]
|
|
840
|
+
: undefined;
|
|
841
|
+
if (versionColName) {
|
|
842
|
+
updateMap.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} = ${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} + 1`);
|
|
843
|
+
if (currentVersion !== undefined && currentVersion !== null) {
|
|
844
|
+
pkWhereClauses.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(versionColName))} = ${currentVersion}`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
1676
847
|
if (updateMap.length > 0) {
|
|
1677
848
|
const updateSql = (0, sql_template_tag_1.default) `
|
|
1678
849
|
UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))}
|
|
@@ -1681,117 +852,57 @@ class EntityManager {
|
|
|
1681
852
|
`;
|
|
1682
853
|
const updateStart = Date.now();
|
|
1683
854
|
this.beginTrackQuery();
|
|
1684
|
-
await
|
|
855
|
+
const updateResult = (await session.query(updateSql));
|
|
1685
856
|
this.trackQuery(entity.name, updateSql.text ?? String(updateSql), Date.now() - updateStart);
|
|
857
|
+
if (versionColName && currentVersion !== undefined && currentVersion !== null) {
|
|
858
|
+
let affected = 0;
|
|
859
|
+
if (this.isMySqlFamily()) {
|
|
860
|
+
affected = updateResult?.results?.affectedRows ?? 0;
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
affected = updateResult?.rowCount ?? 0;
|
|
864
|
+
}
|
|
865
|
+
if (affected === 0) {
|
|
866
|
+
throw new OptimisticLockError_1.OptimisticLockError(entity.name, currentVersion);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
1686
869
|
}
|
|
1687
|
-
await
|
|
1688
|
-
await this.
|
|
1689
|
-
await this.runHooks(entity, item, "afterUpdate");
|
|
870
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, primaryKeyValue);
|
|
871
|
+
await this.cascadeHandler.runHooks(entity, item, "afterUpdate");
|
|
1690
872
|
await this.eventEmitter.emit("afterUpdate", { entity, data: item });
|
|
1691
873
|
await this.notifySubscribers(entity, "afterUpdate", {
|
|
1692
874
|
entity: item,
|
|
1693
875
|
manager: this,
|
|
1694
876
|
});
|
|
1695
|
-
const result = await this.
|
|
877
|
+
const result = await this.findOneInternal(entity, {
|
|
1696
878
|
where: buildPkFindWhere(),
|
|
1697
|
-
});
|
|
879
|
+
}, session);
|
|
1698
880
|
return result;
|
|
1699
|
-
}
|
|
1700
|
-
catch (e) {
|
|
1701
|
-
try {
|
|
1702
|
-
await transactionManager.rollback();
|
|
1703
|
-
}
|
|
1704
|
-
catch (rollbackError) {
|
|
1705
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1706
|
-
}
|
|
1707
|
-
throw e;
|
|
1708
|
-
}
|
|
1709
|
-
finally {
|
|
1710
|
-
try {
|
|
1711
|
-
await transactionManager.close();
|
|
1712
|
-
}
|
|
1713
|
-
catch (closeError) {
|
|
1714
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
async deleteMany(entity, ids) {
|
|
1719
|
-
if (ids.length === 0) {
|
|
1720
|
-
return { affected: 0 };
|
|
1721
|
-
}
|
|
1722
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1723
|
-
if (!metadata) {
|
|
1724
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1725
|
-
}
|
|
1726
|
-
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
1727
|
-
if (!pk) {
|
|
1728
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1729
|
-
}
|
|
1730
|
-
const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1731
|
-
try {
|
|
1732
|
-
await transactionManager.connect();
|
|
1733
|
-
await transactionManager.startTransaction();
|
|
1734
|
-
if (this.isMySqlFamily()) {
|
|
1735
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1736
|
-
}
|
|
1737
|
-
const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
|
|
1738
|
-
const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${(0, sql_template_tag_1.raw)(this.wrap(pk.name))} IN (${placeholders})`;
|
|
1739
|
-
const queryResult = (await transactionManager.query(deleteQuery));
|
|
1740
|
-
await transactionManager.commit();
|
|
1741
|
-
let affected = 0;
|
|
1742
|
-
if (this.isMySqlFamily()) {
|
|
1743
|
-
affected = queryResult?.results?.affectedRows ?? 0;
|
|
1744
|
-
}
|
|
1745
|
-
else {
|
|
1746
|
-
affected = queryResult?.rowCount ?? 0;
|
|
1747
|
-
}
|
|
1748
|
-
return { affected };
|
|
1749
|
-
}
|
|
1750
|
-
catch (e) {
|
|
1751
|
-
try {
|
|
1752
|
-
await transactionManager.rollback();
|
|
1753
|
-
}
|
|
1754
|
-
catch (rollbackError) {
|
|
1755
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1756
|
-
}
|
|
1757
|
-
throw e;
|
|
1758
|
-
}
|
|
1759
|
-
finally {
|
|
1760
|
-
try {
|
|
1761
|
-
await transactionManager.close();
|
|
1762
|
-
}
|
|
1763
|
-
catch (closeError) {
|
|
1764
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
881
|
+
}, existingSession);
|
|
1767
882
|
}
|
|
1768
883
|
async saveMany(entity, items) {
|
|
1769
884
|
if (items.length === 0) {
|
|
1770
885
|
return [];
|
|
1771
886
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
887
|
+
return this.executeInTransaction(async (session) => {
|
|
888
|
+
const results = [];
|
|
889
|
+
for (const item of items) {
|
|
890
|
+
const saved = await this.saveInternal(entity, item, session);
|
|
891
|
+
results.push(saved);
|
|
892
|
+
}
|
|
893
|
+
return results;
|
|
894
|
+
});
|
|
1778
895
|
}
|
|
1779
896
|
async insertMany(entity, items) {
|
|
1780
897
|
if (items.length === 0) {
|
|
1781
898
|
return { affected: 0 };
|
|
1782
899
|
}
|
|
1783
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
900
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1784
901
|
if (!metadata) {
|
|
1785
902
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1786
903
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
await transactionManager.connect();
|
|
1790
|
-
await transactionManager.startTransaction();
|
|
1791
|
-
if (this.isMySqlFamily()) {
|
|
1792
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1793
|
-
}
|
|
1794
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
904
|
+
return this.executeInTransaction(async (session) => {
|
|
905
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
1795
906
|
const timestampTypes = new Set(["datetime", "timestamp", "date"]);
|
|
1796
907
|
const timestampColumns = metadata.columns.filter((col) => col.options?.type &&
|
|
1797
908
|
timestampTypes.has(col.options.type) &&
|
|
@@ -1806,6 +917,14 @@ class EntityManager {
|
|
|
1806
917
|
}
|
|
1807
918
|
}
|
|
1808
919
|
}
|
|
920
|
+
const versionCol = this.resolver.getVersionColumn(entity);
|
|
921
|
+
if (versionCol) {
|
|
922
|
+
for (const item of items) {
|
|
923
|
+
if (item[versionCol] == null) {
|
|
924
|
+
item[versionCol] = 1;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
1809
928
|
const insertableColumns = metadata.columns.filter((column) => {
|
|
1810
929
|
const isAutoIncrement = column.options?.autoIncrement;
|
|
1811
930
|
if (!isAutoIncrement)
|
|
@@ -1814,7 +933,7 @@ class EntityManager {
|
|
|
1814
933
|
item[column.name] !== undefined);
|
|
1815
934
|
});
|
|
1816
935
|
const columns = insertableColumns.map((column) => (0, sql_template_tag_1.raw)(this.wrap(column.name)));
|
|
1817
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
936
|
+
const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1818
937
|
const fkColumns = [];
|
|
1819
938
|
for (const rel of manyToOneRelations) {
|
|
1820
939
|
if (!rel.joinColumn)
|
|
@@ -1837,7 +956,7 @@ class EntityManager {
|
|
|
1837
956
|
if (relatedValue != null) {
|
|
1838
957
|
if (typeof relatedValue === "object") {
|
|
1839
958
|
const RelatedEntity = fk.relMeta.getMappingEntity();
|
|
1840
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
959
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1841
960
|
const relatedPk = relatedMeta?.columns.find((col) => col.options?.primary);
|
|
1842
961
|
rowValues.push(relatedPk ? relatedValue[relatedPk.name] ?? null : null);
|
|
1843
962
|
}
|
|
@@ -1852,143 +971,34 @@ class EntityManager {
|
|
|
1852
971
|
rowValues.push(null);
|
|
1853
972
|
}
|
|
1854
973
|
}
|
|
1855
|
-
return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
|
|
1856
|
-
});
|
|
1857
|
-
const queryStr = (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} (${(0, sql_template_tag_1.join)(columns, ", ")}) VALUES ${(0, sql_template_tag_1.join)(valueRows, ", ")}`;
|
|
1858
|
-
const queryResult = (await
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
affected = queryResult?.results?.affectedRows ?? items.length;
|
|
1863
|
-
}
|
|
1864
|
-
else if (queryResult?.rowCount !== undefined) {
|
|
1865
|
-
affected = queryResult.rowCount;
|
|
1866
|
-
}
|
|
1867
|
-
return { affected };
|
|
1868
|
-
}
|
|
1869
|
-
catch (e) {
|
|
1870
|
-
try {
|
|
1871
|
-
await transactionManager.rollback();
|
|
1872
|
-
}
|
|
1873
|
-
catch (rollbackError) {
|
|
1874
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1875
|
-
}
|
|
1876
|
-
throw e;
|
|
1877
|
-
}
|
|
1878
|
-
finally {
|
|
1879
|
-
try {
|
|
1880
|
-
await transactionManager.close();
|
|
1881
|
-
}
|
|
1882
|
-
catch (closeError) {
|
|
1883
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
async upsert(entity, data, conflictColumns) {
|
|
1888
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1889
|
-
if (!metadata) {
|
|
1890
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1891
|
-
}
|
|
1892
|
-
if (!this.driver) {
|
|
1893
|
-
throw new Error("Driver is not initialized.");
|
|
1894
|
-
}
|
|
1895
|
-
const pkColumns = metadata.columns
|
|
1896
|
-
.filter((col) => col.options?.primary)
|
|
1897
|
-
.map((col) => col.name);
|
|
1898
|
-
const resolvedConflictColumns = conflictColumns ?? pkColumns;
|
|
1899
|
-
if (resolvedConflictColumns.length === 0) {
|
|
1900
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1901
|
-
}
|
|
1902
|
-
const insertableColumns = metadata.columns.filter((col) => {
|
|
1903
|
-
const value = data[col.name];
|
|
1904
|
-
if (col.options?.autoIncrement &&
|
|
1905
|
-
(value === null || value === undefined)) {
|
|
1906
|
-
return false;
|
|
1907
|
-
}
|
|
1908
|
-
return value !== undefined;
|
|
1909
|
-
});
|
|
1910
|
-
if (insertableColumns.length === 0) {
|
|
1911
|
-
return;
|
|
1912
|
-
}
|
|
1913
|
-
const conflictSet = new Set(resolvedConflictColumns);
|
|
1914
|
-
const updateColumnNames = insertableColumns
|
|
1915
|
-
.map((col) => col.name)
|
|
1916
|
-
.filter((name) => !conflictSet.has(name));
|
|
1917
|
-
const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
|
|
1918
|
-
const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
|
|
1919
|
-
const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
|
|
1920
|
-
const tableName = this.wrap(metadata.name);
|
|
1921
|
-
if (wrappedUpdate.length === 0) {
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1925
|
-
try {
|
|
1926
|
-
await transactionManager.connect();
|
|
1927
|
-
await transactionManager.startTransaction();
|
|
1928
|
-
if (this.isMySqlFamily()) {
|
|
1929
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1930
|
-
}
|
|
1931
|
-
const columnValues = insertableColumns.map((col) => data[col.name]);
|
|
1932
|
-
const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
|
|
1933
|
-
await transactionManager.query(upsertSql);
|
|
1934
|
-
await transactionManager.commit();
|
|
1935
|
-
}
|
|
1936
|
-
catch (e) {
|
|
1937
|
-
try {
|
|
1938
|
-
await transactionManager.rollback();
|
|
1939
|
-
}
|
|
1940
|
-
catch (rollbackError) {
|
|
1941
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1942
|
-
}
|
|
1943
|
-
throw e;
|
|
1944
|
-
}
|
|
1945
|
-
finally {
|
|
1946
|
-
try {
|
|
1947
|
-
await transactionManager.close();
|
|
974
|
+
return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
|
|
975
|
+
});
|
|
976
|
+
const queryStr = (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} (${(0, sql_template_tag_1.join)(columns, ", ")}) VALUES ${(0, sql_template_tag_1.join)(valueRows, ", ")}`;
|
|
977
|
+
const queryResult = (await session.query(queryStr));
|
|
978
|
+
let affected = items.length;
|
|
979
|
+
if (this.isMySqlFamily()) {
|
|
980
|
+
affected = queryResult?.results?.affectedRows ?? items.length;
|
|
1948
981
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
982
|
+
else if (queryResult?.rowCount !== undefined) {
|
|
983
|
+
affected = queryResult.rowCount;
|
|
1951
984
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
|
|
1955
|
-
const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1956
|
-
const valueList = (0, sql_template_tag_1.join)(values, ", ");
|
|
1957
|
-
if (this.isMySqlFamily()) {
|
|
1958
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
|
|
1959
|
-
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
|
|
1960
|
-
}
|
|
1961
|
-
const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1962
|
-
if (this.isPostgres()) {
|
|
1963
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
|
|
1964
|
-
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
|
|
1965
|
-
}
|
|
1966
|
-
if ((this.dbType ?? this.client.type) === "sqlite") {
|
|
1967
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
|
|
1968
|
-
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
|
|
1969
|
-
}
|
|
1970
|
-
throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
|
|
985
|
+
return { affected };
|
|
986
|
+
});
|
|
1971
987
|
}
|
|
1972
988
|
async delete(entity, criteria) {
|
|
1973
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
989
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1974
990
|
if (!metadata) {
|
|
1975
991
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1976
992
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
await transactionManager.connect();
|
|
1980
|
-
await transactionManager.startTransaction();
|
|
1981
|
-
if (this.isMySqlFamily()) {
|
|
1982
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1983
|
-
}
|
|
1984
|
-
await this.runHooks(entity, criteria, "beforeDelete");
|
|
993
|
+
return this.executeInTransaction(async (session) => {
|
|
994
|
+
await this.cascadeHandler.runHooks(entity, criteria, "beforeDelete");
|
|
1985
995
|
await this.eventEmitter.emit("beforeDelete", { entity, data: criteria });
|
|
1986
996
|
await this.notifySubscribers(entity, "beforeDelete", {
|
|
1987
997
|
entityClass: entity,
|
|
1988
998
|
criteria,
|
|
1989
999
|
manager: this,
|
|
1990
1000
|
});
|
|
1991
|
-
await this.cascadeDeleteOneToMany(entity, criteria);
|
|
1001
|
+
await this.cascadeHandler.cascadeDeleteOneToMany(entity, criteria);
|
|
1992
1002
|
const whereMap = [];
|
|
1993
1003
|
for (const key in criteria) {
|
|
1994
1004
|
const value = criteria[key];
|
|
@@ -2003,9 +1013,8 @@ class EntityManager {
|
|
|
2003
1013
|
const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${whereSql}`;
|
|
2004
1014
|
const deleteStart = Date.now();
|
|
2005
1015
|
this.beginTrackQuery();
|
|
2006
|
-
const queryResult = (await
|
|
1016
|
+
const queryResult = (await session.query(deleteQuery));
|
|
2007
1017
|
this.trackQuery(entity.name, deleteQuery.text ?? String(deleteQuery), Date.now() - deleteStart);
|
|
2008
|
-
await transactionManager.commit();
|
|
2009
1018
|
let affected = 0;
|
|
2010
1019
|
if (this.isMySqlFamily()) {
|
|
2011
1020
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -2013,7 +1022,7 @@ class EntityManager {
|
|
|
2013
1022
|
else {
|
|
2014
1023
|
affected = queryResult?.rowCount ?? 0;
|
|
2015
1024
|
}
|
|
2016
|
-
await this.runHooks(entity, criteria, "afterDelete");
|
|
1025
|
+
await this.cascadeHandler.runHooks(entity, criteria, "afterDelete");
|
|
2017
1026
|
await this.eventEmitter.emit("afterDelete", { entity, data: criteria });
|
|
2018
1027
|
await this.notifySubscribers(entity, "afterDelete", {
|
|
2019
1028
|
entityClass: entity,
|
|
@@ -2021,27 +1030,36 @@ class EntityManager {
|
|
|
2021
1030
|
manager: this,
|
|
2022
1031
|
});
|
|
2023
1032
|
return { affected };
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async deleteMany(entity, ids) {
|
|
1036
|
+
if (ids.length === 0) {
|
|
1037
|
+
return { affected: 0 };
|
|
2024
1038
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
}
|
|
2029
|
-
catch (rollbackError) {
|
|
2030
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
2031
|
-
}
|
|
2032
|
-
throw e;
|
|
1039
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1040
|
+
if (!metadata) {
|
|
1041
|
+
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2033
1042
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
1043
|
+
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
1044
|
+
if (!pk) {
|
|
1045
|
+
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1046
|
+
}
|
|
1047
|
+
return this.executeInTransaction(async (session) => {
|
|
1048
|
+
const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
|
|
1049
|
+
const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${(0, sql_template_tag_1.raw)(this.wrap(pk.name))} IN (${placeholders})`;
|
|
1050
|
+
const queryResult = (await session.query(deleteQuery));
|
|
1051
|
+
let affected = 0;
|
|
1052
|
+
if (this.isMySqlFamily()) {
|
|
1053
|
+
affected = queryResult?.results?.affectedRows ?? 0;
|
|
2037
1054
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
1055
|
+
else {
|
|
1056
|
+
affected = queryResult?.rowCount ?? 0;
|
|
2040
1057
|
}
|
|
2041
|
-
|
|
1058
|
+
return { affected };
|
|
1059
|
+
});
|
|
2042
1060
|
}
|
|
2043
1061
|
async clear(entity) {
|
|
2044
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1062
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2045
1063
|
if (!metadata) {
|
|
2046
1064
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2047
1065
|
}
|
|
@@ -2051,21 +1069,15 @@ class EntityManager {
|
|
|
2051
1069
|
await this.driver.clear(metadata.name);
|
|
2052
1070
|
}
|
|
2053
1071
|
async softDelete(entity, criteria) {
|
|
2054
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1072
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2055
1073
|
if (!metadata) {
|
|
2056
1074
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2057
1075
|
}
|
|
2058
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1076
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
2059
1077
|
if (!deletedAtColumn) {
|
|
2060
1078
|
throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Use delete() instead.`);
|
|
2061
1079
|
}
|
|
2062
|
-
|
|
2063
|
-
try {
|
|
2064
|
-
await transactionManager.connect();
|
|
2065
|
-
await transactionManager.startTransaction();
|
|
2066
|
-
if (this.isMySqlFamily()) {
|
|
2067
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2068
|
-
}
|
|
1080
|
+
return this.executeInTransaction(async (session) => {
|
|
2069
1081
|
const whereMap = [];
|
|
2070
1082
|
for (const key in criteria) {
|
|
2071
1083
|
const value = criteria[key];
|
|
@@ -2079,8 +1091,7 @@ class EntityManager {
|
|
|
2079
1091
|
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2080
1092
|
const nowExpr = this.isPostgres() ? (0, sql_template_tag_1.raw)("NOW()") : (0, sql_template_tag_1.raw)("NOW()");
|
|
2081
1093
|
const updateQuery = (0, sql_template_tag_1.default) `UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} SET ${(0, sql_template_tag_1.raw)(this.wrap(deletedAtColumn))} = ${nowExpr} WHERE ${whereSql}`;
|
|
2082
|
-
const queryResult = (await
|
|
2083
|
-
await transactionManager.commit();
|
|
1094
|
+
const queryResult = (await session.query(updateQuery));
|
|
2084
1095
|
let affected = 0;
|
|
2085
1096
|
if (this.isMySqlFamily()) {
|
|
2086
1097
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -2089,41 +1100,18 @@ class EntityManager {
|
|
|
2089
1100
|
affected = queryResult?.rowCount ?? 0;
|
|
2090
1101
|
}
|
|
2091
1102
|
return { affected };
|
|
2092
|
-
}
|
|
2093
|
-
catch (e) {
|
|
2094
|
-
try {
|
|
2095
|
-
await transactionManager.rollback();
|
|
2096
|
-
}
|
|
2097
|
-
catch (rollbackError) {
|
|
2098
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
2099
|
-
}
|
|
2100
|
-
throw e;
|
|
2101
|
-
}
|
|
2102
|
-
finally {
|
|
2103
|
-
try {
|
|
2104
|
-
await transactionManager.close();
|
|
2105
|
-
}
|
|
2106
|
-
catch (closeError) {
|
|
2107
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
1103
|
+
});
|
|
2110
1104
|
}
|
|
2111
1105
|
async restore(entity, criteria) {
|
|
2112
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1106
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2113
1107
|
if (!metadata) {
|
|
2114
1108
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2115
1109
|
}
|
|
2116
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1110
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
2117
1111
|
if (!deletedAtColumn) {
|
|
2118
1112
|
throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Cannot restore.`);
|
|
2119
1113
|
}
|
|
2120
|
-
|
|
2121
|
-
try {
|
|
2122
|
-
await transactionManager.connect();
|
|
2123
|
-
await transactionManager.startTransaction();
|
|
2124
|
-
if (this.isMySqlFamily()) {
|
|
2125
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2126
|
-
}
|
|
1114
|
+
return this.executeInTransaction(async (session) => {
|
|
2127
1115
|
const whereMap = [];
|
|
2128
1116
|
for (const key in criteria) {
|
|
2129
1117
|
const value = criteria[key];
|
|
@@ -2136,8 +1124,7 @@ class EntityManager {
|
|
|
2136
1124
|
}
|
|
2137
1125
|
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2138
1126
|
const restoreQuery = (0, sql_template_tag_1.default) `UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} SET ${(0, sql_template_tag_1.raw)(this.wrap(deletedAtColumn))} = NULL WHERE ${whereSql}`;
|
|
2139
|
-
const queryResult = (await
|
|
2140
|
-
await transactionManager.commit();
|
|
1127
|
+
const queryResult = (await session.query(restoreQuery));
|
|
2141
1128
|
let affected = 0;
|
|
2142
1129
|
if (this.isMySqlFamily()) {
|
|
2143
1130
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -2146,138 +1133,167 @@ class EntityManager {
|
|
|
2146
1133
|
affected = queryResult?.rowCount ?? 0;
|
|
2147
1134
|
}
|
|
2148
1135
|
return { affected };
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
async upsert(entity, data, conflictColumns) {
|
|
1139
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1140
|
+
if (!metadata) {
|
|
1141
|
+
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2149
1142
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
await transactionManager.rollback();
|
|
2153
|
-
}
|
|
2154
|
-
catch (rollbackError) {
|
|
2155
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
2156
|
-
}
|
|
2157
|
-
throw e;
|
|
1143
|
+
if (!this.driver) {
|
|
1144
|
+
throw new Error("Driver is not initialized.");
|
|
2158
1145
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
1146
|
+
const pkColumns = metadata.columns
|
|
1147
|
+
.filter((col) => col.options?.primary)
|
|
1148
|
+
.map((col) => col.name);
|
|
1149
|
+
const resolvedConflictColumns = conflictColumns ?? pkColumns;
|
|
1150
|
+
if (resolvedConflictColumns.length === 0) {
|
|
1151
|
+
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1152
|
+
}
|
|
1153
|
+
const insertableColumns = metadata.columns.filter((col) => {
|
|
1154
|
+
const value = data[col.name];
|
|
1155
|
+
if (col.options?.autoIncrement &&
|
|
1156
|
+
(value === null || value === undefined)) {
|
|
1157
|
+
return false;
|
|
2165
1158
|
}
|
|
1159
|
+
return value !== undefined;
|
|
1160
|
+
});
|
|
1161
|
+
if (insertableColumns.length === 0) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const conflictSet = new Set(resolvedConflictColumns);
|
|
1165
|
+
const updateColumnNames = insertableColumns
|
|
1166
|
+
.map((col) => col.name)
|
|
1167
|
+
.filter((name) => !conflictSet.has(name));
|
|
1168
|
+
const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
|
|
1169
|
+
const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
|
|
1170
|
+
const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
|
|
1171
|
+
const tableName = this.wrap(metadata.name);
|
|
1172
|
+
if (wrappedUpdate.length === 0) {
|
|
1173
|
+
return;
|
|
2166
1174
|
}
|
|
1175
|
+
await this.executeInTransaction(async (session) => {
|
|
1176
|
+
const columnValues = insertableColumns.map((col) => data[col.name]);
|
|
1177
|
+
const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
|
|
1178
|
+
await session.query(upsertSql);
|
|
1179
|
+
});
|
|
2167
1180
|
}
|
|
2168
|
-
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
await this.save(RelatedEntity, child);
|
|
2184
|
-
}
|
|
1181
|
+
buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
|
|
1182
|
+
const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1183
|
+
const valueList = (0, sql_template_tag_1.join)(values, ", ");
|
|
1184
|
+
if (this.isMySqlFamily()) {
|
|
1185
|
+
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
|
|
1186
|
+
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
|
|
1187
|
+
}
|
|
1188
|
+
const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1189
|
+
if (this.isPostgres()) {
|
|
1190
|
+
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
|
|
1191
|
+
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
|
|
1192
|
+
}
|
|
1193
|
+
if ((this.dbType ?? this.client.type) === "sqlite") {
|
|
1194
|
+
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
|
|
1195
|
+
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON CONFLICT (${conflictList}) DO UPDATE SET ${updateSet}`;
|
|
2185
1196
|
}
|
|
1197
|
+
throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
|
|
2186
1198
|
}
|
|
2187
|
-
async
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
1199
|
+
async count(entity, where) {
|
|
1200
|
+
return this.aggregateHandler.count(entity, where);
|
|
1201
|
+
}
|
|
1202
|
+
async sum(entity, field, where) {
|
|
1203
|
+
return this.aggregateHandler.sum(entity, field, where);
|
|
1204
|
+
}
|
|
1205
|
+
async avg(entity, field, where) {
|
|
1206
|
+
return this.aggregateHandler.avg(entity, field, where);
|
|
1207
|
+
}
|
|
1208
|
+
async min(entity, field, where) {
|
|
1209
|
+
return this.aggregateHandler.min(entity, field, where);
|
|
1210
|
+
}
|
|
1211
|
+
async max(entity, field, where) {
|
|
1212
|
+
return this.aggregateHandler.max(entity, field, where);
|
|
1213
|
+
}
|
|
1214
|
+
async explain(entity, findOption = {}) {
|
|
1215
|
+
return this.explainHandler.explain(entity, findOption);
|
|
1216
|
+
}
|
|
1217
|
+
wrap(columnName) {
|
|
1218
|
+
if (this.driver && "wrap" in this.driver) {
|
|
1219
|
+
return this.driver.wrap(columnName);
|
|
1220
|
+
}
|
|
1221
|
+
if (this.isPostgres()) {
|
|
1222
|
+
return `"${columnName.replace(/"/g, '""')}"`;
|
|
2205
1223
|
}
|
|
1224
|
+
return `\`${columnName.replace(/`/g, "``")}\``;
|
|
2206
1225
|
}
|
|
2207
|
-
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
1226
|
+
isMySqlFamily() {
|
|
1227
|
+
const t = this.dbType ?? this.client.type;
|
|
1228
|
+
return ["mysql", "mariadb"].includes(t);
|
|
1229
|
+
}
|
|
1230
|
+
isPostgres() {
|
|
1231
|
+
const t = this.dbType ?? this.client.type;
|
|
1232
|
+
return t === "postgres";
|
|
1233
|
+
}
|
|
1234
|
+
isSqlite() {
|
|
1235
|
+
const t = this.dbType ?? this.client.type;
|
|
1236
|
+
return t === "sqlite";
|
|
1237
|
+
}
|
|
1238
|
+
warnIfNonSortablePk(entityName, pk) {
|
|
1239
|
+
const pkType = pk.options?.type;
|
|
1240
|
+
const numericTypes = new Set([
|
|
1241
|
+
"int", "number", "float", "double", "bigint",
|
|
1242
|
+
]);
|
|
1243
|
+
if (!pkType || numericTypes.has(pkType)) {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const key = entityName;
|
|
1247
|
+
if (this.cursorPkWarned.has(key)) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
this.cursorPkWarned.add(key);
|
|
1251
|
+
const base = `[CursorPagination] '${entityName}' entity uses a non-numeric PK ` +
|
|
1252
|
+
`(type: ${pkType}). Cursor pagination defaults to PK ordering, ` +
|
|
1253
|
+
`which may not reflect insertion order for random values like UUID v4.`;
|
|
1254
|
+
if (this.isMySqlFamily()) {
|
|
1255
|
+
this.logger.warn(`${base} MySQL stores UUIDs as VARCHAR — lexicographic ordering ` +
|
|
1256
|
+
`does not match time-based ordering. Consider specifying ` +
|
|
1257
|
+
`orderBy: "createdAt" or using a sequential ID.`);
|
|
1258
|
+
}
|
|
1259
|
+
else if (this.isPostgres()) {
|
|
1260
|
+
this.logger.warn(`${base} PostgreSQL compares UUID values lexicographically. ` +
|
|
1261
|
+
`For time-ordered pagination, use UUID v7 (sortable) or specify ` +
|
|
1262
|
+
`orderBy: "createdAt".`);
|
|
1263
|
+
}
|
|
1264
|
+
else if (this.isSqlite()) {
|
|
1265
|
+
this.logger.warn(`${base} SQLite compares TEXT values lexicographically. ` +
|
|
1266
|
+
`Consider specifying orderBy: "createdAt" or using an INTEGER PK.`);
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
this.logger.warn(`${base} Consider specifying an explicit orderBy column ` +
|
|
1270
|
+
`(e.g. orderBy: "createdAt") for meaningful pagination order.`);
|
|
2236
1271
|
}
|
|
2237
1272
|
}
|
|
2238
|
-
async
|
|
2239
|
-
const
|
|
2240
|
-
if (
|
|
2241
|
-
|
|
1273
|
+
async executeInTransaction(fn, existingSession, readNodeOverride) {
|
|
1274
|
+
const reusable = existingSession ?? Transactional_1.transactionStorage.getStore();
|
|
1275
|
+
if (reusable) {
|
|
1276
|
+
return fn(reusable);
|
|
2242
1277
|
}
|
|
2243
|
-
const
|
|
1278
|
+
const session = new TransactionSessionManager_1.TransactionSessionManager();
|
|
2244
1279
|
try {
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
if (this.isMySqlFamily()) {
|
|
2248
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2249
|
-
}
|
|
2250
|
-
const tableName = metadata.name;
|
|
2251
|
-
const selectExpr = (0, sql_template_tag_1.raw)(`${fn}(${field === "*" ? "*" : this.wrap(field)})`);
|
|
2252
|
-
const whereMap = [];
|
|
2253
|
-
if (where) {
|
|
2254
|
-
for (const key in where) {
|
|
2255
|
-
const value = where[key];
|
|
2256
|
-
if (value !== undefined && value !== null) {
|
|
2257
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
let queryStr;
|
|
2262
|
-
if (whereMap.length > 0) {
|
|
2263
|
-
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2264
|
-
queryStr = (0, sql_template_tag_1.default) `SELECT ${selectExpr} AS ${(0, sql_template_tag_1.raw)(this.wrap("result"))} FROM ${(0, sql_template_tag_1.raw)(this.wrap(tableName))} WHERE ${whereSql}`;
|
|
1280
|
+
if (readNodeOverride) {
|
|
1281
|
+
await session.connectToNode(readNodeOverride);
|
|
2265
1282
|
}
|
|
2266
1283
|
else {
|
|
2267
|
-
|
|
1284
|
+
await session.connect();
|
|
2268
1285
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
return value === null || value === undefined ? 0 : Number(value);
|
|
1286
|
+
await session.startTransaction();
|
|
1287
|
+
if (this.isMySqlFamily()) {
|
|
1288
|
+
await session.query("SET autocommit = 0");
|
|
1289
|
+
}
|
|
1290
|
+
const result = await fn(session);
|
|
1291
|
+
await session.commit();
|
|
1292
|
+
return result;
|
|
2277
1293
|
}
|
|
2278
1294
|
catch (e) {
|
|
2279
1295
|
try {
|
|
2280
|
-
await
|
|
1296
|
+
await session.rollback();
|
|
2281
1297
|
}
|
|
2282
1298
|
catch (rollbackError) {
|
|
2283
1299
|
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
@@ -2286,43 +1302,27 @@ class EntityManager {
|
|
|
2286
1302
|
}
|
|
2287
1303
|
finally {
|
|
2288
1304
|
try {
|
|
2289
|
-
await
|
|
1305
|
+
await session.close();
|
|
2290
1306
|
}
|
|
2291
1307
|
catch (closeError) {
|
|
2292
1308
|
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
2293
1309
|
}
|
|
2294
1310
|
}
|
|
2295
1311
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
return this.aggregate(entity, "SUM", field, where);
|
|
2308
|
-
}
|
|
2309
|
-
async avg(entity, field, where) {
|
|
2310
|
-
return this.aggregate(entity, "AVG", field, where);
|
|
2311
|
-
}
|
|
2312
|
-
async min(entity, field, where) {
|
|
2313
|
-
return this.aggregate(entity, "MIN", field, where);
|
|
2314
|
-
}
|
|
2315
|
-
async max(entity, field, where) {
|
|
2316
|
-
return this.aggregate(entity, "MAX", field, where);
|
|
1312
|
+
resolveSelectColumns(select) {
|
|
1313
|
+
if (Array.isArray(select)) {
|
|
1314
|
+
return select.map((col) => String(col));
|
|
1315
|
+
}
|
|
1316
|
+
const columns = [];
|
|
1317
|
+
for (const key in select) {
|
|
1318
|
+
if (select[key] === true) {
|
|
1319
|
+
columns.push(key);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return columns;
|
|
2317
1323
|
}
|
|
2318
1324
|
async query(sqlQuery, params) {
|
|
2319
|
-
|
|
2320
|
-
try {
|
|
2321
|
-
await transactionHolder.connect();
|
|
2322
|
-
await transactionHolder.startTransaction();
|
|
2323
|
-
if (this.isMySqlFamily()) {
|
|
2324
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
2325
|
-
}
|
|
1325
|
+
return this.executeInTransaction(async (session) => {
|
|
2326
1326
|
let queryResult;
|
|
2327
1327
|
if (typeof sqlQuery === "string") {
|
|
2328
1328
|
if (params && params.length > 0) {
|
|
@@ -2332,16 +1332,15 @@ class EntityManager {
|
|
|
2332
1332
|
values: params,
|
|
2333
1333
|
strings: [sqlQuery],
|
|
2334
1334
|
};
|
|
2335
|
-
queryResult = await
|
|
1335
|
+
queryResult = await session.query(parameterizedSql);
|
|
2336
1336
|
}
|
|
2337
1337
|
else {
|
|
2338
|
-
queryResult = await
|
|
1338
|
+
queryResult = await session.query(sqlQuery);
|
|
2339
1339
|
}
|
|
2340
1340
|
}
|
|
2341
1341
|
else {
|
|
2342
|
-
queryResult = await
|
|
1342
|
+
queryResult = await session.query(sqlQuery);
|
|
2343
1343
|
}
|
|
2344
|
-
await transactionHolder.commit();
|
|
2345
1344
|
if (queryResult?.results) {
|
|
2346
1345
|
return queryResult.results ?? [];
|
|
2347
1346
|
}
|
|
@@ -2349,24 +1348,7 @@ class EntityManager {
|
|
|
2349
1348
|
return queryResult;
|
|
2350
1349
|
}
|
|
2351
1350
|
return [];
|
|
2352
|
-
}
|
|
2353
|
-
catch (e) {
|
|
2354
|
-
try {
|
|
2355
|
-
await transactionHolder.rollback();
|
|
2356
|
-
}
|
|
2357
|
-
catch (rollbackError) {
|
|
2358
|
-
this.logger.error(`Failed to rollback raw query transaction: ${rollbackError}`);
|
|
2359
|
-
}
|
|
2360
|
-
throw e;
|
|
2361
|
-
}
|
|
2362
|
-
finally {
|
|
2363
|
-
try {
|
|
2364
|
-
await transactionHolder.close();
|
|
2365
|
-
}
|
|
2366
|
-
catch (closeError) {
|
|
2367
|
-
this.logger.error(`Failed to close raw query transaction: ${closeError}`);
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
1351
|
+
});
|
|
2370
1352
|
}
|
|
2371
1353
|
getRepository(entity) {
|
|
2372
1354
|
return BaseRepository_1.BaseRepository.of(entity, this);
|