@stingerloom/orm 0.2.0 → 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 -43
- package/dist/core/EntityManager.d.ts.map +1 -1
- package/dist/core/EntityManager.js +509 -1492
- 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/generators/SchemaGenerator.d.ts.map +1 -1
- package/dist/core/generators/SchemaGenerator.js +4 -0
- package/dist/core/generators/SchemaGenerator.js.map +1 -1
- 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/Column.d.ts +1 -1
- package/dist/decorators/Column.d.ts.map +1 -1
- package/dist/decorators/Column.js.map +1 -1
- package/dist/decorators/CreateTimestamp.d.ts +3 -0
- package/dist/decorators/CreateTimestamp.d.ts.map +1 -0
- package/dist/decorators/CreateTimestamp.js +16 -0
- package/dist/decorators/CreateTimestamp.js.map +1 -0
- 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/UpdateTimestamp.d.ts +3 -0
- package/dist/decorators/UpdateTimestamp.d.ts.map +1 -0
- package/dist/decorators/UpdateTimestamp.js +16 -0
- package/dist/decorators/UpdateTimestamp.js.map +1 -0
- 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/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.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 +11 -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 +6 -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 +5 -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/metadata/MetadataContext.d.ts.map +1 -1
- package/dist/metadata/MetadataContext.js +3 -1
- package/dist/metadata/MetadataContext.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,9 +155,43 @@ class EntityManager {
|
|
|
132
155
|
this.defaultQueryTimeout = queryTimeout;
|
|
133
156
|
}
|
|
134
157
|
if (replication) {
|
|
135
|
-
this.
|
|
158
|
+
this.replication.initialize(replication);
|
|
136
159
|
}
|
|
137
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
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return allQueriesCompleted;
|
|
191
|
+
}
|
|
192
|
+
getNameStrategy(clazz) {
|
|
193
|
+
return clazz.name;
|
|
194
|
+
}
|
|
138
195
|
initQueryTracker(options) {
|
|
139
196
|
const logging = options.logging;
|
|
140
197
|
if (typeof logging === "object" && logging !== null) {
|
|
@@ -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,606 +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
|
-
async runHooks(entity, item, event) {
|
|
279
|
-
const hooks = Reflect.getMetadata(decorators_1.HOOK_TOKEN, entity);
|
|
280
|
-
if (!hooks || hooks.length === 0)
|
|
281
|
-
return;
|
|
282
|
-
for (const hook of hooks) {
|
|
283
|
-
if (hook.event !== event)
|
|
284
|
-
continue;
|
|
285
|
-
const method = item[hook.methodName];
|
|
286
|
-
if (typeof method === "function") {
|
|
287
|
-
await method.call(item);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
createProxy(entity) {
|
|
292
|
-
return new Proxy(entity, {
|
|
293
|
-
set: (target, prop, value) => {
|
|
294
|
-
target[prop] = value;
|
|
295
|
-
this.dirtyEntities.add(target);
|
|
296
|
-
return true;
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
async registerEntities() {
|
|
301
|
-
const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
|
|
302
|
-
const entities = entityScanner.makeEntities();
|
|
303
|
-
let entity;
|
|
304
|
-
const { synchronize } = this.client.getOptions();
|
|
305
|
-
if (synchronize && this.isPostgres() && this.driver) {
|
|
306
|
-
const pgDriver = this.driver;
|
|
307
|
-
const hasSchema = await pgDriver.hasSchema();
|
|
308
|
-
if (!hasSchema || hasSchema.length === 0) {
|
|
309
|
-
await pgDriver.createSchema();
|
|
310
|
-
await pgDriver.setSearchPath();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
const entityList = [];
|
|
314
|
-
while ((entity = entities.next())) {
|
|
315
|
-
if (entity.done) {
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
const metadata = entity.value;
|
|
319
|
-
const TargetEntity = metadata.target;
|
|
320
|
-
let tableName = metadata.name;
|
|
321
|
-
if (!tableName) {
|
|
322
|
-
tableName = this.getNameStrategy(TargetEntity);
|
|
323
|
-
}
|
|
324
|
-
if (!utils_1.ReflectManager.isEntity(TargetEntity)) {
|
|
325
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(tableName ?? "Unknown");
|
|
326
|
-
}
|
|
327
|
-
if (synchronize) {
|
|
328
|
-
const hasTable = await this.driver?.hasTable(tableName);
|
|
329
|
-
if (!hasTable || hasTable.length === 0) {
|
|
330
|
-
await this.driver?.createTable(tableName, metadata.columns);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
entityList.push({ TargetEntity, tableName, metadata });
|
|
334
|
-
}
|
|
335
|
-
if (synchronize) {
|
|
336
|
-
for (const { TargetEntity, tableName } of entityList) {
|
|
337
|
-
await this.registerForeignKeys(TargetEntity, tableName);
|
|
338
|
-
await this.registerIndex(TargetEntity, tableName);
|
|
339
|
-
await this.registerUniqueIndexes(TargetEntity, tableName);
|
|
340
|
-
}
|
|
341
|
-
await this.registerManyToManyJoinTables(entityList.map((e) => e.TargetEntity));
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async registerUniqueIndexes(TargetEntity, tableName) {
|
|
345
|
-
const uniqueIndexes = Reflect.getMetadata(UniqueIndex_1.UNIQUE_INDEX_TOKEN, TargetEntity);
|
|
346
|
-
if (!uniqueIndexes || uniqueIndexes.length === 0)
|
|
347
|
-
return;
|
|
348
|
-
for (const uq of uniqueIndexes) {
|
|
349
|
-
const indexName = uq.name ?? `uq_${tableName}_${uq.columns.join("_")}`;
|
|
350
|
-
const indexes = (await this.driver?.getIndexes(tableName));
|
|
351
|
-
let isExist = false;
|
|
352
|
-
for (const idx of indexes || []) {
|
|
353
|
-
const existingIndexName = idx["Key_name"] ?? idx["Field"] ?? idx["name"];
|
|
354
|
-
if (existingIndexName === indexName) {
|
|
355
|
-
isExist = true;
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (!isExist) {
|
|
360
|
-
await this.driver?.addCompositeUniqueIndex(tableName, uq.columns, indexName);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
async registerManyToManyJoinTables(entities) {
|
|
365
|
-
const processedTables = new Set();
|
|
366
|
-
for (const entity of entities) {
|
|
367
|
-
const m2mMeta = (Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
|
|
368
|
-
[]);
|
|
369
|
-
for (const rel of m2mMeta) {
|
|
370
|
-
if (!rel.joinTable)
|
|
371
|
-
continue;
|
|
372
|
-
const { name: joinTableName, joinColumn, inverseJoinColumn, } = rel.joinTable;
|
|
373
|
-
if (processedTables.has(joinTableName))
|
|
374
|
-
continue;
|
|
375
|
-
processedTables.add(joinTableName);
|
|
376
|
-
const ownerEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, entity);
|
|
377
|
-
const ownerTable = ownerEntityMeta?.name ?? entity.name;
|
|
378
|
-
const relatedEntity = rel.getRelatedEntity();
|
|
379
|
-
const relatedEntityMeta = Reflect.getMetadata(decorators_1.ENTITY_TOKEN, relatedEntity);
|
|
380
|
-
const relatedTable = relatedEntityMeta?.name ?? relatedEntity.name;
|
|
381
|
-
const hasTable = await this.driver?.hasTable(joinTableName);
|
|
382
|
-
if (!hasTable || hasTable.length === 0) {
|
|
383
|
-
const wJoinTable = this.wrap(joinTableName);
|
|
384
|
-
const wJoinCol = this.wrap(joinColumn);
|
|
385
|
-
const wInvCol = this.wrap(inverseJoinColumn);
|
|
386
|
-
let ddl = `CREATE TABLE IF NOT EXISTS ${wJoinTable} (${wJoinCol} INT NOT NULL, ${wInvCol} INT NOT NULL, PRIMARY KEY (${wJoinCol}, ${wInvCol}))`;
|
|
387
|
-
if (this.isMySqlFamily())
|
|
388
|
-
ddl += " ENGINE=InnoDB";
|
|
389
|
-
await this.driver?.executeRaw(ddl);
|
|
390
|
-
}
|
|
391
|
-
const ownerColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ?? []);
|
|
392
|
-
const ownerPk = ownerColumns.find((c) => c.options?.primary)?.name;
|
|
393
|
-
const relatedColumns = (Reflect.getMetadata(decorators_1.COLUMN_TOKEN, relatedEntity.prototype) ?? []);
|
|
394
|
-
const relatedPk = relatedColumns.find((c) => c.options?.primary)?.name;
|
|
395
|
-
const ownerFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, joinColumn, ownerTable);
|
|
396
|
-
if (ownerPk &&
|
|
397
|
-
this.driver &&
|
|
398
|
-
!(await this.driver.hasForeignKey(joinTableName, ownerFkName))) {
|
|
399
|
-
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`;
|
|
400
|
-
await this.driver.executeRaw(ddl);
|
|
401
|
-
}
|
|
402
|
-
const relatedFkName = SchemaGenerator_1.SchemaGenerator.generateForeignKeyName(joinTableName, inverseJoinColumn, relatedTable);
|
|
403
|
-
if (relatedPk &&
|
|
404
|
-
this.driver &&
|
|
405
|
-
!(await this.driver.hasForeignKey(joinTableName, relatedFkName))) {
|
|
406
|
-
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`;
|
|
407
|
-
await this.driver.executeRaw(ddl);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
resolveManyToOneMetadata(entity) {
|
|
413
|
-
const manyToOneScanner = typedi_1.default.get(scanner_1.ManyToOneScanner);
|
|
414
|
-
const allRelations = manyToOneScanner
|
|
415
|
-
.allMetadata()
|
|
416
|
-
.filter((rel) => rel.target === entity);
|
|
417
|
-
if (allRelations.length > 0) {
|
|
418
|
-
return this.resolveJoinColumnsFromColumnMeta(entity, allRelations);
|
|
419
|
-
}
|
|
420
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity) ??
|
|
421
|
-
Reflect.getMetadata(decorators_1.MANY_TO_ONE_TOKEN, entity.prototype);
|
|
422
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
423
|
-
this.logger.warn(`[resolveManyToOneMetadata] "${entity.name}" ManyToOne resolved via Reflect.getMetadata fallback.`);
|
|
424
|
-
return this.resolveJoinColumnsFromColumnMeta(entity, reflectMetadata);
|
|
425
|
-
}
|
|
426
|
-
return [];
|
|
427
|
-
}
|
|
428
|
-
resolveJoinColumnsFromColumnMeta(entity, relations) {
|
|
429
|
-
const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
|
|
430
|
-
Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
|
|
431
|
-
[];
|
|
432
|
-
if (columnsMeta.length === 0) {
|
|
433
|
-
return relations;
|
|
434
|
-
}
|
|
435
|
-
return relations.map((rel) => {
|
|
436
|
-
if (rel.joinColumn)
|
|
437
|
-
return rel;
|
|
438
|
-
const fkPropertyName = `${rel.columnName}Id`;
|
|
439
|
-
const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
|
|
440
|
-
if (!matchingColumn)
|
|
441
|
-
return rel;
|
|
442
|
-
const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
|
|
443
|
-
return {
|
|
444
|
-
...rel,
|
|
445
|
-
joinColumn: resolvedJoinColumn,
|
|
446
|
-
};
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
resolveOneToManyMetadata(entity) {
|
|
450
|
-
const oneToManyScanner = typedi_1.default.get(scanner_1.OneToManyScanner);
|
|
451
|
-
const allRelations = oneToManyScanner
|
|
452
|
-
.allMetadata()
|
|
453
|
-
.filter((rel) => rel.target === entity);
|
|
454
|
-
if (allRelations.length > 0) {
|
|
455
|
-
return allRelations;
|
|
456
|
-
}
|
|
457
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity) ??
|
|
458
|
-
Reflect.getMetadata(decorators_1.ONE_TO_MANY_TOKEN, entity.prototype);
|
|
459
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
460
|
-
this.logger.warn(`[resolveOneToManyMetadata] "${entity.name}" OneToMany resolved via Reflect.getMetadata fallback.`);
|
|
461
|
-
return reflectMetadata;
|
|
462
|
-
}
|
|
463
|
-
return [];
|
|
464
|
-
}
|
|
465
|
-
async loadOneToManyRelations(entity, parentResults, relations) {
|
|
466
|
-
const oneToManyMeta = this.resolveOneToManyMetadata(entity);
|
|
467
|
-
if (oneToManyMeta.length === 0)
|
|
468
|
-
return;
|
|
469
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
470
|
-
if (!parentMetadata)
|
|
471
|
-
return;
|
|
472
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
473
|
-
if (!pk)
|
|
474
|
-
return;
|
|
475
|
-
const parents = Array.isArray(parentResults)
|
|
476
|
-
? parentResults
|
|
477
|
-
: [parentResults];
|
|
478
|
-
for (const rel of oneToManyMeta) {
|
|
479
|
-
if (!relations.includes(rel.propertyKey))
|
|
480
|
-
continue;
|
|
481
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
482
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
483
|
-
if (!relatedMetadata)
|
|
484
|
-
continue;
|
|
485
|
-
const manyToOneItems = this.resolveManyToOneMetadata(RelatedEntity);
|
|
486
|
-
const matchingRelation = manyToOneItems.find((m) => m.columnName === rel.mappedBy);
|
|
487
|
-
const fkColumn = matchingRelation?.joinColumn ?? rel.mappedBy;
|
|
488
|
-
for (const parent of parents) {
|
|
489
|
-
const parentId = parent[pk.name];
|
|
490
|
-
if (parentId === undefined || parentId === null)
|
|
491
|
-
continue;
|
|
492
|
-
const children = await this.find(RelatedEntity, {
|
|
493
|
-
where: { [fkColumn]: parentId },
|
|
494
|
-
});
|
|
495
|
-
if (children === undefined) {
|
|
496
|
-
parent[rel.propertyKey] = [];
|
|
497
|
-
}
|
|
498
|
-
else if (Array.isArray(children)) {
|
|
499
|
-
parent[rel.propertyKey] = children;
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
parent[rel.propertyKey] = [children];
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
resolveManyToManyMetadata(entity) {
|
|
508
|
-
const manyToManyScanner = typedi_1.default.get(scanner_1.ManyToManyScanner);
|
|
509
|
-
const allRelations = manyToManyScanner
|
|
510
|
-
.allMetadata()
|
|
511
|
-
.filter((rel) => rel.target === entity);
|
|
512
|
-
if (allRelations.length > 0) {
|
|
513
|
-
return allRelations;
|
|
514
|
-
}
|
|
515
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity) ??
|
|
516
|
-
Reflect.getMetadata(decorators_1.MANY_TO_MANY_TOKEN, entity.prototype);
|
|
517
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
518
|
-
this.logger.warn(`[resolveManyToManyMetadata] "${entity.name}" ManyToMany resolved via Reflect.getMetadata fallback.`);
|
|
519
|
-
return reflectMetadata;
|
|
520
|
-
}
|
|
521
|
-
return [];
|
|
522
|
-
}
|
|
523
|
-
resolveOneToOneMetadata(entity) {
|
|
524
|
-
const oneToOneScanner = typedi_1.default.get(scanner_1.OneToOneScanner);
|
|
525
|
-
const allRelations = oneToOneScanner
|
|
526
|
-
.allMetadata()
|
|
527
|
-
.filter((rel) => rel.target === entity);
|
|
528
|
-
if (allRelations.length > 0) {
|
|
529
|
-
return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, allRelations);
|
|
530
|
-
}
|
|
531
|
-
const reflectMetadata = Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity) ??
|
|
532
|
-
Reflect.getMetadata(decorators_1.ONE_TO_ONE_TOKEN, entity.prototype);
|
|
533
|
-
if (reflectMetadata && reflectMetadata.length > 0) {
|
|
534
|
-
this.logger.warn(`[resolveOneToOneMetadata] "${entity.name}" OneToOne resolved via Reflect.getMetadata fallback.`);
|
|
535
|
-
return this.resolveJoinColumnsFromColumnMetaForOneToOne(entity, reflectMetadata);
|
|
536
|
-
}
|
|
537
|
-
return [];
|
|
538
|
-
}
|
|
539
|
-
resolveJoinColumnsFromColumnMetaForOneToOne(entity, relations) {
|
|
540
|
-
const columnsMeta = Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity) ??
|
|
541
|
-
Reflect.getMetadata(decorators_1.COLUMN_TOKEN, entity.prototype) ??
|
|
542
|
-
[];
|
|
543
|
-
if (columnsMeta.length === 0) {
|
|
544
|
-
return relations;
|
|
545
|
-
}
|
|
546
|
-
return relations.map((rel) => {
|
|
547
|
-
if (rel.joinColumn)
|
|
548
|
-
return rel;
|
|
549
|
-
const fkPropertyName = `${rel.propertyKey}Id`;
|
|
550
|
-
const matchingColumn = columnsMeta.find((col) => col.propertyKey === fkPropertyName);
|
|
551
|
-
if (!matchingColumn)
|
|
552
|
-
return rel;
|
|
553
|
-
const resolvedJoinColumn = matchingColumn.name ?? fkPropertyName;
|
|
554
|
-
return {
|
|
555
|
-
...rel,
|
|
556
|
-
joinColumn: resolvedJoinColumn,
|
|
557
|
-
};
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
resolveManyToManyJoinTable(rel) {
|
|
561
|
-
if (rel.joinTable) {
|
|
562
|
-
return {
|
|
563
|
-
joinTableName: rel.joinTable.name,
|
|
564
|
-
joinColumn: rel.joinTable.joinColumn,
|
|
565
|
-
inverseJoinColumn: rel.joinTable.inverseJoinColumn,
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
if (rel.mappedBy) {
|
|
569
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
570
|
-
const relatedManyToMany = this.resolveManyToManyMetadata(RelatedEntity);
|
|
571
|
-
const ownerRel = relatedManyToMany.find((r) => r.propertyKey === rel.mappedBy && r.joinTable);
|
|
572
|
-
if (ownerRel?.joinTable) {
|
|
573
|
-
return {
|
|
574
|
-
joinTableName: ownerRel.joinTable.name,
|
|
575
|
-
joinColumn: ownerRel.joinTable.inverseJoinColumn,
|
|
576
|
-
inverseJoinColumn: ownerRel.joinTable.joinColumn,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
async loadManyToManyRelations(entity, parentResults, relations) {
|
|
583
|
-
const manyToManyMeta = this.resolveManyToManyMetadata(entity);
|
|
584
|
-
if (manyToManyMeta.length === 0)
|
|
585
|
-
return;
|
|
586
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
587
|
-
if (!parentMetadata)
|
|
588
|
-
return;
|
|
589
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
590
|
-
if (!pk)
|
|
591
|
-
return;
|
|
592
|
-
const parents = Array.isArray(parentResults)
|
|
593
|
-
? parentResults
|
|
594
|
-
: [parentResults];
|
|
595
|
-
for (const rel of manyToManyMeta) {
|
|
596
|
-
if (!relations.includes(rel.propertyKey))
|
|
597
|
-
continue;
|
|
598
|
-
const joinInfo = this.resolveManyToManyJoinTable(rel);
|
|
599
|
-
if (!joinInfo)
|
|
600
|
-
continue;
|
|
601
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
602
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
603
|
-
if (!relatedMetadata)
|
|
604
|
-
continue;
|
|
605
|
-
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
606
|
-
if (!relatedPk)
|
|
607
|
-
continue;
|
|
608
|
-
const relatedTableName = relatedMetadata.name ?? RelatedEntity.name;
|
|
609
|
-
for (const parent of parents) {
|
|
610
|
-
const parentId = parent[pk.name];
|
|
611
|
-
if (parentId === undefined || parentId === null)
|
|
612
|
-
continue;
|
|
613
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
614
|
-
const selectCols = relatedMetadata.columns.map((col) => `${this.wrap(relatedTableName)}.${this.wrap(col.name)}`);
|
|
615
|
-
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))}`;
|
|
616
|
-
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}`;
|
|
617
|
-
qb.select(selectCols)
|
|
618
|
-
.from(this.wrap(relatedTableName))
|
|
619
|
-
.innerJoin(this.wrap(joinInfo.joinTableName), this.wrap(joinInfo.joinTableName), joinCondition)
|
|
620
|
-
.where([whereCondition]);
|
|
621
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
622
|
-
try {
|
|
623
|
-
await transactionHolder.connect();
|
|
624
|
-
await transactionHolder.startTransaction();
|
|
625
|
-
if (this.isMySqlFamily()) {
|
|
626
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
627
|
-
}
|
|
628
|
-
const resultQuery = qb.build();
|
|
629
|
-
const subQueryStart = Date.now();
|
|
630
|
-
this.beginTrackQuery();
|
|
631
|
-
const queryResult = (await transactionHolder.query(resultQuery));
|
|
632
|
-
this.trackQuery(relatedTableName, resultQuery.text ?? String(resultQuery), Date.now() - subQueryStart);
|
|
633
|
-
await transactionHolder.commit();
|
|
634
|
-
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
635
|
-
const { results } = queryResult;
|
|
636
|
-
if (!results || results.length === 0) {
|
|
637
|
-
parent[rel.propertyKey] = [];
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
parent[rel.propertyKey] = resultTransformer.toEntities(RelatedEntity, queryResult);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
catch (e) {
|
|
644
|
-
try {
|
|
645
|
-
await transactionHolder.rollback();
|
|
646
|
-
}
|
|
647
|
-
catch (rollbackError) {
|
|
648
|
-
this.logger.error(`Failed to rollback ManyToMany transaction: ${rollbackError}`);
|
|
649
|
-
}
|
|
650
|
-
throw e;
|
|
651
|
-
}
|
|
652
|
-
finally {
|
|
653
|
-
try {
|
|
654
|
-
await transactionHolder.close();
|
|
655
|
-
}
|
|
656
|
-
catch (closeError) {
|
|
657
|
-
this.logger.error(`Failed to close ManyToMany transaction: ${closeError}`);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
async loadOneToOneRelations(entity, parentResults, relations) {
|
|
664
|
-
const oneToOneMeta = this.resolveOneToOneMetadata(entity);
|
|
665
|
-
if (oneToOneMeta.length === 0)
|
|
666
|
-
return;
|
|
667
|
-
const parentMetadata = this.resolveEntityMetadata(entity);
|
|
668
|
-
if (!parentMetadata)
|
|
669
|
-
return;
|
|
670
|
-
const pk = parentMetadata.columns.find((column) => column.options?.primary);
|
|
671
|
-
if (!pk)
|
|
672
|
-
return;
|
|
673
|
-
const parents = Array.isArray(parentResults)
|
|
674
|
-
? parentResults
|
|
675
|
-
: [parentResults];
|
|
676
|
-
for (const rel of oneToOneMeta) {
|
|
677
|
-
if (!relations.includes(rel.propertyKey))
|
|
678
|
-
continue;
|
|
679
|
-
if (rel.joinColumn) {
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
const RelatedEntity = rel.getRelatedEntity();
|
|
683
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
684
|
-
if (!relatedMetadata)
|
|
685
|
-
continue;
|
|
686
|
-
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
687
|
-
if (!relatedPk)
|
|
688
|
-
continue;
|
|
689
|
-
for (const parent of parents) {
|
|
690
|
-
if (rel.joinColumn) {
|
|
691
|
-
const fkValue = parent[rel.joinColumn];
|
|
692
|
-
if (fkValue === undefined || fkValue === null) {
|
|
693
|
-
parent[rel.propertyKey] = null;
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
const related = await this.findOne(RelatedEntity, {
|
|
697
|
-
where: { [relatedPk.name]: fkValue },
|
|
698
|
-
});
|
|
699
|
-
parent[rel.propertyKey] = related ?? null;
|
|
700
|
-
}
|
|
701
|
-
else if (rel.inverseSide) {
|
|
702
|
-
const parentId = parent[pk.name];
|
|
703
|
-
if (parentId === undefined || parentId === null) {
|
|
704
|
-
parent[rel.propertyKey] = null;
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
const relatedOneToOne = this.resolveOneToOneMetadata(RelatedEntity);
|
|
708
|
-
const ownerRel = relatedOneToOne.find((r) => r.propertyKey === rel.inverseSide && r.joinColumn);
|
|
709
|
-
if (ownerRel?.joinColumn) {
|
|
710
|
-
const related = await this.findOne(RelatedEntity, {
|
|
711
|
-
where: { [ownerRel.joinColumn]: parentId },
|
|
712
|
-
});
|
|
713
|
-
parent[rel.propertyKey] = related ?? null;
|
|
714
|
-
}
|
|
715
|
-
else {
|
|
716
|
-
parent[rel.propertyKey] = null;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
async registerForeignKeys(TargetEntity, tableName) {
|
|
723
|
-
const entityScanner = typedi_1.default.get(scanner_1.EntityScanner);
|
|
724
|
-
const manyToOneItems = this.resolveManyToOneMetadata(TargetEntity);
|
|
725
|
-
const isValidManyToOne = manyToOneItems && manyToOneItems.length > 0;
|
|
726
|
-
if (isValidManyToOne) {
|
|
727
|
-
for (const manyToOneItem of manyToOneItems) {
|
|
728
|
-
const { joinColumn } = manyToOneItem;
|
|
729
|
-
const mappingEntity = manyToOneItem.getMappingEntity();
|
|
730
|
-
if (!mappingEntity) {
|
|
731
|
-
throw new EntityNotFound_1.EntityNotFound(mappingEntity);
|
|
732
|
-
}
|
|
733
|
-
const mappingTableMetadata = entityScanner.scan(mappingEntity);
|
|
734
|
-
if (!mappingTableMetadata) {
|
|
735
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(mappingEntity.name);
|
|
736
|
-
}
|
|
737
|
-
if (!joinColumn) {
|
|
738
|
-
throw new InvalidQueryError_1.InvalidQueryError("JoinColumn does not exist.");
|
|
739
|
-
}
|
|
740
|
-
const mappingTablePrimaryKey = manyToOneItem.references
|
|
741
|
-
? manyToOneItem.references
|
|
742
|
-
: mappingTableMetadata.columns.find((e) => e.options?.primary)?.name;
|
|
743
|
-
if (!mappingTablePrimaryKey) {
|
|
744
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(mappingEntity.name);
|
|
745
|
-
}
|
|
746
|
-
const mappingTableName = mappingTableMetadata.name || this.getNameStrategy(mappingEntity);
|
|
747
|
-
if (this.driver) {
|
|
748
|
-
const columnExists = await this.driver.hasColumn(tableName, joinColumn);
|
|
749
|
-
if (!columnExists) {
|
|
750
|
-
const fkColumnType = this.driver.castType("int") + " NULL";
|
|
751
|
-
await this.driver.addColumn(tableName, joinColumn, fkColumnType);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
if (this.driver) {
|
|
755
|
-
const fkName = this.driver.generateForeignKeyName(tableName, mappingTableName, joinColumn);
|
|
756
|
-
const fkExists = await this.driver.hasForeignKey(tableName, fkName);
|
|
757
|
-
if (fkExists)
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
await this.driver?.addForeignKey(tableName, joinColumn, mappingTableName, mappingTablePrimaryKey);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
const oneToOneItems = this.resolveOneToOneMetadata(TargetEntity);
|
|
764
|
-
for (const oneToOneItem of oneToOneItems) {
|
|
765
|
-
const { joinColumn } = oneToOneItem;
|
|
766
|
-
if (!joinColumn)
|
|
767
|
-
continue;
|
|
768
|
-
const RelatedEntity = oneToOneItem.getRelatedEntity();
|
|
769
|
-
if (!RelatedEntity) {
|
|
770
|
-
throw new EntityNotFound_1.EntityNotFound(RelatedEntity);
|
|
771
|
-
}
|
|
772
|
-
const relatedMetadata = entityScanner.scan(RelatedEntity);
|
|
773
|
-
if (!relatedMetadata) {
|
|
774
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(RelatedEntity.name);
|
|
775
|
-
}
|
|
776
|
-
const relatedPrimaryKey = relatedMetadata.columns.find((e) => e.options?.primary)?.name;
|
|
777
|
-
if (!relatedPrimaryKey) {
|
|
778
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(RelatedEntity.name);
|
|
779
|
-
}
|
|
780
|
-
if (this.driver) {
|
|
781
|
-
const columnExists = await this.driver.hasColumn(tableName, joinColumn);
|
|
782
|
-
if (!columnExists) {
|
|
783
|
-
const fkColumnType = this.driver.castType("int") + " NULL";
|
|
784
|
-
await this.driver.addColumn(tableName, joinColumn, fkColumnType);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
const relatedTableName = relatedMetadata.name || this.getNameStrategy(RelatedEntity);
|
|
788
|
-
if (this.driver) {
|
|
789
|
-
const fkName = this.driver.generateForeignKeyName(tableName, relatedTableName, joinColumn);
|
|
790
|
-
const fkExists = await this.driver.hasForeignKey(tableName, fkName);
|
|
791
|
-
if (fkExists)
|
|
792
|
-
continue;
|
|
793
|
-
}
|
|
794
|
-
await this.driver?.addForeignKey(tableName, joinColumn, relatedTableName, relatedPrimaryKey);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
async registerIndex(TargetEntity, tableName) {
|
|
798
|
-
const indexer = Reflect.getMetadata(Indexer_1.INDEX_TOKEN, TargetEntity.prototype);
|
|
799
|
-
if (indexer) {
|
|
800
|
-
for (const index of indexer) {
|
|
801
|
-
const indexName = `INDEX_${tableName}_${index.name}`;
|
|
802
|
-
const indexes = (await this.driver?.getIndexes(tableName));
|
|
803
|
-
let isExist = false;
|
|
804
|
-
for (const idx of indexes || []) {
|
|
805
|
-
const existingIndexName = idx["Key_name"] ?? idx["Field"];
|
|
806
|
-
if (existingIndexName === indexName) {
|
|
807
|
-
isExist = true;
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
if (!isExist) {
|
|
812
|
-
await this.driver?.addIndex(tableName, index.name, indexName);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
270
|
async findOne(entity, findOption) {
|
|
818
|
-
|
|
271
|
+
return this.findOneInternal(entity, findOption);
|
|
272
|
+
}
|
|
273
|
+
async findOneInternal(entity, findOption, existingSession) {
|
|
274
|
+
const result = await this.findInternal(entity, { ...findOption, limit: 1 }, existingSession);
|
|
819
275
|
if (result === undefined || result === null) {
|
|
820
276
|
return null;
|
|
821
277
|
}
|
|
@@ -824,347 +280,16 @@ class EntityManager {
|
|
|
824
280
|
}
|
|
825
281
|
return result;
|
|
826
282
|
}
|
|
827
|
-
async explain(entity, findOption = {}) {
|
|
828
|
-
if (!this.driver || !this.driver.supportsExplain()) {
|
|
829
|
-
throw new InvalidQueryError_1.InvalidQueryError("EXPLAIN is not supported by the current database driver.");
|
|
830
|
-
}
|
|
831
|
-
const { select, orderBy, where, take } = findOption;
|
|
832
|
-
const { limit } = findOption;
|
|
833
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
834
|
-
if (!metadata) {
|
|
835
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
836
|
-
}
|
|
837
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
838
|
-
const selectMap = [];
|
|
839
|
-
const whereMap = [];
|
|
840
|
-
const orderByMap = [];
|
|
841
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
842
|
-
const eagerRelations = manyToOneRelations.filter((rel) => {
|
|
843
|
-
const isEager = rel.option?.eager === true;
|
|
844
|
-
const isInRelations = findOption.relations?.includes(rel.columnName);
|
|
845
|
-
return isEager || isInRelations;
|
|
846
|
-
});
|
|
847
|
-
const oneToOneRelations = this.resolveOneToOneMetadata(entity);
|
|
848
|
-
const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
|
|
849
|
-
if (!rel.joinColumn)
|
|
850
|
-
return false;
|
|
851
|
-
const isEager = rel.option?.eager === true;
|
|
852
|
-
const isInRelations = findOption.relations?.includes(rel.propertyKey);
|
|
853
|
-
return isEager || isInRelations;
|
|
854
|
-
});
|
|
855
|
-
const hasEagerJoins = eagerRelations.length > 0 || eagerOneToOneRelations.length > 0;
|
|
856
|
-
const tableName = metadata.name;
|
|
857
|
-
if (select) {
|
|
858
|
-
const selectedColumns = this.resolveSelectColumns(select);
|
|
859
|
-
if (hasEagerJoins) {
|
|
860
|
-
selectMap.push(...selectedColumns.map((col) => `${this.wrap(tableName)}.${this.wrap(col)}`));
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
selectMap.push(...selectedColumns.map((col) => this.wrap(col)));
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
if (hasEagerJoins) {
|
|
868
|
-
selectMap.push(...metadata.columns.map((column) => `${this.wrap(tableName)}.${this.wrap(column.name)}`));
|
|
869
|
-
}
|
|
870
|
-
else {
|
|
871
|
-
selectMap.push(...metadata.columns.map((column) => this.wrap(column.name)));
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
for (const key in where) {
|
|
875
|
-
const value = where[key];
|
|
876
|
-
if (value !== undefined && value !== null) {
|
|
877
|
-
if (hasEagerJoins) {
|
|
878
|
-
whereMap.push(Conditions_1.Conditions.equals(`${this.wrap(tableName)}.${this.wrap(key)}`, value));
|
|
879
|
-
}
|
|
880
|
-
else {
|
|
881
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
886
|
-
if (deletedAtColumn && !findOption.withDeleted) {
|
|
887
|
-
if (hasEagerJoins) {
|
|
888
|
-
whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
|
|
889
|
-
}
|
|
890
|
-
else {
|
|
891
|
-
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
for (const key in orderBy) {
|
|
895
|
-
const value = orderBy[key];
|
|
896
|
-
if (value) {
|
|
897
|
-
orderByMap.push({ column: this.wrap(key), direction: value });
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
qb.select(selectMap).from(this.wrap(tableName));
|
|
901
|
-
qb.where(whereMap).orderBy(orderByMap);
|
|
902
|
-
if (Array.isArray(limit)) {
|
|
903
|
-
let [offset, count] = limit;
|
|
904
|
-
if (offset < 0)
|
|
905
|
-
offset = 0;
|
|
906
|
-
if (count < 0)
|
|
907
|
-
count = 0;
|
|
908
|
-
if (count === 0)
|
|
909
|
-
count = 1;
|
|
910
|
-
if (take && take > 0)
|
|
911
|
-
count = take;
|
|
912
|
-
if (this.isMySqlFamily())
|
|
913
|
-
qb.setDatabaseType("mysql");
|
|
914
|
-
qb.limit([offset, count]);
|
|
915
|
-
}
|
|
916
|
-
else if (limit) {
|
|
917
|
-
qb.limit(limit);
|
|
918
|
-
}
|
|
919
|
-
const selectQuery = qb.build();
|
|
920
|
-
const explainPrefix = this.driver.buildExplainSql("");
|
|
921
|
-
const explainQuery = (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(explainPrefix)}${selectQuery}`;
|
|
922
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
923
|
-
try {
|
|
924
|
-
const readNode = this.getReadNode(findOption.useMaster);
|
|
925
|
-
if (readNode) {
|
|
926
|
-
await transactionHolder.connectToNode(readNode);
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
await transactionHolder.connect();
|
|
930
|
-
}
|
|
931
|
-
await transactionHolder.startTransaction();
|
|
932
|
-
if (this.isMySqlFamily()) {
|
|
933
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
934
|
-
}
|
|
935
|
-
const result = await transactionHolder.query(explainQuery);
|
|
936
|
-
await transactionHolder.commit();
|
|
937
|
-
const rawRows = result?.results ?? [];
|
|
938
|
-
return this.parseExplainResult(rawRows);
|
|
939
|
-
}
|
|
940
|
-
catch (e) {
|
|
941
|
-
try {
|
|
942
|
-
await transactionHolder.rollback();
|
|
943
|
-
}
|
|
944
|
-
catch {
|
|
945
|
-
}
|
|
946
|
-
throw e;
|
|
947
|
-
}
|
|
948
|
-
finally {
|
|
949
|
-
try {
|
|
950
|
-
await transactionHolder.close();
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
parseExplainResult(rawRows) {
|
|
957
|
-
if (!rawRows || rawRows.length === 0) {
|
|
958
|
-
return {
|
|
959
|
-
raw: [],
|
|
960
|
-
rows: null,
|
|
961
|
-
type: null,
|
|
962
|
-
possibleKeys: null,
|
|
963
|
-
key: null,
|
|
964
|
-
cost: null,
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
const firstRow = rawRows[0];
|
|
968
|
-
if (firstRow && "QUERY PLAN" in firstRow) {
|
|
969
|
-
return this.parsePostgresExplain(firstRow["QUERY PLAN"]);
|
|
970
|
-
}
|
|
971
|
-
if ("type" in firstRow || "select_type" in firstRow) {
|
|
972
|
-
return this.parseMysqlExplain(rawRows);
|
|
973
|
-
}
|
|
974
|
-
if ("detail" in firstRow || "notused" in firstRow) {
|
|
975
|
-
return this.parseSqliteExplain(rawRows);
|
|
976
|
-
}
|
|
977
|
-
return {
|
|
978
|
-
raw: rawRows,
|
|
979
|
-
rows: null,
|
|
980
|
-
type: null,
|
|
981
|
-
possibleKeys: null,
|
|
982
|
-
key: null,
|
|
983
|
-
cost: null,
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
parseMysqlExplain(rawRows) {
|
|
987
|
-
const first = rawRows[0];
|
|
988
|
-
const rows = first.rows != null ? Number(first.rows) : null;
|
|
989
|
-
const type = first.type != null ? String(first.type) : null;
|
|
990
|
-
const possibleKeysRaw = first.possible_keys;
|
|
991
|
-
const possibleKeys = possibleKeysRaw != null
|
|
992
|
-
? String(possibleKeysRaw)
|
|
993
|
-
.split(",")
|
|
994
|
-
.map((k) => k.trim())
|
|
995
|
-
: null;
|
|
996
|
-
const key = first.key != null ? String(first.key) : null;
|
|
997
|
-
const cost = first.filtered != null ? Number(first.filtered) : null;
|
|
998
|
-
return { raw: rawRows, rows, type, possibleKeys, key, cost };
|
|
999
|
-
}
|
|
1000
|
-
parsePostgresExplain(queryPlan) {
|
|
1001
|
-
const rawArray = Array.isArray(queryPlan) ? queryPlan : [queryPlan];
|
|
1002
|
-
const plan = rawArray[0]?.Plan ?? rawArray[0]?.["Plan"] ?? null;
|
|
1003
|
-
if (!plan) {
|
|
1004
|
-
return {
|
|
1005
|
-
raw: rawArray,
|
|
1006
|
-
rows: null,
|
|
1007
|
-
type: null,
|
|
1008
|
-
possibleKeys: null,
|
|
1009
|
-
key: null,
|
|
1010
|
-
cost: null,
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
const rows = plan["Plan Rows"] != null ? Number(plan["Plan Rows"]) : null;
|
|
1014
|
-
const type = plan["Node Type"] != null ? String(plan["Node Type"]) : null;
|
|
1015
|
-
const key = plan["Index Name"] != null ? String(plan["Index Name"]) : null;
|
|
1016
|
-
const cost = plan["Total Cost"] != null ? Number(plan["Total Cost"]) : null;
|
|
1017
|
-
return { raw: rawArray, rows, type, possibleKeys: null, key, cost };
|
|
1018
|
-
}
|
|
1019
|
-
parseSqliteExplain(rawRows) {
|
|
1020
|
-
const details = rawRows.map((r) => String(r.detail ?? ""));
|
|
1021
|
-
const firstDetail = details[0] ?? "";
|
|
1022
|
-
let type = null;
|
|
1023
|
-
let key = null;
|
|
1024
|
-
if (firstDetail.startsWith("SCAN"))
|
|
1025
|
-
type = "SCAN";
|
|
1026
|
-
else if (firstDetail.startsWith("SEARCH"))
|
|
1027
|
-
type = "SEARCH";
|
|
1028
|
-
const indexMatch = firstDetail.match(/USING (?:COVERING )?INDEX (\S+)/);
|
|
1029
|
-
if (indexMatch)
|
|
1030
|
-
key = indexMatch[1];
|
|
1031
|
-
return {
|
|
1032
|
-
raw: rawRows,
|
|
1033
|
-
rows: null,
|
|
1034
|
-
type,
|
|
1035
|
-
possibleKeys: null,
|
|
1036
|
-
key,
|
|
1037
|
-
cost: null,
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
async findWithCursor(entity, option = {}) {
|
|
1041
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1042
|
-
if (!metadata) {
|
|
1043
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1044
|
-
}
|
|
1045
|
-
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
1046
|
-
const orderByColumn = option.orderBy ?? pk?.name;
|
|
1047
|
-
if (!orderByColumn) {
|
|
1048
|
-
throw new InvalidQueryError_1.InvalidQueryError("Cursor pagination requires an orderBy column or a primary key.");
|
|
1049
|
-
}
|
|
1050
|
-
const direction = option.direction ?? "ASC";
|
|
1051
|
-
const pageSize = (0, CursorPagination_1.normalizePageSize)(option.take);
|
|
1052
|
-
let cursorValue = null;
|
|
1053
|
-
if (option.cursor) {
|
|
1054
|
-
cursorValue = (0, CursorPagination_1.decodeCursor)(option.cursor);
|
|
1055
|
-
if (cursorValue === null) {
|
|
1056
|
-
throw new InvalidQueryError_1.InvalidQueryError("Invalid cursor value.");
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
const where = { ...(option.where ?? {}) };
|
|
1060
|
-
const transactionHolder = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1061
|
-
const resultTransformer = ResultTransformerFactory_1.ResultTransformerFactory.create();
|
|
1062
|
-
try {
|
|
1063
|
-
const readNode = this.getReadNode(option.useMaster);
|
|
1064
|
-
if (readNode) {
|
|
1065
|
-
await transactionHolder.connectToNode(readNode);
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
await transactionHolder.connect();
|
|
1069
|
-
}
|
|
1070
|
-
await transactionHolder.startTransaction();
|
|
1071
|
-
if (this.isMySqlFamily()) {
|
|
1072
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
1073
|
-
}
|
|
1074
|
-
const tableName = metadata.name;
|
|
1075
|
-
const qb = RawQueryBuilderFactory_1.RawQueryBuilderFactory.create();
|
|
1076
|
-
const selectMap = metadata.columns.map((column) => this.wrap(column.name));
|
|
1077
|
-
const whereMap = [];
|
|
1078
|
-
for (const key in where) {
|
|
1079
|
-
const value = where[key];
|
|
1080
|
-
if (value !== undefined && value !== null) {
|
|
1081
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1085
|
-
if (deletedAtColumn) {
|
|
1086
|
-
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
1087
|
-
}
|
|
1088
|
-
if (cursorValue !== null) {
|
|
1089
|
-
if (direction === "ASC") {
|
|
1090
|
-
whereMap.push(Conditions_1.Conditions.gt(this.wrap(orderByColumn), cursorValue));
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
whereMap.push(Conditions_1.Conditions.lt(this.wrap(orderByColumn), cursorValue));
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
qb.select(selectMap)
|
|
1097
|
-
.from(this.wrap(tableName))
|
|
1098
|
-
.where(whereMap)
|
|
1099
|
-
.orderBy([{ column: this.wrap(orderByColumn), direction }]);
|
|
1100
|
-
qb.limit(pageSize + 1);
|
|
1101
|
-
const resultQuery = qb.build();
|
|
1102
|
-
const queryResult = (await transactionHolder.query(resultQuery));
|
|
1103
|
-
await transactionHolder.commit();
|
|
1104
|
-
const { results } = queryResult;
|
|
1105
|
-
if (!results || results.length === 0) {
|
|
1106
|
-
return {
|
|
1107
|
-
data: [],
|
|
1108
|
-
hasNextPage: false,
|
|
1109
|
-
nextCursor: null,
|
|
1110
|
-
count: 0,
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
const hasNextPage = results.length > pageSize;
|
|
1114
|
-
const pageResults = hasNextPage ? results.slice(0, pageSize) : results;
|
|
1115
|
-
const entities = resultTransformer.toEntities(entity, {
|
|
1116
|
-
results: pageResults,
|
|
1117
|
-
fields: queryResult.fields,
|
|
1118
|
-
});
|
|
1119
|
-
let nextCursor = null;
|
|
1120
|
-
if (hasNextPage && pageResults.length > 0) {
|
|
1121
|
-
const lastItem = pageResults[pageResults.length - 1];
|
|
1122
|
-
const lastValue = lastItem[orderByColumn];
|
|
1123
|
-
nextCursor = (0, CursorPagination_1.encodeCursor)(lastValue);
|
|
1124
|
-
}
|
|
1125
|
-
return {
|
|
1126
|
-
data: entities,
|
|
1127
|
-
hasNextPage,
|
|
1128
|
-
nextCursor,
|
|
1129
|
-
count: entities.length,
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
catch (e) {
|
|
1133
|
-
try {
|
|
1134
|
-
await transactionHolder.rollback();
|
|
1135
|
-
}
|
|
1136
|
-
catch (rollbackError) {
|
|
1137
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1138
|
-
}
|
|
1139
|
-
throw e;
|
|
1140
|
-
}
|
|
1141
|
-
finally {
|
|
1142
|
-
try {
|
|
1143
|
-
await transactionHolder.close();
|
|
1144
|
-
}
|
|
1145
|
-
catch (closeError) {
|
|
1146
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
283
|
async find(entity, findOption = {}) {
|
|
284
|
+
return this.findInternal(entity, findOption);
|
|
285
|
+
}
|
|
286
|
+
async findInternal(entity, findOption = {}, existingSession) {
|
|
1151
287
|
const { select, orderBy, where, take, groupBy, having } = findOption;
|
|
1152
288
|
const { limit } = findOption;
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const
|
|
1157
|
-
if (readNode) {
|
|
1158
|
-
await transactionHolder.connectToNode(readNode);
|
|
1159
|
-
}
|
|
1160
|
-
else {
|
|
1161
|
-
await transactionHolder.connect();
|
|
1162
|
-
}
|
|
1163
|
-
await transactionHolder.startTransaction();
|
|
1164
|
-
if (this.isMySqlFamily()) {
|
|
1165
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
1166
|
-
}
|
|
1167
|
-
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);
|
|
1168
293
|
if (!metadata) {
|
|
1169
294
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1170
295
|
}
|
|
@@ -1172,13 +297,13 @@ class EntityManager {
|
|
|
1172
297
|
const selectMap = [];
|
|
1173
298
|
const whereMap = [];
|
|
1174
299
|
const orderByMap = [];
|
|
1175
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
300
|
+
const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1176
301
|
const eagerRelations = manyToOneRelations.filter((rel) => {
|
|
1177
302
|
const isEager = rel.option?.eager === true;
|
|
1178
303
|
const isInRelations = findOption.relations?.includes(rel.columnName);
|
|
1179
304
|
return isEager || isInRelations;
|
|
1180
305
|
});
|
|
1181
|
-
const oneToOneRelations = this.resolveOneToOneMetadata(entity);
|
|
306
|
+
const oneToOneRelations = this.resolver.resolveOneToOneMetadata(entity);
|
|
1182
307
|
const eagerOneToOneRelations = oneToOneRelations.filter((rel) => {
|
|
1183
308
|
if (!rel.joinColumn)
|
|
1184
309
|
return false;
|
|
@@ -1207,7 +332,7 @@ class EntityManager {
|
|
|
1207
332
|
}
|
|
1208
333
|
for (const rel of eagerRelations) {
|
|
1209
334
|
const RelatedEntity = rel.getMappingEntity();
|
|
1210
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
335
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1211
336
|
if (!relatedMetadata)
|
|
1212
337
|
continue;
|
|
1213
338
|
const relatedName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1218,7 +343,7 @@ class EntityManager {
|
|
|
1218
343
|
}
|
|
1219
344
|
for (const rel of eagerOneToOneRelations) {
|
|
1220
345
|
const RelatedEntity = rel.getRelatedEntity();
|
|
1221
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
346
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1222
347
|
if (!relatedMetadata)
|
|
1223
348
|
continue;
|
|
1224
349
|
const relatedName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1238,7 +363,7 @@ class EntityManager {
|
|
|
1238
363
|
}
|
|
1239
364
|
}
|
|
1240
365
|
}
|
|
1241
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
366
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
1242
367
|
if (deletedAtColumn && !findOption.withDeleted) {
|
|
1243
368
|
if (hasEagerJoins) {
|
|
1244
369
|
whereMap.push(Conditions_1.Conditions.isNull(`${this.wrap(tableName)}.${this.wrap(deletedAtColumn)}`));
|
|
@@ -1256,7 +381,7 @@ class EntityManager {
|
|
|
1256
381
|
qb.select(selectMap).from(this.wrap(tableName));
|
|
1257
382
|
for (const rel of eagerRelations) {
|
|
1258
383
|
const RelatedEntity = rel.getMappingEntity();
|
|
1259
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
384
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1260
385
|
if (!relatedMetadata)
|
|
1261
386
|
continue;
|
|
1262
387
|
const relatedTableName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1269,7 +394,7 @@ class EntityManager {
|
|
|
1269
394
|
}
|
|
1270
395
|
for (const rel of eagerOneToOneRelations) {
|
|
1271
396
|
const RelatedEntity = rel.getRelatedEntity();
|
|
1272
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
397
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1273
398
|
if (!relatedMetadata)
|
|
1274
399
|
continue;
|
|
1275
400
|
const relatedTableName = relatedMetadata.name || RelatedEntity.name;
|
|
@@ -1293,21 +418,16 @@ class EntityManager {
|
|
|
1293
418
|
qb.orderBy(orderByMap);
|
|
1294
419
|
if (Array.isArray(limit)) {
|
|
1295
420
|
let [offset, count] = limit;
|
|
1296
|
-
if (offset < 0)
|
|
421
|
+
if (offset < 0)
|
|
1297
422
|
offset = 0;
|
|
1298
|
-
|
|
1299
|
-
if (count < 0) {
|
|
423
|
+
if (count < 0)
|
|
1300
424
|
count = 0;
|
|
1301
|
-
|
|
1302
|
-
if (count === 0) {
|
|
425
|
+
if (count === 0)
|
|
1303
426
|
count = 1;
|
|
1304
|
-
|
|
1305
|
-
if (take && take > 0) {
|
|
427
|
+
if (take && take > 0)
|
|
1306
428
|
count = take;
|
|
1307
|
-
|
|
1308
|
-
if (this.isMySqlFamily()) {
|
|
429
|
+
if (this.isMySqlFamily())
|
|
1309
430
|
qb.setDatabaseType("mysql");
|
|
1310
|
-
}
|
|
1311
431
|
qb.limit([offset, count]);
|
|
1312
432
|
}
|
|
1313
433
|
else {
|
|
@@ -1319,13 +439,12 @@ class EntityManager {
|
|
|
1319
439
|
const effectiveTimeout = findOption.timeout ?? this.defaultQueryTimeout;
|
|
1320
440
|
if (effectiveTimeout && effectiveTimeout > 0 && this.driver) {
|
|
1321
441
|
const timeoutSql = this.driver.setQueryTimeout(effectiveTimeout);
|
|
1322
|
-
await
|
|
442
|
+
await session.query(timeoutSql);
|
|
1323
443
|
}
|
|
1324
444
|
const queryStartTime = Date.now();
|
|
1325
445
|
this.beginTrackQuery();
|
|
1326
|
-
const queryResult = (await
|
|
446
|
+
const queryResult = (await session.query(resultQuery));
|
|
1327
447
|
this.trackQuery(entity.name, resultQuery.text ?? String(resultQuery), Date.now() - queryStartTime);
|
|
1328
|
-
await transactionHolder.commit();
|
|
1329
448
|
const { results } = queryResult;
|
|
1330
449
|
if (!results || results.length === 0) {
|
|
1331
450
|
return undefined;
|
|
@@ -1344,9 +463,9 @@ class EntityManager {
|
|
|
1344
463
|
if (findOption.relations &&
|
|
1345
464
|
findOption.relations.length > 0 &&
|
|
1346
465
|
entityResult) {
|
|
1347
|
-
await this.loadOneToManyRelations(entity, entityResult, findOption.relations);
|
|
1348
|
-
await this.loadManyToManyRelations(entity, entityResult, findOption.relations);
|
|
1349
|
-
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);
|
|
1350
469
|
}
|
|
1351
470
|
const lazyRelations = manyToOneRelations.filter((rel) => {
|
|
1352
471
|
return rel.option?.lazy === true && rel.option?.eager !== true;
|
|
@@ -1362,7 +481,7 @@ class EntityManager {
|
|
|
1362
481
|
const fkValue = item[joinColumn];
|
|
1363
482
|
if (fkValue === undefined || fkValue === null)
|
|
1364
483
|
continue;
|
|
1365
|
-
const relatedMetadata = this.resolveEntityMetadata(RelatedEntity);
|
|
484
|
+
const relatedMetadata = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1366
485
|
if (!relatedMetadata)
|
|
1367
486
|
continue;
|
|
1368
487
|
const relatedPk = relatedMetadata.columns.find((col) => col.options?.primary);
|
|
@@ -1378,69 +497,111 @@ class EntityManager {
|
|
|
1378
497
|
}
|
|
1379
498
|
}
|
|
1380
499
|
}
|
|
1381
|
-
return entityResult;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
+
}
|
|
524
|
+
}
|
|
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
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
540
|
+
if (deletedAtColumn) {
|
|
541
|
+
whereMap.push(Conditions_1.Conditions.isNull(this.wrap(deletedAtColumn)));
|
|
1389
542
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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
|
+
}
|
|
1395
550
|
}
|
|
1396
|
-
|
|
1397
|
-
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
|
+
};
|
|
1398
566
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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);
|
|
1409
578
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}
|
|
1417
|
-
if (this.isPostgres()) {
|
|
1418
|
-
return `"${columnName.replace(/"/g, '""')}"`;
|
|
1419
|
-
}
|
|
1420
|
-
return `\`${columnName.replace(/`/g, "``")}\``;
|
|
1421
|
-
}
|
|
1422
|
-
isMySqlFamily() {
|
|
1423
|
-
const t = this.dbType ?? this.client.type;
|
|
1424
|
-
return ["mysql", "mariadb"].includes(t);
|
|
579
|
+
return {
|
|
580
|
+
data: entities,
|
|
581
|
+
hasNextPage,
|
|
582
|
+
nextCursor,
|
|
583
|
+
count: entities.length,
|
|
584
|
+
};
|
|
585
|
+
}, undefined, readNode);
|
|
1425
586
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
+
});
|
|
1429
593
|
}
|
|
1430
594
|
async save(entity, item) {
|
|
1431
|
-
|
|
595
|
+
return this.saveInternal(entity, item);
|
|
596
|
+
}
|
|
597
|
+
async saveInternal(entity, item, existingSession) {
|
|
598
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1432
599
|
if (!metadata) {
|
|
1433
600
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1434
601
|
}
|
|
1435
602
|
EntityValidator_1.EntityValidator.validate(entity, item);
|
|
1436
|
-
await this.cascadeSaveManyToOne(entity, item);
|
|
1437
|
-
|
|
1438
|
-
try {
|
|
1439
|
-
await transactionManager.connect();
|
|
1440
|
-
await transactionManager.startTransaction();
|
|
1441
|
-
if (this.isMySqlFamily()) {
|
|
1442
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1443
|
-
}
|
|
603
|
+
await this.cascadeHandler.cascadeSaveManyToOne(entity, item);
|
|
604
|
+
return this.executeInTransaction(async (session) => {
|
|
1444
605
|
const pkColumns = metadata.columns.filter((column) => column.options?.primary);
|
|
1445
606
|
const pk = pkColumns[0];
|
|
1446
607
|
const hasAutoIncrementPk = pkColumns.some((col) => col.options?.autoIncrement);
|
|
@@ -1466,7 +627,7 @@ class EntityManager {
|
|
|
1466
627
|
return where;
|
|
1467
628
|
};
|
|
1468
629
|
if (isInsert) {
|
|
1469
|
-
await this.runHooks(entity, item, "beforeInsert");
|
|
630
|
+
await this.cascadeHandler.runHooks(entity, item, "beforeInsert");
|
|
1470
631
|
await this.eventEmitter.emit("beforeInsert", { entity, data: item });
|
|
1471
632
|
await this.notifySubscribers(entity, "beforeInsert", {
|
|
1472
633
|
entity: item,
|
|
@@ -1486,7 +647,29 @@ class EntityManager {
|
|
|
1486
647
|
const values = insertableColumns.map((column) => {
|
|
1487
648
|
return item[column.name];
|
|
1488
649
|
});
|
|
1489
|
-
const
|
|
650
|
+
const now = new Date().toISOString();
|
|
651
|
+
const createTsCol = this.resolver.getCreateTimestampColumn(entity);
|
|
652
|
+
if (createTsCol) {
|
|
653
|
+
const idx = insertableColumns.findIndex((col) => col.name === createTsCol);
|
|
654
|
+
if (idx >= 0) {
|
|
655
|
+
values[idx] = item[createTsCol]?.toISOString?.() ?? now;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const updateTsCol = this.resolver.getUpdateTimestampColumn(entity);
|
|
659
|
+
if (updateTsCol) {
|
|
660
|
+
const idx = insertableColumns.findIndex((col) => col.name === updateTsCol);
|
|
661
|
+
if (idx >= 0) {
|
|
662
|
+
values[idx] = item[updateTsCol]?.toISOString?.() ?? now;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
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);
|
|
1490
673
|
for (const rel of manyToOneRelations) {
|
|
1491
674
|
if (!rel.joinColumn)
|
|
1492
675
|
continue;
|
|
@@ -1499,7 +682,7 @@ class EntityManager {
|
|
|
1499
682
|
}
|
|
1500
683
|
else if (relatedValue && typeof relatedValue === "object") {
|
|
1501
684
|
const RelatedEntity = rel.getMappingEntity();
|
|
1502
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
685
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1503
686
|
if (relatedMeta) {
|
|
1504
687
|
const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
|
|
1505
688
|
if (relatedPk) {
|
|
@@ -1534,21 +717,20 @@ class EntityManager {
|
|
|
1534
717
|
`;
|
|
1535
718
|
const saveQueryStart = Date.now();
|
|
1536
719
|
this.beginTrackQuery();
|
|
1537
|
-
const queryResult = (await
|
|
720
|
+
const queryResult = (await session.query(insertSql));
|
|
1538
721
|
this.trackQuery(entity.name, insertSql.text ?? String(insertSql), Date.now() - saveQueryStart);
|
|
1539
|
-
await transactionManager.commit();
|
|
1540
722
|
if (this.isMySqlFamily()) {
|
|
1541
723
|
const findWhere = hasAutoIncrementPk
|
|
1542
724
|
? { [pk.name]: queryResult?.results?.insertId }
|
|
1543
725
|
: buildPkFindWhere();
|
|
1544
|
-
const result = await this.
|
|
726
|
+
const result = await this.findOneInternal(entity, {
|
|
1545
727
|
where: findWhere,
|
|
1546
|
-
});
|
|
728
|
+
}, session);
|
|
1547
729
|
const cascadeId = hasAutoIncrementPk
|
|
1548
730
|
? queryResult?.results?.insertId
|
|
1549
731
|
: primaryKeyValue;
|
|
1550
|
-
await this.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
1551
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
732
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
733
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1552
734
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1553
735
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1554
736
|
entity: item,
|
|
@@ -1559,12 +741,12 @@ class EntityManager {
|
|
|
1559
741
|
if (isPostgres && queryResult?.results?.length > 0) {
|
|
1560
742
|
const returnedRow = queryResult.results[0];
|
|
1561
743
|
const findWhere = buildPkFindWhere(returnedRow);
|
|
1562
|
-
const result = await this.
|
|
744
|
+
const result = await this.findOneInternal(entity, {
|
|
1563
745
|
where: findWhere,
|
|
1564
|
-
});
|
|
746
|
+
}, session);
|
|
1565
747
|
const cascadeId = returnedRow[pk.name];
|
|
1566
|
-
await this.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
1567
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
748
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, cascadeId);
|
|
749
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1568
750
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1569
751
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1570
752
|
entity: item,
|
|
@@ -1572,7 +754,7 @@ class EntityManager {
|
|
|
1572
754
|
});
|
|
1573
755
|
return result;
|
|
1574
756
|
}
|
|
1575
|
-
await this.runHooks(entity, item, "afterInsert");
|
|
757
|
+
await this.cascadeHandler.runHooks(entity, item, "afterInsert");
|
|
1576
758
|
await this.eventEmitter.emit("afterInsert", { entity, data: item });
|
|
1577
759
|
await this.notifySubscribers(entity, "afterInsert", {
|
|
1578
760
|
entity: item,
|
|
@@ -1580,23 +762,38 @@ class EntityManager {
|
|
|
1580
762
|
});
|
|
1581
763
|
return queryResult;
|
|
1582
764
|
}
|
|
1583
|
-
await this.runHooks(entity, item, "beforeUpdate");
|
|
765
|
+
await this.cascadeHandler.runHooks(entity, item, "beforeUpdate");
|
|
1584
766
|
await this.eventEmitter.emit("beforeUpdate", { entity, data: item });
|
|
1585
767
|
await this.notifySubscribers(entity, "beforeUpdate", {
|
|
1586
768
|
entity: item,
|
|
1587
769
|
manager: this,
|
|
1588
770
|
});
|
|
771
|
+
const versionColName = this.resolver.getVersionColumn(entity);
|
|
1589
772
|
const pkColumnNames = new Set(pkColumns.map((col) => col.name));
|
|
1590
773
|
const updatableColumns = metadata.columns.filter((column) => {
|
|
1591
774
|
if (pkColumnNames.has(column.name))
|
|
1592
775
|
return false;
|
|
776
|
+
if (versionColName && column.name === versionColName)
|
|
777
|
+
return false;
|
|
1593
778
|
return item[column.name] !== undefined;
|
|
1594
779
|
});
|
|
1595
780
|
const updateMap = updatableColumns.map((column) => {
|
|
1596
781
|
return (0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(column.name))} = ${item[column.name]}`;
|
|
1597
782
|
});
|
|
783
|
+
const updateTsColName = this.resolver.getUpdateTimestampColumn(entity);
|
|
784
|
+
if (updateTsColName) {
|
|
785
|
+
const existingIdx = updatableColumns.findIndex((col) => col.name === updateTsColName);
|
|
786
|
+
const updateNow = new Date().toISOString();
|
|
787
|
+
if (existingIdx >= 0) {
|
|
788
|
+
updateMap[existingIdx] =
|
|
789
|
+
(0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(updateTsColName))} = ${updateNow}`;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
updateMap.push((0, sql_template_tag_1.default) `${(0, sql_template_tag_1.raw)(this.wrap(updateTsColName))} = ${updateNow}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
1598
795
|
const updatedColumnNames = new Set(updatableColumns.map((col) => col.name));
|
|
1599
|
-
const updateManyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
796
|
+
const updateManyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1600
797
|
for (const rel of updateManyToOneRelations) {
|
|
1601
798
|
if (!rel.joinColumn)
|
|
1602
799
|
continue;
|
|
@@ -1617,7 +814,7 @@ class EntityManager {
|
|
|
1617
814
|
}
|
|
1618
815
|
else if (typeof relatedValue === "object") {
|
|
1619
816
|
const RelatedEntity = rel.getMappingEntity();
|
|
1620
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
817
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1621
818
|
if (relatedMeta) {
|
|
1622
819
|
const relatedPk = relatedMeta.columns.find((col) => col.options?.primary);
|
|
1623
820
|
if (relatedPk) {
|
|
@@ -1638,6 +835,15 @@ class EntityManager {
|
|
|
1638
835
|
}
|
|
1639
836
|
}
|
|
1640
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
|
+
}
|
|
1641
847
|
if (updateMap.length > 0) {
|
|
1642
848
|
const updateSql = (0, sql_template_tag_1.default) `
|
|
1643
849
|
UPDATE ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))}
|
|
@@ -1646,117 +852,57 @@ class EntityManager {
|
|
|
1646
852
|
`;
|
|
1647
853
|
const updateStart = Date.now();
|
|
1648
854
|
this.beginTrackQuery();
|
|
1649
|
-
await
|
|
855
|
+
const updateResult = (await session.query(updateSql));
|
|
1650
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
|
+
}
|
|
1651
869
|
}
|
|
1652
|
-
await
|
|
1653
|
-
await this.
|
|
1654
|
-
await this.runHooks(entity, item, "afterUpdate");
|
|
870
|
+
await this.cascadeHandler.cascadeSaveOneToMany(entity, item, primaryKeyValue);
|
|
871
|
+
await this.cascadeHandler.runHooks(entity, item, "afterUpdate");
|
|
1655
872
|
await this.eventEmitter.emit("afterUpdate", { entity, data: item });
|
|
1656
873
|
await this.notifySubscribers(entity, "afterUpdate", {
|
|
1657
874
|
entity: item,
|
|
1658
875
|
manager: this,
|
|
1659
876
|
});
|
|
1660
|
-
const result = await this.
|
|
877
|
+
const result = await this.findOneInternal(entity, {
|
|
1661
878
|
where: buildPkFindWhere(),
|
|
1662
|
-
});
|
|
879
|
+
}, session);
|
|
1663
880
|
return result;
|
|
1664
|
-
}
|
|
1665
|
-
catch (e) {
|
|
1666
|
-
try {
|
|
1667
|
-
await transactionManager.rollback();
|
|
1668
|
-
}
|
|
1669
|
-
catch (rollbackError) {
|
|
1670
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1671
|
-
}
|
|
1672
|
-
throw e;
|
|
1673
|
-
}
|
|
1674
|
-
finally {
|
|
1675
|
-
try {
|
|
1676
|
-
await transactionManager.close();
|
|
1677
|
-
}
|
|
1678
|
-
catch (closeError) {
|
|
1679
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
async deleteMany(entity, ids) {
|
|
1684
|
-
if (ids.length === 0) {
|
|
1685
|
-
return { affected: 0 };
|
|
1686
|
-
}
|
|
1687
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1688
|
-
if (!metadata) {
|
|
1689
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1690
|
-
}
|
|
1691
|
-
const pk = metadata.columns.find((column) => column.options?.primary);
|
|
1692
|
-
if (!pk) {
|
|
1693
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1694
|
-
}
|
|
1695
|
-
const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1696
|
-
try {
|
|
1697
|
-
await transactionManager.connect();
|
|
1698
|
-
await transactionManager.startTransaction();
|
|
1699
|
-
if (this.isMySqlFamily()) {
|
|
1700
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1701
|
-
}
|
|
1702
|
-
const placeholders = (0, sql_template_tag_1.join)(ids.map((id) => (0, sql_template_tag_1.default) `${id}`), ", ");
|
|
1703
|
-
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})`;
|
|
1704
|
-
const queryResult = (await transactionManager.query(deleteQuery));
|
|
1705
|
-
await transactionManager.commit();
|
|
1706
|
-
let affected = 0;
|
|
1707
|
-
if (this.isMySqlFamily()) {
|
|
1708
|
-
affected = queryResult?.results?.affectedRows ?? 0;
|
|
1709
|
-
}
|
|
1710
|
-
else {
|
|
1711
|
-
affected = queryResult?.rowCount ?? 0;
|
|
1712
|
-
}
|
|
1713
|
-
return { affected };
|
|
1714
|
-
}
|
|
1715
|
-
catch (e) {
|
|
1716
|
-
try {
|
|
1717
|
-
await transactionManager.rollback();
|
|
1718
|
-
}
|
|
1719
|
-
catch (rollbackError) {
|
|
1720
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1721
|
-
}
|
|
1722
|
-
throw e;
|
|
1723
|
-
}
|
|
1724
|
-
finally {
|
|
1725
|
-
try {
|
|
1726
|
-
await transactionManager.close();
|
|
1727
|
-
}
|
|
1728
|
-
catch (closeError) {
|
|
1729
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
881
|
+
}, existingSession);
|
|
1732
882
|
}
|
|
1733
883
|
async saveMany(entity, items) {
|
|
1734
884
|
if (items.length === 0) {
|
|
1735
885
|
return [];
|
|
1736
886
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
const
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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
|
+
});
|
|
1743
895
|
}
|
|
1744
896
|
async insertMany(entity, items) {
|
|
1745
897
|
if (items.length === 0) {
|
|
1746
898
|
return { affected: 0 };
|
|
1747
899
|
}
|
|
1748
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
900
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1749
901
|
if (!metadata) {
|
|
1750
902
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1751
903
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
await transactionManager.connect();
|
|
1755
|
-
await transactionManager.startTransaction();
|
|
1756
|
-
if (this.isMySqlFamily()) {
|
|
1757
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1758
|
-
}
|
|
1759
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
904
|
+
return this.executeInTransaction(async (session) => {
|
|
905
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
1760
906
|
const timestampTypes = new Set(["datetime", "timestamp", "date"]);
|
|
1761
907
|
const timestampColumns = metadata.columns.filter((col) => col.options?.type &&
|
|
1762
908
|
timestampTypes.has(col.options.type) &&
|
|
@@ -1771,6 +917,14 @@ class EntityManager {
|
|
|
1771
917
|
}
|
|
1772
918
|
}
|
|
1773
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
|
+
}
|
|
1774
928
|
const insertableColumns = metadata.columns.filter((column) => {
|
|
1775
929
|
const isAutoIncrement = column.options?.autoIncrement;
|
|
1776
930
|
if (!isAutoIncrement)
|
|
@@ -1779,7 +933,7 @@ class EntityManager {
|
|
|
1779
933
|
item[column.name] !== undefined);
|
|
1780
934
|
});
|
|
1781
935
|
const columns = insertableColumns.map((column) => (0, sql_template_tag_1.raw)(this.wrap(column.name)));
|
|
1782
|
-
const manyToOneRelations = this.resolveManyToOneMetadata(entity);
|
|
936
|
+
const manyToOneRelations = this.resolver.resolveManyToOneMetadata(entity);
|
|
1783
937
|
const fkColumns = [];
|
|
1784
938
|
for (const rel of manyToOneRelations) {
|
|
1785
939
|
if (!rel.joinColumn)
|
|
@@ -1802,7 +956,7 @@ class EntityManager {
|
|
|
1802
956
|
if (relatedValue != null) {
|
|
1803
957
|
if (typeof relatedValue === "object") {
|
|
1804
958
|
const RelatedEntity = fk.relMeta.getMappingEntity();
|
|
1805
|
-
const relatedMeta = this.resolveEntityMetadata(RelatedEntity);
|
|
959
|
+
const relatedMeta = this.resolver.resolveEntityMetadata(RelatedEntity);
|
|
1806
960
|
const relatedPk = relatedMeta?.columns.find((col) => col.options?.primary);
|
|
1807
961
|
rowValues.push(relatedPk ? relatedValue[relatedPk.name] ?? null : null);
|
|
1808
962
|
}
|
|
@@ -1817,143 +971,34 @@ class EntityManager {
|
|
|
1817
971
|
rowValues.push(null);
|
|
1818
972
|
}
|
|
1819
973
|
}
|
|
1820
|
-
return (0, sql_template_tag_1.default) `(${(0, sql_template_tag_1.join)(rowValues, ", ")})`;
|
|
1821
|
-
});
|
|
1822
|
-
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, ", ")}`;
|
|
1823
|
-
const queryResult = (await
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
affected = queryResult?.results?.affectedRows ?? items.length;
|
|
1828
|
-
}
|
|
1829
|
-
else if (queryResult?.rowCount !== undefined) {
|
|
1830
|
-
affected = queryResult.rowCount;
|
|
1831
|
-
}
|
|
1832
|
-
return { affected };
|
|
1833
|
-
}
|
|
1834
|
-
catch (e) {
|
|
1835
|
-
try {
|
|
1836
|
-
await transactionManager.rollback();
|
|
1837
|
-
}
|
|
1838
|
-
catch (rollbackError) {
|
|
1839
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1840
|
-
}
|
|
1841
|
-
throw e;
|
|
1842
|
-
}
|
|
1843
|
-
finally {
|
|
1844
|
-
try {
|
|
1845
|
-
await transactionManager.close();
|
|
1846
|
-
}
|
|
1847
|
-
catch (closeError) {
|
|
1848
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
async upsert(entity, data, conflictColumns) {
|
|
1853
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1854
|
-
if (!metadata) {
|
|
1855
|
-
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1856
|
-
}
|
|
1857
|
-
if (!this.driver) {
|
|
1858
|
-
throw new Error("Driver is not initialized.");
|
|
1859
|
-
}
|
|
1860
|
-
const pkColumns = metadata.columns
|
|
1861
|
-
.filter((col) => col.options?.primary)
|
|
1862
|
-
.map((col) => col.name);
|
|
1863
|
-
const resolvedConflictColumns = conflictColumns ?? pkColumns;
|
|
1864
|
-
if (resolvedConflictColumns.length === 0) {
|
|
1865
|
-
throw new PrimaryKeyNotFoundError_1.PrimaryKeyNotFoundError(entity.name);
|
|
1866
|
-
}
|
|
1867
|
-
const insertableColumns = metadata.columns.filter((col) => {
|
|
1868
|
-
const value = data[col.name];
|
|
1869
|
-
if (col.options?.autoIncrement &&
|
|
1870
|
-
(value === null || value === undefined)) {
|
|
1871
|
-
return false;
|
|
1872
|
-
}
|
|
1873
|
-
return value !== undefined;
|
|
1874
|
-
});
|
|
1875
|
-
if (insertableColumns.length === 0) {
|
|
1876
|
-
return;
|
|
1877
|
-
}
|
|
1878
|
-
const conflictSet = new Set(resolvedConflictColumns);
|
|
1879
|
-
const updateColumnNames = insertableColumns
|
|
1880
|
-
.map((col) => col.name)
|
|
1881
|
-
.filter((name) => !conflictSet.has(name));
|
|
1882
|
-
const wrappedColumns = insertableColumns.map((col) => this.wrap(col.name));
|
|
1883
|
-
const wrappedConflict = resolvedConflictColumns.map((name) => this.wrap(name));
|
|
1884
|
-
const wrappedUpdate = updateColumnNames.map((name) => this.wrap(name));
|
|
1885
|
-
const tableName = this.wrap(metadata.name);
|
|
1886
|
-
if (wrappedUpdate.length === 0) {
|
|
1887
|
-
return;
|
|
1888
|
-
}
|
|
1889
|
-
const transactionManager = new TransactionSessionManager_1.TransactionSessionManager();
|
|
1890
|
-
try {
|
|
1891
|
-
await transactionManager.connect();
|
|
1892
|
-
await transactionManager.startTransaction();
|
|
1893
|
-
if (this.isMySqlFamily()) {
|
|
1894
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1895
|
-
}
|
|
1896
|
-
const columnValues = insertableColumns.map((col) => data[col.name]);
|
|
1897
|
-
const upsertSql = this.buildUpsertQuery(tableName, wrappedColumns, columnValues, wrappedConflict, wrappedUpdate);
|
|
1898
|
-
await transactionManager.query(upsertSql);
|
|
1899
|
-
await transactionManager.commit();
|
|
1900
|
-
}
|
|
1901
|
-
catch (e) {
|
|
1902
|
-
try {
|
|
1903
|
-
await transactionManager.rollback();
|
|
1904
|
-
}
|
|
1905
|
-
catch (rollbackError) {
|
|
1906
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1907
|
-
}
|
|
1908
|
-
throw e;
|
|
1909
|
-
}
|
|
1910
|
-
finally {
|
|
1911
|
-
try {
|
|
1912
|
-
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;
|
|
1913
981
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
982
|
+
else if (queryResult?.rowCount !== undefined) {
|
|
983
|
+
affected = queryResult.rowCount;
|
|
1916
984
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
buildUpsertQuery(tableName, columns, values, conflictColumns, updateColumns) {
|
|
1920
|
-
const columnList = (0, sql_template_tag_1.join)(columns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1921
|
-
const valueList = (0, sql_template_tag_1.join)(values, ", ");
|
|
1922
|
-
if (this.isMySqlFamily()) {
|
|
1923
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = VALUES(${col})`)), ", ");
|
|
1924
|
-
return (0, sql_template_tag_1.default) `INSERT INTO ${(0, sql_template_tag_1.raw)(tableName)} (${columnList}) VALUES (${valueList}) ON DUPLICATE KEY UPDATE ${updateSet}`;
|
|
1925
|
-
}
|
|
1926
|
-
const conflictList = (0, sql_template_tag_1.join)(conflictColumns.map((c) => (0, sql_template_tag_1.raw)(c)), ", ");
|
|
1927
|
-
if (this.isPostgres()) {
|
|
1928
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = EXCLUDED.${col}`)), ", ");
|
|
1929
|
-
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}`;
|
|
1930
|
-
}
|
|
1931
|
-
if ((this.dbType ?? this.client.type) === "sqlite") {
|
|
1932
|
-
const updateSet = (0, sql_template_tag_1.join)(updateColumns.map((col) => (0, sql_template_tag_1.raw)(`${col} = excluded.${col}`)), ", ");
|
|
1933
|
-
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}`;
|
|
1934
|
-
}
|
|
1935
|
-
throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
|
|
985
|
+
return { affected };
|
|
986
|
+
});
|
|
1936
987
|
}
|
|
1937
988
|
async delete(entity, criteria) {
|
|
1938
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
989
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1939
990
|
if (!metadata) {
|
|
1940
991
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1941
992
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
await transactionManager.connect();
|
|
1945
|
-
await transactionManager.startTransaction();
|
|
1946
|
-
if (this.isMySqlFamily()) {
|
|
1947
|
-
await transactionManager.query("SET autocommit = 0");
|
|
1948
|
-
}
|
|
1949
|
-
await this.runHooks(entity, criteria, "beforeDelete");
|
|
993
|
+
return this.executeInTransaction(async (session) => {
|
|
994
|
+
await this.cascadeHandler.runHooks(entity, criteria, "beforeDelete");
|
|
1950
995
|
await this.eventEmitter.emit("beforeDelete", { entity, data: criteria });
|
|
1951
996
|
await this.notifySubscribers(entity, "beforeDelete", {
|
|
1952
997
|
entityClass: entity,
|
|
1953
998
|
criteria,
|
|
1954
999
|
manager: this,
|
|
1955
1000
|
});
|
|
1956
|
-
await this.cascadeDeleteOneToMany(entity, criteria);
|
|
1001
|
+
await this.cascadeHandler.cascadeDeleteOneToMany(entity, criteria);
|
|
1957
1002
|
const whereMap = [];
|
|
1958
1003
|
for (const key in criteria) {
|
|
1959
1004
|
const value = criteria[key];
|
|
@@ -1968,9 +1013,8 @@ class EntityManager {
|
|
|
1968
1013
|
const deleteQuery = (0, sql_template_tag_1.default) `DELETE FROM ${(0, sql_template_tag_1.raw)(this.wrap(metadata.name))} WHERE ${whereSql}`;
|
|
1969
1014
|
const deleteStart = Date.now();
|
|
1970
1015
|
this.beginTrackQuery();
|
|
1971
|
-
const queryResult = (await
|
|
1016
|
+
const queryResult = (await session.query(deleteQuery));
|
|
1972
1017
|
this.trackQuery(entity.name, deleteQuery.text ?? String(deleteQuery), Date.now() - deleteStart);
|
|
1973
|
-
await transactionManager.commit();
|
|
1974
1018
|
let affected = 0;
|
|
1975
1019
|
if (this.isMySqlFamily()) {
|
|
1976
1020
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -1978,7 +1022,7 @@ class EntityManager {
|
|
|
1978
1022
|
else {
|
|
1979
1023
|
affected = queryResult?.rowCount ?? 0;
|
|
1980
1024
|
}
|
|
1981
|
-
await this.runHooks(entity, criteria, "afterDelete");
|
|
1025
|
+
await this.cascadeHandler.runHooks(entity, criteria, "afterDelete");
|
|
1982
1026
|
await this.eventEmitter.emit("afterDelete", { entity, data: criteria });
|
|
1983
1027
|
await this.notifySubscribers(entity, "afterDelete", {
|
|
1984
1028
|
entityClass: entity,
|
|
@@ -1986,27 +1030,36 @@ class EntityManager {
|
|
|
1986
1030
|
manager: this,
|
|
1987
1031
|
});
|
|
1988
1032
|
return { affected };
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async deleteMany(entity, ids) {
|
|
1036
|
+
if (ids.length === 0) {
|
|
1037
|
+
return { affected: 0 };
|
|
1989
1038
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
}
|
|
1994
|
-
catch (rollbackError) {
|
|
1995
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
1996
|
-
}
|
|
1997
|
-
throw e;
|
|
1039
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
1040
|
+
if (!metadata) {
|
|
1041
|
+
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
1998
1042
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
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;
|
|
2002
1054
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
1055
|
+
else {
|
|
1056
|
+
affected = queryResult?.rowCount ?? 0;
|
|
2005
1057
|
}
|
|
2006
|
-
|
|
1058
|
+
return { affected };
|
|
1059
|
+
});
|
|
2007
1060
|
}
|
|
2008
1061
|
async clear(entity) {
|
|
2009
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1062
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2010
1063
|
if (!metadata) {
|
|
2011
1064
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2012
1065
|
}
|
|
@@ -2016,21 +1069,15 @@ class EntityManager {
|
|
|
2016
1069
|
await this.driver.clear(metadata.name);
|
|
2017
1070
|
}
|
|
2018
1071
|
async softDelete(entity, criteria) {
|
|
2019
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1072
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2020
1073
|
if (!metadata) {
|
|
2021
1074
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2022
1075
|
}
|
|
2023
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1076
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
2024
1077
|
if (!deletedAtColumn) {
|
|
2025
1078
|
throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Use delete() instead.`);
|
|
2026
1079
|
}
|
|
2027
|
-
|
|
2028
|
-
try {
|
|
2029
|
-
await transactionManager.connect();
|
|
2030
|
-
await transactionManager.startTransaction();
|
|
2031
|
-
if (this.isMySqlFamily()) {
|
|
2032
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2033
|
-
}
|
|
1080
|
+
return this.executeInTransaction(async (session) => {
|
|
2034
1081
|
const whereMap = [];
|
|
2035
1082
|
for (const key in criteria) {
|
|
2036
1083
|
const value = criteria[key];
|
|
@@ -2044,8 +1091,7 @@ class EntityManager {
|
|
|
2044
1091
|
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2045
1092
|
const nowExpr = this.isPostgres() ? (0, sql_template_tag_1.raw)("NOW()") : (0, sql_template_tag_1.raw)("NOW()");
|
|
2046
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}`;
|
|
2047
|
-
const queryResult = (await
|
|
2048
|
-
await transactionManager.commit();
|
|
1094
|
+
const queryResult = (await session.query(updateQuery));
|
|
2049
1095
|
let affected = 0;
|
|
2050
1096
|
if (this.isMySqlFamily()) {
|
|
2051
1097
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -2054,41 +1100,18 @@ class EntityManager {
|
|
|
2054
1100
|
affected = queryResult?.rowCount ?? 0;
|
|
2055
1101
|
}
|
|
2056
1102
|
return { affected };
|
|
2057
|
-
}
|
|
2058
|
-
catch (e) {
|
|
2059
|
-
try {
|
|
2060
|
-
await transactionManager.rollback();
|
|
2061
|
-
}
|
|
2062
|
-
catch (rollbackError) {
|
|
2063
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
2064
|
-
}
|
|
2065
|
-
throw e;
|
|
2066
|
-
}
|
|
2067
|
-
finally {
|
|
2068
|
-
try {
|
|
2069
|
-
await transactionManager.close();
|
|
2070
|
-
}
|
|
2071
|
-
catch (closeError) {
|
|
2072
|
-
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
1103
|
+
});
|
|
2075
1104
|
}
|
|
2076
1105
|
async restore(entity, criteria) {
|
|
2077
|
-
const metadata = this.resolveEntityMetadata(entity);
|
|
1106
|
+
const metadata = this.resolver.resolveEntityMetadata(entity);
|
|
2078
1107
|
if (!metadata) {
|
|
2079
1108
|
throw new EntityMetadataNotFoundError_1.EntityMetadataNotFoundError(entity.name);
|
|
2080
1109
|
}
|
|
2081
|
-
const deletedAtColumn = this.getDeletedAtColumn(entity);
|
|
1110
|
+
const deletedAtColumn = this.resolver.getDeletedAtColumn(entity);
|
|
2082
1111
|
if (!deletedAtColumn) {
|
|
2083
1112
|
throw new InvalidQueryError_1.InvalidQueryError(`Entity "${entity.name}" does not have a @DeletedAt column. Cannot restore.`);
|
|
2084
1113
|
}
|
|
2085
|
-
|
|
2086
|
-
try {
|
|
2087
|
-
await transactionManager.connect();
|
|
2088
|
-
await transactionManager.startTransaction();
|
|
2089
|
-
if (this.isMySqlFamily()) {
|
|
2090
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2091
|
-
}
|
|
1114
|
+
return this.executeInTransaction(async (session) => {
|
|
2092
1115
|
const whereMap = [];
|
|
2093
1116
|
for (const key in criteria) {
|
|
2094
1117
|
const value = criteria[key];
|
|
@@ -2101,8 +1124,7 @@ class EntityManager {
|
|
|
2101
1124
|
}
|
|
2102
1125
|
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2103
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}`;
|
|
2104
|
-
const queryResult = (await
|
|
2105
|
-
await transactionManager.commit();
|
|
1127
|
+
const queryResult = (await session.query(restoreQuery));
|
|
2106
1128
|
let affected = 0;
|
|
2107
1129
|
if (this.isMySqlFamily()) {
|
|
2108
1130
|
affected = queryResult?.results?.affectedRows ?? 0;
|
|
@@ -2111,138 +1133,167 @@ class EntityManager {
|
|
|
2111
1133
|
affected = queryResult?.rowCount ?? 0;
|
|
2112
1134
|
}
|
|
2113
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);
|
|
2114
1142
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
await transactionManager.rollback();
|
|
2118
|
-
}
|
|
2119
|
-
catch (rollbackError) {
|
|
2120
|
-
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
2121
|
-
}
|
|
2122
|
-
throw e;
|
|
1143
|
+
if (!this.driver) {
|
|
1144
|
+
throw new Error("Driver is not initialized.");
|
|
2123
1145
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
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;
|
|
2130
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;
|
|
2131
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
|
+
});
|
|
2132
1180
|
}
|
|
2133
|
-
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
await this.save(RelatedEntity, child);
|
|
2149
|
-
}
|
|
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}`;
|
|
2150
1196
|
}
|
|
1197
|
+
throw new Error(`Unsupported database type for upsert: ${this.dbType}`);
|
|
2151
1198
|
}
|
|
2152
|
-
async
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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, '""')}"`;
|
|
2170
1223
|
}
|
|
1224
|
+
return `\`${columnName.replace(/`/g, "``")}\``;
|
|
2171
1225
|
}
|
|
2172
|
-
|
|
2173
|
-
const
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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.`);
|
|
2201
1271
|
}
|
|
2202
1272
|
}
|
|
2203
|
-
async
|
|
2204
|
-
const
|
|
2205
|
-
if (
|
|
2206
|
-
|
|
1273
|
+
async executeInTransaction(fn, existingSession, readNodeOverride) {
|
|
1274
|
+
const reusable = existingSession ?? Transactional_1.transactionStorage.getStore();
|
|
1275
|
+
if (reusable) {
|
|
1276
|
+
return fn(reusable);
|
|
2207
1277
|
}
|
|
2208
|
-
const
|
|
1278
|
+
const session = new TransactionSessionManager_1.TransactionSessionManager();
|
|
2209
1279
|
try {
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
if (this.isMySqlFamily()) {
|
|
2213
|
-
await transactionManager.query("SET autocommit = 0");
|
|
2214
|
-
}
|
|
2215
|
-
const tableName = metadata.name;
|
|
2216
|
-
const selectExpr = (0, sql_template_tag_1.raw)(`${fn}(${field === "*" ? "*" : this.wrap(field)})`);
|
|
2217
|
-
const whereMap = [];
|
|
2218
|
-
if (where) {
|
|
2219
|
-
for (const key in where) {
|
|
2220
|
-
const value = where[key];
|
|
2221
|
-
if (value !== undefined && value !== null) {
|
|
2222
|
-
whereMap.push(Conditions_1.Conditions.equals(this.wrap(key), value));
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
let queryStr;
|
|
2227
|
-
if (whereMap.length > 0) {
|
|
2228
|
-
const whereSql = (0, sql_template_tag_1.join)(whereMap, " AND ");
|
|
2229
|
-
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);
|
|
2230
1282
|
}
|
|
2231
1283
|
else {
|
|
2232
|
-
|
|
1284
|
+
await session.connect();
|
|
2233
1285
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
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;
|
|
2242
1293
|
}
|
|
2243
1294
|
catch (e) {
|
|
2244
1295
|
try {
|
|
2245
|
-
await
|
|
1296
|
+
await session.rollback();
|
|
2246
1297
|
}
|
|
2247
1298
|
catch (rollbackError) {
|
|
2248
1299
|
this.logger.error(`Failed to rollback transaction: ${rollbackError}`);
|
|
@@ -2251,43 +1302,27 @@ class EntityManager {
|
|
|
2251
1302
|
}
|
|
2252
1303
|
finally {
|
|
2253
1304
|
try {
|
|
2254
|
-
await
|
|
1305
|
+
await session.close();
|
|
2255
1306
|
}
|
|
2256
1307
|
catch (closeError) {
|
|
2257
1308
|
this.logger.error(`Failed to close transaction: ${closeError}`);
|
|
2258
1309
|
}
|
|
2259
1310
|
}
|
|
2260
1311
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
return this.aggregate(entity, "SUM", field, where);
|
|
2273
|
-
}
|
|
2274
|
-
async avg(entity, field, where) {
|
|
2275
|
-
return this.aggregate(entity, "AVG", field, where);
|
|
2276
|
-
}
|
|
2277
|
-
async min(entity, field, where) {
|
|
2278
|
-
return this.aggregate(entity, "MIN", field, where);
|
|
2279
|
-
}
|
|
2280
|
-
async max(entity, field, where) {
|
|
2281
|
-
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;
|
|
2282
1323
|
}
|
|
2283
1324
|
async query(sqlQuery, params) {
|
|
2284
|
-
|
|
2285
|
-
try {
|
|
2286
|
-
await transactionHolder.connect();
|
|
2287
|
-
await transactionHolder.startTransaction();
|
|
2288
|
-
if (this.isMySqlFamily()) {
|
|
2289
|
-
await transactionHolder.query("SET autocommit = 0");
|
|
2290
|
-
}
|
|
1325
|
+
return this.executeInTransaction(async (session) => {
|
|
2291
1326
|
let queryResult;
|
|
2292
1327
|
if (typeof sqlQuery === "string") {
|
|
2293
1328
|
if (params && params.length > 0) {
|
|
@@ -2297,16 +1332,15 @@ class EntityManager {
|
|
|
2297
1332
|
values: params,
|
|
2298
1333
|
strings: [sqlQuery],
|
|
2299
1334
|
};
|
|
2300
|
-
queryResult = await
|
|
1335
|
+
queryResult = await session.query(parameterizedSql);
|
|
2301
1336
|
}
|
|
2302
1337
|
else {
|
|
2303
|
-
queryResult = await
|
|
1338
|
+
queryResult = await session.query(sqlQuery);
|
|
2304
1339
|
}
|
|
2305
1340
|
}
|
|
2306
1341
|
else {
|
|
2307
|
-
queryResult = await
|
|
1342
|
+
queryResult = await session.query(sqlQuery);
|
|
2308
1343
|
}
|
|
2309
|
-
await transactionHolder.commit();
|
|
2310
1344
|
if (queryResult?.results) {
|
|
2311
1345
|
return queryResult.results ?? [];
|
|
2312
1346
|
}
|
|
@@ -2314,24 +1348,7 @@ class EntityManager {
|
|
|
2314
1348
|
return queryResult;
|
|
2315
1349
|
}
|
|
2316
1350
|
return [];
|
|
2317
|
-
}
|
|
2318
|
-
catch (e) {
|
|
2319
|
-
try {
|
|
2320
|
-
await transactionHolder.rollback();
|
|
2321
|
-
}
|
|
2322
|
-
catch (rollbackError) {
|
|
2323
|
-
this.logger.error(`Failed to rollback raw query transaction: ${rollbackError}`);
|
|
2324
|
-
}
|
|
2325
|
-
throw e;
|
|
2326
|
-
}
|
|
2327
|
-
finally {
|
|
2328
|
-
try {
|
|
2329
|
-
await transactionHolder.close();
|
|
2330
|
-
}
|
|
2331
|
-
catch (closeError) {
|
|
2332
|
-
this.logger.error(`Failed to close raw query transaction: ${closeError}`);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
1351
|
+
});
|
|
2335
1352
|
}
|
|
2336
1353
|
getRepository(entity) {
|
|
2337
1354
|
return BaseRepository_1.BaseRepository.of(entity, this);
|