@mikro-orm/sql 7.1.0-dev.9 → 7.1.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/AbstractSqlConnection.d.ts +1 -1
- package/AbstractSqlConnection.js +27 -6
- package/AbstractSqlDriver.d.ts +15 -1
- package/AbstractSqlDriver.js +143 -26
- package/AbstractSqlPlatform.d.ts +15 -3
- package/AbstractSqlPlatform.js +25 -7
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +6 -1
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +44 -5
- package/SqlEntityManager.js +41 -6
- package/SqlMikroORM.d.ts +23 -0
- package/SqlMikroORM.js +23 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +3 -5
- package/dialects/mysql/BaseMySqlPlatform.js +6 -10
- package/dialects/mysql/MySqlSchemaHelper.d.ts +16 -3
- package/dialects/mysql/MySqlSchemaHelper.js +197 -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 +31 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +269 -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 +4 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +49 -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 +131 -4
- package/schema/DatabaseTable.d.ts +14 -1
- package/schema/DatabaseTable.js +165 -32
- package/schema/SchemaComparator.d.ts +18 -0
- package/schema/SchemaComparator.js +196 -1
- package/schema/SchemaHelper.d.ts +67 -1
- package/schema/SchemaHelper.js +255 -25
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +40 -10
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +59 -5
|
@@ -60,7 +60,7 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
60
60
|
/** Executes a SQL query and returns the result based on the method: `'all'` for rows, `'get'` for single row, `'run'` for affected count. */
|
|
61
61
|
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
62
62
|
/** Executes a SQL query and returns an async iterable that yields results row by row. */
|
|
63
|
-
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], ctx?: Transaction<Kysely<any>>, loggerContext?: LoggingOptions): AsyncIterableIterator<T>;
|
|
63
|
+
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], ctx?: Transaction<Kysely<any>>, loggerContext?: LoggingOptions, chunkSize?: number): AsyncIterableIterator<T>;
|
|
64
64
|
/** @inheritDoc */
|
|
65
65
|
executeDump(dump: string): Promise<void>;
|
|
66
66
|
protected getSql(query: string, formatted: string, context?: LogContext): string;
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { CompiledQuery, Kysely } from 'kysely';
|
|
2
2
|
import { Connection, EventType, isRaw, Utils, } from '@mikro-orm/core';
|
|
3
3
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
4
|
+
/**
|
|
5
|
+
* Pulls cancellation controls out of a `loggerContext` payload, returning the abort options
|
|
6
|
+
* and a sanitized context that no longer carries them. The QueryBuilder/EM stash
|
|
7
|
+
* `signal`/`inflightQueryAbortStrategy` on `loggerContext` to avoid widening the public
|
|
8
|
+
* connection API; stripping them prevents leakage into user `Logger.logQuery` payloads.
|
|
9
|
+
*/
|
|
10
|
+
function extractAbortOptions(loggerContext) {
|
|
11
|
+
const ctx = loggerContext;
|
|
12
|
+
if (ctx?.signal == null && ctx?.inflightQueryAbortStrategy == null) {
|
|
13
|
+
return { loggerContext };
|
|
14
|
+
}
|
|
15
|
+
const { signal, inflightQueryAbortStrategy, ...rest } = ctx;
|
|
16
|
+
return {
|
|
17
|
+
abort: { signal, inflightQueryAbortStrategy },
|
|
18
|
+
loggerContext: rest,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
4
21
|
/** Base class for SQL database connections, built on top of Kysely. */
|
|
5
22
|
export class AbstractSqlConnection extends Connection {
|
|
6
23
|
#client;
|
|
@@ -183,17 +200,19 @@ export class AbstractSqlConnection extends Connection {
|
|
|
183
200
|
await this.ensureConnection();
|
|
184
201
|
const q = this.prepareQuery(query, params);
|
|
185
202
|
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
203
|
+
const { abort, loggerContext: cleanCtx } = extractAbortOptions(loggerContext);
|
|
186
204
|
return this.executeQuery(sql, async () => {
|
|
187
205
|
const compiled = CompiledQuery.raw(q.formatted);
|
|
188
|
-
const res = await (ctx ?? this.#client).executeQuery(compiled);
|
|
206
|
+
const res = await (ctx ?? this.#client).executeQuery(compiled, abort);
|
|
189
207
|
return this.transformRawResult(res, method);
|
|
190
|
-
}, { ...q, ...
|
|
208
|
+
}, { ...q, ...cleanCtx });
|
|
191
209
|
}
|
|
192
210
|
/** Executes a SQL query and returns an async iterable that yields results row by row. */
|
|
193
|
-
async *stream(query, params = [], ctx, loggerContext) {
|
|
211
|
+
async *stream(query, params = [], ctx, loggerContext, chunkSize) {
|
|
194
212
|
await this.ensureConnection();
|
|
195
213
|
const q = this.prepareQuery(query, params);
|
|
196
214
|
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
215
|
+
const { abort, loggerContext: cleanCtx } = extractAbortOptions(loggerContext);
|
|
197
216
|
// construct the compiled query manually with `kind: 'SelectQueryNode'` to avoid sqlite validation for select queries when streaming
|
|
198
217
|
const compiled = {
|
|
199
218
|
query: {
|
|
@@ -203,11 +222,13 @@ export class AbstractSqlConnection extends Connection {
|
|
|
203
222
|
parameters: [],
|
|
204
223
|
};
|
|
205
224
|
try {
|
|
206
|
-
const res = (ctx ?? this.getClient())
|
|
225
|
+
const res = (ctx ?? this.getClient())
|
|
226
|
+
.getExecutor()
|
|
227
|
+
.stream(compiled, chunkSize ?? 100, abort ? { signal: abort.signal } : undefined);
|
|
207
228
|
this.logQuery(sql, {
|
|
208
229
|
sql,
|
|
209
230
|
params,
|
|
210
|
-
...
|
|
231
|
+
...cleanCtx,
|
|
211
232
|
affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
|
|
212
233
|
});
|
|
213
234
|
for await (const items of res) {
|
|
@@ -217,7 +238,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
217
238
|
}
|
|
218
239
|
}
|
|
219
240
|
catch (e) {
|
|
220
|
-
this.logQuery(sql, { sql, params, ...
|
|
241
|
+
this.logQuery(sql, { sql, params, ...cleanCtx, level: 'error' });
|
|
221
242
|
throw e;
|
|
222
243
|
}
|
|
223
244
|
}
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
31
31
|
protected findFromVirtual<T extends object>(entityName: EntityName<T>, where: ObjectQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
|
|
32
32
|
protected streamFromVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T, any>): AsyncIterableIterator<EntityData<T>>;
|
|
33
33
|
protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
|
|
34
|
-
protected wrapVirtualExpressionInSubqueryStream<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options:
|
|
34
|
+
protected wrapVirtualExpressionInSubqueryStream<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: StreamOptions<T, any, any, any>, type: QueryType.SELECT): AsyncIterableIterator<T>;
|
|
35
35
|
/**
|
|
36
36
|
* Virtual entities have no PKs, so to-many populate joins can't be deduplicated.
|
|
37
37
|
* Force balanced strategy to load to-many relations via separate queries.
|
|
@@ -118,6 +118,13 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
118
118
|
mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
|
|
119
119
|
protected shouldHaveColumn<T, U>(meta: EntityMetadata<T>, prop: EntityProperty<U>, populate: readonly PopulateOptions<U>[], fields?: readonly InternalField<U>[], exclude?: readonly InternalField<U>[]): boolean;
|
|
120
120
|
protected getFieldsForJoinedLoad<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): InternalField<T>[];
|
|
121
|
+
/**
|
|
122
|
+
* Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
|
|
123
|
+
* Registers the parent aliases in `qb.state.tptAlias` so column resolution finds them
|
|
124
|
+
* when filter conditions reference parent-table columns.
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
protected addTPTParentJoinsForRelation<T extends object>(qb: AnyQueryBuilder<T>, leafMeta: EntityMetadata, leafAlias: string, basePath: string): void;
|
|
121
128
|
/**
|
|
122
129
|
* Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
|
|
123
130
|
* @internal
|
|
@@ -147,6 +154,13 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
147
154
|
mapPropToFieldNames<T extends object>(qb: AnyQueryBuilder<T>, prop: EntityProperty<T>, tableAlias: string, meta: EntityMetadata<T>, schema?: string, explicitFields?: readonly InternalField<T>[]): InternalField<T>[];
|
|
148
155
|
/** @internal */
|
|
149
156
|
createQueryBuilder<T extends object>(entityName: EntityName<T> | AnyQueryBuilder<T>, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggingOptions, alias?: string, em?: SqlEntityManager): AnyQueryBuilder<T>;
|
|
157
|
+
/**
|
|
158
|
+
* Renders a `FilterQuery` predicate into a SQL fragment (without the `WHERE` keyword and
|
|
159
|
+
* without table-alias prefixes) suitable for inlining into a partial-index DDL statement.
|
|
160
|
+
* Used by `DatabaseTable.addIndex` when the user passes an object `where` on `@Index` /
|
|
161
|
+
* `@Unique`. Strings are returned unchanged.
|
|
162
|
+
*/
|
|
163
|
+
renderPartialIndexWhere<T extends object>(entityName: EntityName<T>, where: string | FilterQuery<T>): string;
|
|
150
164
|
protected resolveConnectionType(args: {
|
|
151
165
|
ctx?: Transaction;
|
|
152
166
|
connectionType?: ConnectionType;
|
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];
|
|
@@ -57,7 +79,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
57
79
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
58
80
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
59
81
|
const schema = this.getSchemaName(meta, options);
|
|
60
|
-
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));
|
|
61
86
|
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
62
87
|
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
63
88
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
@@ -95,6 +120,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
95
120
|
if (options.em) {
|
|
96
121
|
await qb.applyJoinedFilters(options.em, options.filters);
|
|
97
122
|
}
|
|
123
|
+
if (options._partitionLimit) {
|
|
124
|
+
qb.setPartitionLimit(options._partitionLimit);
|
|
125
|
+
}
|
|
98
126
|
return qb;
|
|
99
127
|
}
|
|
100
128
|
async find(entityName, where, options = {}) {
|
|
@@ -211,7 +239,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
211
239
|
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
212
240
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
213
241
|
const query = native.compile();
|
|
214
|
-
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));
|
|
215
243
|
if (type === QueryType.COUNT) {
|
|
216
244
|
return res[0].count;
|
|
217
245
|
}
|
|
@@ -228,7 +256,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
228
256
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
229
257
|
const query = native.compile();
|
|
230
258
|
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
231
|
-
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);
|
|
232
260
|
for await (const row of res) {
|
|
233
261
|
yield this.mapResult(row, meta);
|
|
234
262
|
}
|
|
@@ -536,6 +564,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
536
564
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
537
565
|
const schema = this.getSchemaName(meta, options);
|
|
538
566
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
567
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
539
568
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
540
569
|
if (meta && !Utils.isEmpty(populate)) {
|
|
541
570
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
@@ -561,6 +590,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
561
590
|
const meta = this.metadata.get(entityName);
|
|
562
591
|
const collections = this.extractManyToMany(meta, data);
|
|
563
592
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
593
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
564
594
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
565
595
|
res.row = res.row || {};
|
|
566
596
|
let pk;
|
|
@@ -593,9 +623,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
593
623
|
const props = this.getCloneableProps(meta);
|
|
594
624
|
const mappedOverrides = this.mapCloneOverrides(overrides, meta, options);
|
|
595
625
|
const { selectFields, insertColumns } = this.buildCloneFields(props, mappedOverrides, meta);
|
|
626
|
+
const abort = pickAbortOptions(options);
|
|
596
627
|
const selectQb = this.createQueryBuilder(meta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
597
628
|
selectQb.select(selectFields).where(where);
|
|
629
|
+
selectQb.setAbortOptions(abort);
|
|
598
630
|
const insertQb = this.createQueryBuilder(meta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
631
|
+
insertQb.setAbortOptions(abort);
|
|
599
632
|
return this.rethrow(insertQb
|
|
600
633
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
601
634
|
.execute('run', false));
|
|
@@ -625,9 +658,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
625
658
|
}
|
|
626
659
|
}
|
|
627
660
|
const sourceWhere = tableMeta === rootMeta ? where : (Utils.extractPK(where, tableMeta) ?? where);
|
|
661
|
+
const abort = pickAbortOptions(options);
|
|
628
662
|
const selectQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
629
663
|
selectQb.select(selectFields).where(sourceWhere);
|
|
664
|
+
selectQb.setAbortOptions(abort);
|
|
630
665
|
const insertQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
666
|
+
insertQb.setAbortOptions(abort);
|
|
631
667
|
const res = await this.rethrow(insertQb
|
|
632
668
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
633
669
|
.execute('run', false));
|
|
@@ -802,13 +838,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
802
838
|
else {
|
|
803
839
|
const field = prop.fieldNames[0];
|
|
804
840
|
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
841
|
+
const rowValue = row[prop.name];
|
|
842
|
+
const rowValueIsRaw = isRaw(rowValue);
|
|
805
843
|
if (prop.customType &&
|
|
806
844
|
!prop.object &&
|
|
807
845
|
'convertToDatabaseValueSQL' in prop.customType &&
|
|
808
|
-
|
|
809
|
-
!
|
|
846
|
+
rowValue != null &&
|
|
847
|
+
!rowValueIsRaw) {
|
|
810
848
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
811
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
|
+
}
|
|
812
854
|
else {
|
|
813
855
|
keys.push('?');
|
|
814
856
|
}
|
|
@@ -835,7 +877,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
835
877
|
if (transform) {
|
|
836
878
|
sql = transform(sql);
|
|
837
879
|
}
|
|
838
|
-
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));
|
|
839
881
|
let pk;
|
|
840
882
|
/* v8 ignore next */
|
|
841
883
|
if (pks.length > 1) {
|
|
@@ -868,6 +910,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
868
910
|
}
|
|
869
911
|
if (Utils.hasObjectKeys(data)) {
|
|
870
912
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
913
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
871
914
|
if (options.upsert) {
|
|
872
915
|
/* v8 ignore next */
|
|
873
916
|
const uniqueFields = options.onConflictFields ??
|
|
@@ -913,6 +956,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
913
956
|
? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
|
|
914
957
|
: meta.primaryKeys);
|
|
915
958
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
959
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
916
960
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
917
961
|
qb.insert(data)
|
|
918
962
|
.onConflict(uniqueFields)
|
|
@@ -1058,7 +1102,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1058
1102
|
if (transform) {
|
|
1059
1103
|
sql = transform(sql, params);
|
|
1060
1104
|
}
|
|
1061
|
-
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)));
|
|
1062
1106
|
for (let i = 0; i < collections.length; i++) {
|
|
1063
1107
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
1064
1108
|
}
|
|
@@ -1076,6 +1120,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1076
1120
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext)
|
|
1077
1121
|
.delete(where)
|
|
1078
1122
|
.withSchema(this.getSchemaName(meta, options));
|
|
1123
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1079
1124
|
return this.rethrow(qb.execute('run', false));
|
|
1080
1125
|
}
|
|
1081
1126
|
/**
|
|
@@ -1146,6 +1191,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1146
1191
|
if (coll.property.kind === ReferenceKind.ONE_TO_MANY) {
|
|
1147
1192
|
const cols = coll.property.referencedColumnNames;
|
|
1148
1193
|
const qb = this.createQueryBuilder(coll.property.targetMeta.class, options?.ctx, 'write').withSchema(this.getSchemaName(meta, options));
|
|
1194
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1149
1195
|
if (coll.getSnapshot() === undefined) {
|
|
1150
1196
|
if (coll.property.orphanRemoval) {
|
|
1151
1197
|
const query = qb
|
|
@@ -1187,7 +1233,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1187
1233
|
schema = this.config.get('schema');
|
|
1188
1234
|
}
|
|
1189
1235
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
1190
|
-
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)));
|
|
1191
1237
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks, coll.isInitialized());
|
|
1192
1238
|
}
|
|
1193
1239
|
for (const persister of Utils.values(groups)) {
|
|
@@ -1224,7 +1270,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1224
1270
|
const fields = pivotJoin
|
|
1225
1271
|
? [pivotProp1.name, pivotProp2.name]
|
|
1226
1272
|
: [pivotProp1.name, pivotProp2.name, ...childFields];
|
|
1227
|
-
const
|
|
1273
|
+
const pivotFindOptions = {
|
|
1228
1274
|
ctx,
|
|
1229
1275
|
...options,
|
|
1230
1276
|
fields,
|
|
@@ -1240,10 +1286,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1240
1286
|
},
|
|
1241
1287
|
],
|
|
1242
1288
|
populateWhere: undefined,
|
|
1243
|
-
// @ts-ignore
|
|
1244
1289
|
_populateWhere: 'infer',
|
|
1245
1290
|
populateFilter: this.wrapPopulateFilter(options, pivotProp2.name),
|
|
1246
|
-
}
|
|
1291
|
+
};
|
|
1292
|
+
if (pivotFindOptions._partitionLimit) {
|
|
1293
|
+
pivotFindOptions._partitionLimit.partitionBy = pivotProp2.name;
|
|
1294
|
+
}
|
|
1295
|
+
const res = await this.find(pivotMeta.class, where, pivotFindOptions);
|
|
1247
1296
|
// Convert result FK values back to JS format so key hashing
|
|
1248
1297
|
// in buildPivotResultMap is consistent with the owner keys.
|
|
1249
1298
|
if (needsConversion) {
|
|
@@ -1684,6 +1733,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1684
1733
|
const targetPath = `${pathPrefix}${basePath}[${targetMeta.className}]`;
|
|
1685
1734
|
const schema = targetMeta.schema === '*' ? (options?.schema ?? this.config.get('schema')) : targetMeta.schema;
|
|
1686
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
|
+
}
|
|
1687
1743
|
// For polymorphic targets that are TPT base classes, also LEFT JOIN
|
|
1688
1744
|
// all descendant tables so child-specific fields can be selected.
|
|
1689
1745
|
if (targetMeta.inheritanceType === 'tpt' && targetMeta.tptChildren?.length && !ref) {
|
|
@@ -1732,17 +1788,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1732
1788
|
qb.join(field, tableAlias, {}, joinType, path, schema);
|
|
1733
1789
|
// For relations to TPT child entities, INNER JOIN parent tables (GH #7469)
|
|
1734
1790
|
if (meta2.inheritanceType === 'tpt' && meta2.tptParent) {
|
|
1735
|
-
|
|
1736
|
-
let childMeta = meta2;
|
|
1737
|
-
while (childMeta.tptParent) {
|
|
1738
|
-
const parentMeta = childMeta.tptParent;
|
|
1739
|
-
const parentAlias = qb.getNextAlias(parentMeta.className);
|
|
1740
|
-
qb.createAlias(parentMeta.class, parentAlias);
|
|
1741
|
-
qb.state.tptAlias[`${tableAlias}:${parentMeta.className}`] = parentAlias;
|
|
1742
|
-
qb.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${path}.[tpt]${childMeta.className}`);
|
|
1743
|
-
childAlias = parentAlias;
|
|
1744
|
-
childMeta = parentMeta;
|
|
1745
|
-
}
|
|
1791
|
+
this.addTPTParentJoinsForRelation(qb, meta2, tableAlias, path);
|
|
1746
1792
|
}
|
|
1747
1793
|
// For relations to TPT base classes, add LEFT JOINs for all child tables (polymorphic loading)
|
|
1748
1794
|
if (meta2.inheritanceType === 'tpt' && meta2.tptChildren?.length && !ref) {
|
|
@@ -1790,6 +1836,25 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1790
1836
|
}
|
|
1791
1837
|
return fields;
|
|
1792
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
|
+
}
|
|
1793
1858
|
/**
|
|
1794
1859
|
* Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
|
|
1795
1860
|
* @internal
|
|
@@ -1901,18 +1966,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1901
1966
|
});
|
|
1902
1967
|
}
|
|
1903
1968
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
|
|
1969
|
+
const sourceAlias = qb.helper.getTPTAliasForProperty(prop.name, tableAlias);
|
|
1904
1970
|
if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
|
|
1905
1971
|
return prop.fieldNames.map((col, idx) => {
|
|
1906
1972
|
if (!prop.customTypes[idx]?.convertToJSValueSQL) {
|
|
1907
1973
|
return col;
|
|
1908
1974
|
}
|
|
1909
|
-
const prefixed = this.platform.quoteIdentifier(`${
|
|
1975
|
+
const prefixed = this.platform.quoteIdentifier(`${sourceAlias}.${col}`);
|
|
1910
1976
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${col}`);
|
|
1911
1977
|
return raw(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
|
|
1912
1978
|
});
|
|
1913
1979
|
}
|
|
1914
1980
|
if (prop.customType?.convertToJSValueSQL) {
|
|
1915
|
-
const prefixed = this.platform.quoteIdentifier(`${
|
|
1981
|
+
const prefixed = this.platform.quoteIdentifier(`${sourceAlias}.${prop.fieldNames[0]}`);
|
|
1916
1982
|
return [raw(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
1917
1983
|
}
|
|
1918
1984
|
if (prop.formula) {
|
|
@@ -1921,7 +1987,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1921
1987
|
const columns = meta.createColumnMappingObject(tableAlias);
|
|
1922
1988
|
return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
|
|
1923
1989
|
}
|
|
1924
|
-
const sourceAlias = qb.helper.getTPTAliasForProperty(prop.name, tableAlias);
|
|
1925
1990
|
return prop.fieldNames.map(fieldName => {
|
|
1926
1991
|
return raw('?? as ??', [`${sourceAlias}.${fieldName}`, `${tableAlias}__${fieldName}`]);
|
|
1927
1992
|
});
|
|
@@ -1938,6 +2003,57 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1938
2003
|
}
|
|
1939
2004
|
return qb;
|
|
1940
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
|
+
}
|
|
1941
2057
|
resolveConnectionType(args) {
|
|
1942
2058
|
if (args.ctx) {
|
|
1943
2059
|
return 'write';
|
|
@@ -1964,7 +2080,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1964
2080
|
for (const prop of meta.relations) {
|
|
1965
2081
|
if (collections[prop.name]) {
|
|
1966
2082
|
const pivotMeta = this.metadata.get(prop.pivotEntity);
|
|
1967
|
-
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));
|
|
1968
2084
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1969
2085
|
await this.rethrow(persister.execute());
|
|
1970
2086
|
}
|
|
@@ -1973,6 +2089,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1973
2089
|
async lockPessimistic(entity, options) {
|
|
1974
2090
|
const meta = helper(entity).__meta;
|
|
1975
2091
|
const qb = this.createQueryBuilder(meta.class, options.ctx, undefined, undefined, options.logging).withSchema(options.schema ?? meta.schema);
|
|
2092
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1976
2093
|
const cond = Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
1977
2094
|
qb.select(raw('1'))
|
|
1978
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
|
/**
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -66,18 +66,23 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
66
66
|
}
|
|
67
67
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
68
68
|
const [a, ...b] = path;
|
|
69
|
+
const jsonPath = this.quoteValue(`$.${b.map(this.quoteJsonKey).join('.')}`);
|
|
69
70
|
if (aliased) {
|
|
70
|
-
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)},
|
|
71
|
+
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, ${jsonPath})`);
|
|
71
72
|
}
|
|
72
|
-
return raw(`json_extract(${this.quoteIdentifier(a)},
|
|
73
|
+
return raw(`json_extract(${this.quoteIdentifier(a)}, ${jsonPath})`);
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
76
|
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
76
|
-
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
|
|
77
|
+
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
|
|
78
|
+
* with embedded `\` and `"` escaped per the JSON path string syntax.
|
|
77
79
|
* @internal
|
|
78
80
|
*/
|
|
79
81
|
quoteJsonKey(key) {
|
|
80
|
-
|
|
82
|
+
if (/^[a-z]\w*$/i.test(key)) {
|
|
83
|
+
return key;
|
|
84
|
+
}
|
|
85
|
+
return `"${key.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
81
86
|
}
|
|
82
87
|
getJsonIndexDefinition(index) {
|
|
83
88
|
return index.columnNames.map(column => {
|
|
@@ -123,12 +128,25 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
123
128
|
this.validateCollationName(collation);
|
|
124
129
|
return this.quoteIdentifier(collation);
|
|
125
130
|
}
|
|
126
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* PG ICU locale names include hyphens (`en-US-x-icu`) and libc locales include dots (`en_US.utf8`),
|
|
133
|
+
* so word-chars alone would reject valid real-world collations.
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
127
136
|
validateCollationName(collation) {
|
|
128
|
-
if (!/^[\w]+$/.test(collation)) {
|
|
129
|
-
throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
|
|
137
|
+
if (!/^[\w\-.]+$/.test(collation)) {
|
|
138
|
+
throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters, hyphens, and dots.`);
|
|
130
139
|
}
|
|
131
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Whether collation names compare case-insensitively in this dialect. MySQL/MariaDB, MSSQL, and
|
|
143
|
+
* SQLite use case-insensitive collation identifiers; PostgreSQL stores them as case-sensitive
|
|
144
|
+
* names in `pg_collation` (e.g. `en-US-x-icu` is distinct from `EN-US-X-ICU`).
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
caseInsensitiveCollationNames() {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
132
150
|
/** @internal */
|
|
133
151
|
validateJsonPropertyName(name) {
|
|
134
152
|
if (!AbstractSqlPlatform.#JSON_PROPERTY_NAME_RE.test(name)) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { type AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
export declare class PivotCollectionPersister<Entity extends object> {
|
|
4
4
|
#private;
|
|
5
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary);
|
|
5
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary, abort?: AbortQueryOptions);
|
|
6
6
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[], isInitialized?: boolean): void;
|
|
7
7
|
private enqueueInsert;
|
|
8
8
|
private enqueueUpsert;
|
|
@@ -44,12 +44,14 @@ export class PivotCollectionPersister {
|
|
|
44
44
|
#ctx;
|
|
45
45
|
#schema;
|
|
46
46
|
#loggerContext;
|
|
47
|
-
|
|
47
|
+
#abort;
|
|
48
|
+
constructor(meta, driver, ctx, schema, loggerContext, abort) {
|
|
48
49
|
this.#meta = meta;
|
|
49
50
|
this.#driver = driver;
|
|
50
51
|
this.#ctx = ctx;
|
|
51
52
|
this.#schema = schema;
|
|
52
53
|
this.#loggerContext = loggerContext;
|
|
54
|
+
this.#abort = abort;
|
|
53
55
|
this.#batchSize = this.#driver.config.get('batchSize');
|
|
54
56
|
}
|
|
55
57
|
enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
|
|
@@ -153,6 +155,7 @@ export class PivotCollectionPersister {
|
|
|
153
155
|
ctx: this.#ctx,
|
|
154
156
|
schema: this.#schema,
|
|
155
157
|
loggerContext: this.#loggerContext,
|
|
158
|
+
...this.#abort,
|
|
156
159
|
});
|
|
157
160
|
}
|
|
158
161
|
}
|
|
@@ -166,6 +169,7 @@ export class PivotCollectionPersister {
|
|
|
166
169
|
convertCustomTypes: false,
|
|
167
170
|
processCollections: false,
|
|
168
171
|
loggerContext: this.#loggerContext,
|
|
172
|
+
...this.#abort,
|
|
169
173
|
});
|
|
170
174
|
}
|
|
171
175
|
}
|
|
@@ -181,6 +185,7 @@ export class PivotCollectionPersister {
|
|
|
181
185
|
upsert: true,
|
|
182
186
|
onConflictAction: 'ignore',
|
|
183
187
|
loggerContext: this.#loggerContext,
|
|
188
|
+
...this.#abort,
|
|
184
189
|
});
|
|
185
190
|
}
|
|
186
191
|
}
|