@mikro-orm/sql 7.1.0-dev.5 → 7.1.0-dev.50
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/AbstractSqlConnection.d.ts +1 -1
- package/AbstractSqlConnection.js +27 -6
- package/AbstractSqlDriver.d.ts +26 -1
- package/AbstractSqlDriver.js +294 -37
- package/AbstractSqlPlatform.d.ts +15 -3
- package/AbstractSqlPlatform.js +25 -7
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +19 -3
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +48 -5
- package/SqlEntityManager.js +77 -7
- package/SqlMikroORM.d.ts +23 -0
- package/SqlMikroORM.js +23 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +4 -5
- package/dialects/mysql/BaseMySqlPlatform.js +9 -10
- package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
- package/dialects/mysql/MySqlSchemaHelper.js +280 -49
- package/dialects/oracledb/OracleDialect.d.ts +1 -1
- package/dialects/oracledb/OracleDialect.js +2 -1
- package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
- package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +11 -5
- package/dialects/postgresql/BasePostgreSqlPlatform.js +75 -17
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +362 -28
- package/dialects/postgresql/index.d.ts +2 -0
- package/dialects/postgresql/index.js +2 -0
- package/dialects/postgresql/typeOverrides.d.ts +14 -0
- package/dialects/postgresql/typeOverrides.js +12 -0
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -1
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +4 -4
- package/plugin/transformer.d.ts +11 -3
- package/plugin/transformer.js +138 -29
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +2 -2
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +42 -1
- package/query/QueryBuilder.js +78 -7
- package/schema/DatabaseSchema.d.ts +29 -2
- package/schema/DatabaseSchema.js +145 -4
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +182 -31
- package/schema/SchemaComparator.d.ts +19 -0
- package/schema/SchemaComparator.js +250 -1
- package/schema/SchemaHelper.d.ts +77 -1
- package/schema/SchemaHelper.js +297 -25
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +47 -10
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +72 -5
package/AbstractSqlDriver.js
CHANGED
|
@@ -3,6 +3,28 @@ import { QueryBuilder } from './query/QueryBuilder.js';
|
|
|
3
3
|
import { JoinType, QueryType } from './query/enums.js';
|
|
4
4
|
import { SqlEntityManager } from './SqlEntityManager.js';
|
|
5
5
|
import { PivotCollectionPersister } from './PivotCollectionPersister.js';
|
|
6
|
+
/** Extracts cancellation controls from any options bag that extends `AbortQueryOptions`. */
|
|
7
|
+
function pickAbortOptions(options) {
|
|
8
|
+
if (!options || (options.signal == null && options.inflightQueryAbortStrategy == null)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
signal: options.signal,
|
|
13
|
+
inflightQueryAbortStrategy: options.inflightQueryAbortStrategy,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns a `loggerContext` payload that carries the abort fields alongside any existing
|
|
18
|
+
* context. The connection layer strips them before logging — this avoids widening the public
|
|
19
|
+
* `Connection.execute()` signature.
|
|
20
|
+
*/
|
|
21
|
+
function withAbortContext(loggerContext, options) {
|
|
22
|
+
const abort = pickAbortOptions(options);
|
|
23
|
+
if (!abort) {
|
|
24
|
+
return loggerContext;
|
|
25
|
+
}
|
|
26
|
+
return { ...loggerContext, ...abort };
|
|
27
|
+
}
|
|
6
28
|
/** Base class for SQL database drivers, implementing find/insert/update/delete using QueryBuilder. */
|
|
7
29
|
export class AbstractSqlDriver extends DatabaseDriver {
|
|
8
30
|
[EntityManagerType];
|
|
@@ -34,6 +56,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
34
56
|
return { alias, name: meta.tableName, schema: effectiveSchema, qualifiedName, toString: () => alias };
|
|
35
57
|
}
|
|
36
58
|
validateSqlOptions(options) {
|
|
59
|
+
if (options.using && !options.indexHint) {
|
|
60
|
+
const names = Utils.asArray(options.using);
|
|
61
|
+
const hint = this.platform.formatIndexHint(names);
|
|
62
|
+
if (hint) {
|
|
63
|
+
options.indexHint = hint;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
37
66
|
if (options.collation != null && typeof options.collation !== 'string') {
|
|
38
67
|
throw new Error('Collation option for SQL drivers must be a string (collation name). Use a CollationOptions object only with MongoDB.');
|
|
39
68
|
}
|
|
@@ -50,7 +79,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
50
79
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
51
80
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
52
81
|
const schema = this.getSchemaName(meta, options);
|
|
53
|
-
const qb = this.createQueryBuilder(meta.class, options.ctx, connectionType, false, options.logging, undefined, options.em)
|
|
82
|
+
const qb = this.createQueryBuilder(meta.class, options.ctx, connectionType, false, options.logging, undefined, options.em)
|
|
83
|
+
.withSchema(schema)
|
|
84
|
+
.cache(false);
|
|
85
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
54
86
|
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
55
87
|
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
56
88
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
@@ -88,6 +120,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
88
120
|
if (options.em) {
|
|
89
121
|
await qb.applyJoinedFilters(options.em, options.filters);
|
|
90
122
|
}
|
|
123
|
+
if (options._partitionLimit) {
|
|
124
|
+
qb.setPartitionLimit(options._partitionLimit);
|
|
125
|
+
}
|
|
91
126
|
return qb;
|
|
92
127
|
}
|
|
93
128
|
async find(entityName, where, options = {}) {
|
|
@@ -204,7 +239,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
204
239
|
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
205
240
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
206
241
|
const query = native.compile();
|
|
207
|
-
const res = await this.execute(query.sql, query.params, 'all', options.ctx);
|
|
242
|
+
const res = await this.execute(query.sql, query.params, 'all', options.ctx, withAbortContext(options.loggerContext, options));
|
|
208
243
|
if (type === QueryType.COUNT) {
|
|
209
244
|
return res[0].count;
|
|
210
245
|
}
|
|
@@ -221,7 +256,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
221
256
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
222
257
|
const query = native.compile();
|
|
223
258
|
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
224
|
-
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, options.loggerContext);
|
|
259
|
+
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, withAbortContext(options.loggerContext, options), options.chunkSize);
|
|
225
260
|
for await (const row of res) {
|
|
226
261
|
yield this.mapResult(row, meta);
|
|
227
262
|
}
|
|
@@ -529,6 +564,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
529
564
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
530
565
|
const schema = this.getSchemaName(meta, options);
|
|
531
566
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
567
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
532
568
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
533
569
|
if (meta && !Utils.isEmpty(populate)) {
|
|
534
570
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
@@ -554,6 +590,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
554
590
|
const meta = this.metadata.get(entityName);
|
|
555
591
|
const collections = this.extractManyToMany(meta, data);
|
|
556
592
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
593
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
557
594
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
558
595
|
res.row = res.row || {};
|
|
559
596
|
let pk;
|
|
@@ -586,9 +623,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
586
623
|
const props = this.getCloneableProps(meta);
|
|
587
624
|
const mappedOverrides = this.mapCloneOverrides(overrides, meta, options);
|
|
588
625
|
const { selectFields, insertColumns } = this.buildCloneFields(props, mappedOverrides, meta);
|
|
626
|
+
const abort = pickAbortOptions(options);
|
|
589
627
|
const selectQb = this.createQueryBuilder(meta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
590
628
|
selectQb.select(selectFields).where(where);
|
|
629
|
+
selectQb.setAbortOptions(abort);
|
|
591
630
|
const insertQb = this.createQueryBuilder(meta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
631
|
+
insertQb.setAbortOptions(abort);
|
|
592
632
|
return this.rethrow(insertQb
|
|
593
633
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
594
634
|
.execute('run', false));
|
|
@@ -618,9 +658,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
618
658
|
}
|
|
619
659
|
}
|
|
620
660
|
const sourceWhere = tableMeta === rootMeta ? where : (Utils.extractPK(where, tableMeta) ?? where);
|
|
661
|
+
const abort = pickAbortOptions(options);
|
|
621
662
|
const selectQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
622
663
|
selectQb.select(selectFields).where(sourceWhere);
|
|
664
|
+
selectQb.setAbortOptions(abort);
|
|
623
665
|
const insertQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
666
|
+
insertQb.setAbortOptions(abort);
|
|
624
667
|
const res = await this.rethrow(insertQb
|
|
625
668
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
626
669
|
.execute('run', false));
|
|
@@ -795,13 +838,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
795
838
|
else {
|
|
796
839
|
const field = prop.fieldNames[0];
|
|
797
840
|
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
841
|
+
const rowValue = row[prop.name];
|
|
842
|
+
const rowValueIsRaw = isRaw(rowValue);
|
|
798
843
|
if (prop.customType &&
|
|
799
844
|
!prop.object &&
|
|
800
845
|
'convertToDatabaseValueSQL' in prop.customType &&
|
|
801
|
-
|
|
802
|
-
!
|
|
846
|
+
rowValue != null &&
|
|
847
|
+
!rowValueIsRaw) {
|
|
803
848
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
804
849
|
}
|
|
850
|
+
else if (rowValueIsRaw && /^\s*(?:with|select)\b/i.test(rowValue.sql)) {
|
|
851
|
+
// raw subqueries must be parenthesized when inlined as a VALUES position
|
|
852
|
+
keys.push('(?)');
|
|
853
|
+
}
|
|
805
854
|
else {
|
|
806
855
|
keys.push('?');
|
|
807
856
|
}
|
|
@@ -828,7 +877,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
828
877
|
if (transform) {
|
|
829
878
|
sql = transform(sql);
|
|
830
879
|
}
|
|
831
|
-
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
880
|
+
const res = await this.execute(sql, params, 'run', options.ctx, withAbortContext(options.loggerContext, options));
|
|
832
881
|
let pk;
|
|
833
882
|
/* v8 ignore next */
|
|
834
883
|
if (pks.length > 1) {
|
|
@@ -861,6 +910,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
861
910
|
}
|
|
862
911
|
if (Utils.hasObjectKeys(data)) {
|
|
863
912
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
913
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
864
914
|
if (options.upsert) {
|
|
865
915
|
/* v8 ignore next */
|
|
866
916
|
const uniqueFields = options.onConflictFields ??
|
|
@@ -906,6 +956,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
906
956
|
? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
|
|
907
957
|
: meta.primaryKeys);
|
|
908
958
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
959
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
909
960
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
910
961
|
qb.insert(data)
|
|
911
962
|
.onConflict(uniqueFields)
|
|
@@ -1051,7 +1102,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1051
1102
|
if (transform) {
|
|
1052
1103
|
sql = transform(sql, params);
|
|
1053
1104
|
}
|
|
1054
|
-
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
1105
|
+
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, withAbortContext(options.loggerContext, options)));
|
|
1055
1106
|
for (let i = 0; i < collections.length; i++) {
|
|
1056
1107
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
1057
1108
|
}
|
|
@@ -1069,6 +1120,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1069
1120
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext)
|
|
1070
1121
|
.delete(where)
|
|
1071
1122
|
.withSchema(this.getSchemaName(meta, options));
|
|
1123
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1072
1124
|
return this.rethrow(qb.execute('run', false));
|
|
1073
1125
|
}
|
|
1074
1126
|
/**
|
|
@@ -1099,8 +1151,29 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1099
1151
|
const pks = wrapped.getPrimaryKeys(true);
|
|
1100
1152
|
const snap = coll.getSnapshot();
|
|
1101
1153
|
const includes = (arr, item) => !!arr.find(i => this.comparePrimaryKeyArrays(i, item));
|
|
1102
|
-
|
|
1103
|
-
|
|
1154
|
+
// For union-target polymorphic M:N, prepend the per-row discriminator value so the pivot
|
|
1155
|
+
// persister can write it alongside the FK id. Memoized per sync-run because a collection can
|
|
1156
|
+
// hold hundreds of items of the same few types, and findDiscriminatorValue walks the prototype
|
|
1157
|
+
// chain + re-scans Object.entries each call.
|
|
1158
|
+
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(coll.property);
|
|
1159
|
+
const classToDisc = new Map();
|
|
1160
|
+
const toDiff = (item) => {
|
|
1161
|
+
const keys = helper(item).getPrimaryKeys(true);
|
|
1162
|
+
if (!isUnionTargetMN) {
|
|
1163
|
+
return keys;
|
|
1164
|
+
}
|
|
1165
|
+
let disc = classToDisc.get(item.constructor);
|
|
1166
|
+
if (!classToDisc.has(item.constructor)) {
|
|
1167
|
+
disc = QueryHelper.findDiscriminatorValue(coll.property.discriminatorMap, item.constructor);
|
|
1168
|
+
if (disc === undefined) {
|
|
1169
|
+
throw new Error(`Cannot resolve discriminator value for ${item.constructor.name} in ${coll.property.name}; the class is not part of the union target list.`);
|
|
1170
|
+
}
|
|
1171
|
+
classToDisc.set(item.constructor, disc);
|
|
1172
|
+
}
|
|
1173
|
+
return [disc, ...keys];
|
|
1174
|
+
};
|
|
1175
|
+
const snapshot = snap ? snap.map(toDiff) : [];
|
|
1176
|
+
const current = coll.getItems(false).map(toDiff);
|
|
1104
1177
|
const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
|
|
1105
1178
|
const insertDiff = current.filter(item => !includes(snapshot, item));
|
|
1106
1179
|
const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
|
|
@@ -1118,6 +1191,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1118
1191
|
if (coll.property.kind === ReferenceKind.ONE_TO_MANY) {
|
|
1119
1192
|
const cols = coll.property.referencedColumnNames;
|
|
1120
1193
|
const qb = this.createQueryBuilder(coll.property.targetMeta.class, options?.ctx, 'write').withSchema(this.getSchemaName(meta, options));
|
|
1194
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1121
1195
|
if (coll.getSnapshot() === undefined) {
|
|
1122
1196
|
if (coll.property.orphanRemoval) {
|
|
1123
1197
|
const query = qb
|
|
@@ -1159,7 +1233,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1159
1233
|
schema = this.config.get('schema');
|
|
1160
1234
|
}
|
|
1161
1235
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
1162
|
-
const persister = (groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext));
|
|
1236
|
+
const persister = (groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext, pickAbortOptions(options)));
|
|
1163
1237
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks, coll.isInitialized());
|
|
1164
1238
|
}
|
|
1165
1239
|
for (const persister of Utils.values(groups)) {
|
|
@@ -1172,21 +1246,16 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1172
1246
|
return {};
|
|
1173
1247
|
}
|
|
1174
1248
|
const pivotMeta = this.metadata.get(prop.pivotEntity);
|
|
1249
|
+
if (prop.discriminatorColumn && QueryHelper.isUnionTargetPolymorphic(prop)) {
|
|
1250
|
+
return this.loadFromUnionTargetPolymorphicPivotTable(prop, owners, where, orderBy, ctx, options, pivotJoin);
|
|
1251
|
+
}
|
|
1175
1252
|
if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
|
|
1176
1253
|
return this.loadFromPolymorphicPivotTable(prop, owners, where, orderBy, ctx, options, pivotJoin);
|
|
1177
1254
|
}
|
|
1178
1255
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
1179
1256
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
1180
1257
|
const ownerMeta = pivotProp2.targetMeta;
|
|
1181
|
-
|
|
1182
|
-
// convert owner PKs to DB format for the query and convert result FKs back to
|
|
1183
|
-
// JS format for consistent key hashing in buildPivotResultMap.
|
|
1184
|
-
const pkProp = ownerMeta.properties[ownerMeta.primaryKeys[0]];
|
|
1185
|
-
const needsConversion = pkProp?.customType?.ensureComparable(ownerMeta, pkProp) && !ownerMeta.compositePK;
|
|
1186
|
-
let ownerPks = ownerMeta.compositePK ? owners : owners.map(o => o[0]);
|
|
1187
|
-
if (needsConversion) {
|
|
1188
|
-
ownerPks = ownerPks.map(v => pkProp.customType.convertToDatabaseValue(v, this.platform, { mode: 'query' }));
|
|
1189
|
-
}
|
|
1258
|
+
const { ownerPks, needsConversion, pkProp } = this.convertOwnerPksForPivotQuery(owners, ownerMeta);
|
|
1190
1259
|
const cond = {
|
|
1191
1260
|
[pivotProp2.name]: { $in: ownerPks },
|
|
1192
1261
|
};
|
|
@@ -1201,7 +1270,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1201
1270
|
const fields = pivotJoin
|
|
1202
1271
|
? [pivotProp1.name, pivotProp2.name]
|
|
1203
1272
|
: [pivotProp1.name, pivotProp2.name, ...childFields];
|
|
1204
|
-
const
|
|
1273
|
+
const pivotFindOptions = {
|
|
1205
1274
|
ctx,
|
|
1206
1275
|
...options,
|
|
1207
1276
|
fields,
|
|
@@ -1217,10 +1286,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1217
1286
|
},
|
|
1218
1287
|
],
|
|
1219
1288
|
populateWhere: undefined,
|
|
1220
|
-
// @ts-ignore
|
|
1221
1289
|
_populateWhere: 'infer',
|
|
1222
1290
|
populateFilter: this.wrapPopulateFilter(options, pivotProp2.name),
|
|
1223
|
-
}
|
|
1291
|
+
};
|
|
1292
|
+
if (pivotFindOptions._partitionLimit) {
|
|
1293
|
+
pivotFindOptions._partitionLimit.partitionBy = pivotProp2.name;
|
|
1294
|
+
}
|
|
1295
|
+
const res = await this.find(pivotMeta.class, where, pivotFindOptions);
|
|
1224
1296
|
// Convert result FK values back to JS format so key hashing
|
|
1225
1297
|
// in buildPivotResultMap is consistent with the owner keys.
|
|
1226
1298
|
if (needsConversion) {
|
|
@@ -1340,6 +1412,102 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1340
1412
|
});
|
|
1341
1413
|
return this.buildPivotResultMap(owners, res, tagProp.name, ownerRelationName);
|
|
1342
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Load a union-target polymorphic M:N pivot (e.g. Post.attachments -> Image | Video).
|
|
1417
|
+
* Each pivot row's discriminator column selects which target table to hydrate.
|
|
1418
|
+
*/
|
|
1419
|
+
async loadFromUnionTargetPolymorphicPivotTable(prop, owners, where = {}, orderBy, ctx, options = {}, pivotJoin) {
|
|
1420
|
+
// :ref hints cannot be honored for union-target — EntityLoader.getReference needs a concrete
|
|
1421
|
+
// class per item, but the ref-mode map only carries flat PK values and would hydrate every
|
|
1422
|
+
// row as the first polymorph target. Fail loudly instead of silently corrupting the collection.
|
|
1423
|
+
if (pivotJoin) {
|
|
1424
|
+
throw new Error(`The ':ref' populate hint is not supported for union-target polymorphic M:N on ${prop.name}. Use a regular populate hint instead.`);
|
|
1425
|
+
}
|
|
1426
|
+
const pivotMeta = this.metadata.get(prop.pivotEntity);
|
|
1427
|
+
const targets = prop.polymorphTargets;
|
|
1428
|
+
const ownerProp = pivotMeta.relations.find(r => r.persist !== false && !r.polymorphic);
|
|
1429
|
+
const discriminatorColumn = prop.discriminatorColumn;
|
|
1430
|
+
const ownerMeta = ownerProp.targetMeta;
|
|
1431
|
+
const { ownerPks, needsConversion, pkProp } = this.convertOwnerPksForPivotQuery(owners, ownerMeta);
|
|
1432
|
+
const pivotRows = (await this.find(pivotMeta.class, { [ownerProp.name]: { $in: ownerPks } }, {
|
|
1433
|
+
ctx,
|
|
1434
|
+
orderBy: this.getPivotOrderBy(prop, ownerProp, orderBy, options?.orderBy),
|
|
1435
|
+
fields: [ownerProp.name, discriminatorColumn, prop.discriminator],
|
|
1436
|
+
populateWhere: undefined,
|
|
1437
|
+
// @ts-ignore
|
|
1438
|
+
_populateWhere: 'infer',
|
|
1439
|
+
}));
|
|
1440
|
+
/* v8 ignore next 7 - custom-type PK conversion, tested via loadFromPivotTable path */
|
|
1441
|
+
if (needsConversion) {
|
|
1442
|
+
for (const item of pivotRows) {
|
|
1443
|
+
const fk = item[ownerProp.name];
|
|
1444
|
+
if (fk != null) {
|
|
1445
|
+
item[ownerProp.name] = pkProp.customType.convertToJSValue(fk, this.platform);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
const classMeta = new Map(targets.map(t => [t.class, t]));
|
|
1450
|
+
const rowsByTarget = new Map();
|
|
1451
|
+
for (const row of pivotRows) {
|
|
1452
|
+
const discValue = row[discriminatorColumn];
|
|
1453
|
+
const targetClass = prop.discriminatorMap[discValue];
|
|
1454
|
+
const targetMeta = classMeta.get(targetClass);
|
|
1455
|
+
/* v8 ignore next 3 - defensive: unknown discriminator value */
|
|
1456
|
+
if (!targetMeta) {
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const list = rowsByTarget.get(targetMeta) ?? [];
|
|
1460
|
+
list.push(row);
|
|
1461
|
+
rowsByTarget.set(targetMeta, list);
|
|
1462
|
+
}
|
|
1463
|
+
// Strip the outer find's orderBy/fields/exclude before bulk-loading targets by PK — those apply
|
|
1464
|
+
// to the owner query, not each polymorph target (Image and Video wouldn't share an orderBy field).
|
|
1465
|
+
// populateFilter is a filter on the populated collection; since union-target splits the pivot
|
|
1466
|
+
// and target queries, we merge it into the target-level `where` instead of wrapping it on the
|
|
1467
|
+
// pivot query (where joins to target tables aren't available).
|
|
1468
|
+
// Hoisted above the loop since `options` doesn't change per target.
|
|
1469
|
+
const { orderBy: _o, fields: _f, exclude: _e, populateFilter, ...childOptions } = options;
|
|
1470
|
+
const populate = options.populate ?? [];
|
|
1471
|
+
const orphanedRows = new Set();
|
|
1472
|
+
for (const [targetMeta, rows] of rowsByTarget) {
|
|
1473
|
+
const targetIds = rows.map(r => r[prop.discriminator]);
|
|
1474
|
+
// Union-target pivot stores one scalar FK per row; composite-PK targets are rejected at
|
|
1475
|
+
// metadata validation time, so a single primary key column is guaranteed here.
|
|
1476
|
+
const pkCol = targetMeta.primaryKeys[0];
|
|
1477
|
+
let cond = { [pkCol]: { $in: targetIds } };
|
|
1478
|
+
if (!Utils.isEmpty(where)) {
|
|
1479
|
+
cond = { $and: [cond, where] };
|
|
1480
|
+
}
|
|
1481
|
+
if (!Utils.isEmpty(populateFilter)) {
|
|
1482
|
+
cond = { $and: [cond, populateFilter] };
|
|
1483
|
+
}
|
|
1484
|
+
const results = (await this.find(targetMeta.class, cond, {
|
|
1485
|
+
ctx,
|
|
1486
|
+
...childOptions,
|
|
1487
|
+
populate: populate,
|
|
1488
|
+
}));
|
|
1489
|
+
const byPk = new Map();
|
|
1490
|
+
for (const row of results) {
|
|
1491
|
+
Object.defineProperty(row, 'constructor', {
|
|
1492
|
+
value: targetMeta.class,
|
|
1493
|
+
enumerable: false,
|
|
1494
|
+
configurable: true,
|
|
1495
|
+
});
|
|
1496
|
+
byPk.set(Utils.getPrimaryKeyHash([row[pkCol]]), row);
|
|
1497
|
+
}
|
|
1498
|
+
for (const row of rows) {
|
|
1499
|
+
const pkHash = Utils.getPrimaryKeyHash([row[prop.discriminator]]);
|
|
1500
|
+
const entity = byPk.get(pkHash);
|
|
1501
|
+
if (entity == null) {
|
|
1502
|
+
orphanedRows.add(row);
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
row[prop.discriminator] = entity;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
const result = orphanedRows.size > 0 ? pivotRows.filter(r => !orphanedRows.has(r)) : pivotRows;
|
|
1509
|
+
return this.buildPivotResultMap(owners, result, ownerProp.name, prop.discriminator);
|
|
1510
|
+
}
|
|
1343
1511
|
/**
|
|
1344
1512
|
* Build a map from owner PKs to their related entities from pivot table results.
|
|
1345
1513
|
*/
|
|
@@ -1364,6 +1532,21 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1364
1532
|
}
|
|
1365
1533
|
return undefined;
|
|
1366
1534
|
}
|
|
1535
|
+
/**
|
|
1536
|
+
* The pivot query builder doesn't convert custom types — manually convert owner PKs to the DB
|
|
1537
|
+
* representation. Returns `needsConversion` + `pkProp` so the caller can convert result FKs back
|
|
1538
|
+
* to JS format for consistent key hashing in `buildPivotResultMap`.
|
|
1539
|
+
*/
|
|
1540
|
+
convertOwnerPksForPivotQuery(owners, ownerMeta) {
|
|
1541
|
+
const pkProp = ownerMeta.properties[ownerMeta.primaryKeys[0]];
|
|
1542
|
+
const needsConversion = !!pkProp?.customType?.ensureComparable(ownerMeta, pkProp) && !ownerMeta.compositePK;
|
|
1543
|
+
let ownerPks = ownerMeta.compositePK ? owners : owners.map(o => o[0]);
|
|
1544
|
+
/* v8 ignore next 4 - custom-type PK conversion, tested via loadFromPivotTable path */
|
|
1545
|
+
if (needsConversion) {
|
|
1546
|
+
ownerPks = ownerPks.map(v => pkProp.customType.convertToDatabaseValue(v, this.platform, { mode: 'query' }));
|
|
1547
|
+
}
|
|
1548
|
+
return { ownerPks, needsConversion, pkProp };
|
|
1549
|
+
}
|
|
1367
1550
|
getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
|
|
1368
1551
|
if (!Utils.isEmpty(orderBy) || RawQueryFragment.hasObjectFragments(orderBy)) {
|
|
1369
1552
|
return Utils.asArray(orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
@@ -1430,6 +1613,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1430
1613
|
if (ref && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
1431
1614
|
return true;
|
|
1432
1615
|
}
|
|
1616
|
+
// Union-target polymorphic M:N cannot be loaded via a single JOIN because rows span multiple
|
|
1617
|
+
// target tables; fall through to SELECT_IN which dispatches through `loadFromPivotTable`.
|
|
1618
|
+
// Polymorphic M:1 (to-one with target_type discriminator) is handled via LEFT JOINs elsewhere.
|
|
1619
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && QueryHelper.isUnionTargetPolymorphic(prop) && prop.owner) {
|
|
1620
|
+
return false;
|
|
1621
|
+
}
|
|
1433
1622
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
|
1434
1623
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
1435
1624
|
return false;
|
|
@@ -1544,6 +1733,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1544
1733
|
const targetPath = `${pathPrefix}${basePath}[${targetMeta.className}]`;
|
|
1545
1734
|
const schema = targetMeta.schema === '*' ? (options?.schema ?? this.config.get('schema')) : targetMeta.schema;
|
|
1546
1735
|
qb.addPolymorphicJoin(prop, targetMeta, options.parentTableAlias, tableAlias, JoinType.leftJoin, targetPath, schema);
|
|
1736
|
+
// For polymorphic targets that are TPT child entities, INNER JOIN parent tables so that
|
|
1737
|
+
// filter conditions referencing parent-table columns resolve to the correct alias. The
|
|
1738
|
+
// INNER JOINs get nested inside the polymorphic LEFT JOIN by processNestedJoins, which
|
|
1739
|
+
// keeps the resulting query valid for rows pointing to other polymorphic targets.
|
|
1740
|
+
if (targetMeta.inheritanceType === 'tpt' && targetMeta.tptParent) {
|
|
1741
|
+
this.addTPTParentJoinsForRelation(qb, targetMeta, tableAlias, targetPath);
|
|
1742
|
+
}
|
|
1547
1743
|
// For polymorphic targets that are TPT base classes, also LEFT JOIN
|
|
1548
1744
|
// all descendant tables so child-specific fields can be selected.
|
|
1549
1745
|
if (targetMeta.inheritanceType === 'tpt' && targetMeta.tptChildren?.length && !ref) {
|
|
@@ -1592,17 +1788,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1592
1788
|
qb.join(field, tableAlias, {}, joinType, path, schema);
|
|
1593
1789
|
// For relations to TPT child entities, INNER JOIN parent tables (GH #7469)
|
|
1594
1790
|
if (meta2.inheritanceType === 'tpt' && meta2.tptParent) {
|
|
1595
|
-
|
|
1596
|
-
let childMeta = meta2;
|
|
1597
|
-
while (childMeta.tptParent) {
|
|
1598
|
-
const parentMeta = childMeta.tptParent;
|
|
1599
|
-
const parentAlias = qb.getNextAlias(parentMeta.className);
|
|
1600
|
-
qb.createAlias(parentMeta.class, parentAlias);
|
|
1601
|
-
qb.state.tptAlias[`${tableAlias}:${parentMeta.className}`] = parentAlias;
|
|
1602
|
-
qb.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${path}.[tpt]${childMeta.className}`);
|
|
1603
|
-
childAlias = parentAlias;
|
|
1604
|
-
childMeta = parentMeta;
|
|
1605
|
-
}
|
|
1791
|
+
this.addTPTParentJoinsForRelation(qb, meta2, tableAlias, path);
|
|
1606
1792
|
}
|
|
1607
1793
|
// For relations to TPT base classes, add LEFT JOINs for all child tables (polymorphic loading)
|
|
1608
1794
|
if (meta2.inheritanceType === 'tpt' && meta2.tptChildren?.length && !ref) {
|
|
@@ -1650,6 +1836,25 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1650
1836
|
}
|
|
1651
1837
|
return fields;
|
|
1652
1838
|
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
|
|
1841
|
+
* Registers the parent aliases in `qb.state.tptAlias` so column resolution finds them
|
|
1842
|
+
* when filter conditions reference parent-table columns.
|
|
1843
|
+
* @internal
|
|
1844
|
+
*/
|
|
1845
|
+
addTPTParentJoinsForRelation(qb, leafMeta, leafAlias, basePath) {
|
|
1846
|
+
let childAlias = leafAlias;
|
|
1847
|
+
let childMeta = leafMeta;
|
|
1848
|
+
while (childMeta.tptParent) {
|
|
1849
|
+
const parentMeta = childMeta.tptParent;
|
|
1850
|
+
const parentAlias = qb.getNextAlias(parentMeta.className);
|
|
1851
|
+
qb.createAlias(parentMeta.class, parentAlias);
|
|
1852
|
+
qb.state.tptAlias[`${leafAlias}:${parentMeta.className}`] = parentAlias;
|
|
1853
|
+
qb.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${basePath}.[tpt]${childMeta.className}`);
|
|
1854
|
+
childAlias = parentAlias;
|
|
1855
|
+
childMeta = parentMeta;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1653
1858
|
/**
|
|
1654
1859
|
* Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
|
|
1655
1860
|
* @internal
|
|
@@ -1761,18 +1966,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1761
1966
|
});
|
|
1762
1967
|
}
|
|
1763
1968
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
|
|
1969
|
+
const sourceAlias = qb.helper.getTPTAliasForProperty(prop.name, tableAlias);
|
|
1764
1970
|
if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
|
|
1765
1971
|
return prop.fieldNames.map((col, idx) => {
|
|
1766
1972
|
if (!prop.customTypes[idx]?.convertToJSValueSQL) {
|
|
1767
1973
|
return col;
|
|
1768
1974
|
}
|
|
1769
|
-
const prefixed = this.platform.quoteIdentifier(`${
|
|
1975
|
+
const prefixed = this.platform.quoteIdentifier(`${sourceAlias}.${col}`);
|
|
1770
1976
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${col}`);
|
|
1771
1977
|
return raw(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
|
|
1772
1978
|
});
|
|
1773
1979
|
}
|
|
1774
1980
|
if (prop.customType?.convertToJSValueSQL) {
|
|
1775
|
-
const prefixed = this.platform.quoteIdentifier(`${
|
|
1981
|
+
const prefixed = this.platform.quoteIdentifier(`${sourceAlias}.${prop.fieldNames[0]}`);
|
|
1776
1982
|
return [raw(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
1777
1983
|
}
|
|
1778
1984
|
if (prop.formula) {
|
|
@@ -1781,7 +1987,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1781
1987
|
const columns = meta.createColumnMappingObject(tableAlias);
|
|
1782
1988
|
return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
|
|
1783
1989
|
}
|
|
1784
|
-
const sourceAlias = qb.helper.getTPTAliasForProperty(prop.name, tableAlias);
|
|
1785
1990
|
return prop.fieldNames.map(fieldName => {
|
|
1786
1991
|
return raw('?? as ??', [`${sourceAlias}.${fieldName}`, `${tableAlias}__${fieldName}`]);
|
|
1787
1992
|
});
|
|
@@ -1798,6 +2003,57 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1798
2003
|
}
|
|
1799
2004
|
return qb;
|
|
1800
2005
|
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Renders a `FilterQuery` predicate into a SQL fragment (without the `WHERE` keyword and
|
|
2008
|
+
* without table-alias prefixes) suitable for inlining into a partial-index DDL statement.
|
|
2009
|
+
* Used by `DatabaseTable.addIndex` when the user passes an object `where` on `@Index` /
|
|
2010
|
+
* `@Unique`. Strings are returned unchanged.
|
|
2011
|
+
*/
|
|
2012
|
+
renderPartialIndexWhere(entityName, where) {
|
|
2013
|
+
if (typeof where === 'string') {
|
|
2014
|
+
return where;
|
|
2015
|
+
}
|
|
2016
|
+
const name = Utils.className(entityName);
|
|
2017
|
+
if (where == null || (Utils.isPlainObject(where) && Object.keys(where).length === 0)) {
|
|
2018
|
+
throw new Error(`Cannot render partial-index predicate for entity '${name}': \`where\` is empty.`);
|
|
2019
|
+
}
|
|
2020
|
+
const alias = '__p';
|
|
2021
|
+
const qb = this.createQueryBuilder(entityName, undefined, undefined, undefined, undefined, alias);
|
|
2022
|
+
qb.where(where);
|
|
2023
|
+
const sql = qb.getFormattedQuery();
|
|
2024
|
+
// Relation traversal produces join clauses whose aliased identifiers can't be inlined
|
|
2025
|
+
// into a CREATE INDEX ... WHERE clause — reject with a clear error rather than emitting broken DDL.
|
|
2026
|
+
if (/\bjoin\b/i.test(sql.split(/\bwhere\b/i)[0])) {
|
|
2027
|
+
throw new Error(`Cannot render partial-index predicate for entity '${name}': \`where\` may not traverse relations.`);
|
|
2028
|
+
}
|
|
2029
|
+
// Anchor at end-of-string only — the synthetic QB has no top-level order by / limit /
|
|
2030
|
+
// group by / having / offset, so any such keyword inside the captured predicate is
|
|
2031
|
+
// inside a subquery and must not terminate the match.
|
|
2032
|
+
const match = /\bwhere\s+([\s\S]+)$/i.exec(sql);
|
|
2033
|
+
if (!match) {
|
|
2034
|
+
throw new Error(`Failed to render partial-index predicate for entity '${name}': ${sql}`);
|
|
2035
|
+
}
|
|
2036
|
+
const quote = (s) => this.platform.quoteIdentifier(s);
|
|
2037
|
+
const aliasPrefix = new RegExp(`${quote(alias).replace(/[[\]]/g, '\\$&')}\\.`, 'g');
|
|
2038
|
+
const stripped = match[1].replace(aliasPrefix, '').trim();
|
|
2039
|
+
// Any qualified column reference remaining after the alias strip points at another table or
|
|
2040
|
+
// subquery and can't be inlined into a CREATE INDEX ... WHERE predicate. Covers both
|
|
2041
|
+
// QB-generated sub-aliases (quoted, e.g. `"e0"."col"`) and raw fragments with bare refs
|
|
2042
|
+
// (e.g. `raw('other_table.col = 1')`). String literals are erased first so dots inside
|
|
2043
|
+
// them (e.g. JSON path operands like `'$.path'`) don't trip the guard.
|
|
2044
|
+
// Both patterns use a `(?!\s*\()` lookahead so schema-qualified function calls
|
|
2045
|
+
// (`pg_catalog.lower(name)`, `"public".my_func(col)`) are accepted — only `<id>.<id>` not
|
|
2046
|
+
// followed by `(` is treated as a cross-table column reference.
|
|
2047
|
+
const withoutStrings = stripped.replace(/'(?:[^']|'')*'/g, "''");
|
|
2048
|
+
const quotedIdent = String.raw `(?:"(?:[^"]|"")+"|\`(?:[^\`]|\`\`)+\`|\[(?:[^\]]|\]\])+\])`;
|
|
2049
|
+
const anyIdent = `(?:${quotedIdent}|[A-Za-z_]\\w*)`;
|
|
2050
|
+
const quotedCrossRef = new RegExp(`${quotedIdent}\\s*\\.\\s*${anyIdent}(?!\\s*\\()`);
|
|
2051
|
+
const bareCrossRef = /\b[A-Za-z_]\w*\s*\.\s*[A-Za-z_]\w*\b(?!\s*\()/;
|
|
2052
|
+
if (quotedCrossRef.test(withoutStrings) || bareCrossRef.test(withoutStrings)) {
|
|
2053
|
+
throw new Error(`Cannot render partial-index predicate for entity '${name}': \`where\` references another table or subquery which cannot be inlined into a CREATE INDEX ... WHERE clause.`);
|
|
2054
|
+
}
|
|
2055
|
+
return stripped;
|
|
2056
|
+
}
|
|
1801
2057
|
resolveConnectionType(args) {
|
|
1802
2058
|
if (args.ctx) {
|
|
1803
2059
|
return 'write';
|
|
@@ -1824,7 +2080,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1824
2080
|
for (const prop of meta.relations) {
|
|
1825
2081
|
if (collections[prop.name]) {
|
|
1826
2082
|
const pivotMeta = this.metadata.get(prop.pivotEntity);
|
|
1827
|
-
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
2083
|
+
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext, pickAbortOptions(options));
|
|
1828
2084
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1829
2085
|
await this.rethrow(persister.execute());
|
|
1830
2086
|
}
|
|
@@ -1833,6 +2089,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1833
2089
|
async lockPessimistic(entity, options) {
|
|
1834
2090
|
const meta = helper(entity).__meta;
|
|
1835
2091
|
const qb = this.createQueryBuilder(meta.class, options.ctx, undefined, undefined, options.logging).withSchema(options.schema ?? meta.schema);
|
|
2092
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1836
2093
|
const cond = Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
1837
2094
|
qb.select(raw('1'))
|
|
1838
2095
|
.where(cond)
|
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -30,7 +30,8 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
30
30
|
getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | RawQueryFragment;
|
|
31
31
|
/**
|
|
32
32
|
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
33
|
-
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
|
|
33
|
+
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
|
|
34
|
+
* with embedded `\` and `"` escaped per the JSON path string syntax.
|
|
34
35
|
* @internal
|
|
35
36
|
*/
|
|
36
37
|
quoteJsonKey(key: string): string;
|
|
@@ -50,8 +51,19 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
50
51
|
* @internal
|
|
51
52
|
*/
|
|
52
53
|
quoteCollation(collation: string): string;
|
|
53
|
-
/**
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* PG ICU locale names include hyphens (`en-US-x-icu`) and libc locales include dots (`en_US.utf8`),
|
|
56
|
+
* so word-chars alone would reject valid real-world collations.
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
validateCollationName(collation: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Whether collation names compare case-insensitively in this dialect. MySQL/MariaDB, MSSQL, and
|
|
62
|
+
* SQLite use case-insensitive collation identifiers; PostgreSQL stores them as case-sensitive
|
|
63
|
+
* names in `pg_collation` (e.g. `en-US-x-icu` is distinct from `EN-US-X-ICU`).
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
caseInsensitiveCollationNames(): boolean;
|
|
55
67
|
/** @internal */
|
|
56
68
|
validateJsonPropertyName(name: string): void;
|
|
57
69
|
/**
|