@mikro-orm/sql 7.2.0-dev.9 → 7.2.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 +14 -3
- package/AbstractSqlConnection.js +42 -4
- package/AbstractSqlDriver.d.ts +1 -8
- package/AbstractSqlDriver.js +63 -35
- package/AbstractSqlPlatform.d.ts +4 -2
- package/AbstractSqlPlatform.js +35 -1
- package/README.md +1 -0
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +4 -2
- package/dialects/mysql/BaseMySqlPlatform.d.ts +2 -0
- package/dialects/mysql/BaseMySqlPlatform.js +4 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +3 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.js +35 -2
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +8 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +18 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +153 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/package.json +3 -3
- package/plugin/transformer.d.ts +7 -1
- package/plugin/transformer.js +60 -1
- package/query/CriteriaNodeFactory.js +4 -0
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +30 -5
- package/query/QueryBuilder.d.ts +26 -3
- package/query/QueryBuilder.js +139 -23
- package/query/QueryBuilderHelper.d.ts +5 -0
- package/query/QueryBuilderHelper.js +33 -8
- package/schema/DatabaseSchema.d.ts +4 -0
- package/schema/DatabaseSchema.js +107 -1
- package/schema/DatabaseTable.d.ts +13 -1
- package/schema/DatabaseTable.js +50 -1
- package/schema/SchemaComparator.d.ts +2 -0
- package/schema/SchemaComparator.js +94 -4
- package/schema/SchemaHelper.d.ts +6 -0
- package/schema/SchemaHelper.js +15 -0
- package/schema/SqlSchemaGenerator.js +8 -0
- package/typings.d.ts +18 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ControlledTransaction, type Dialect, Kysely } from 'kysely';
|
|
2
|
-
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LogContext, type LoggingOptions, type MaybePromise, type QueryResult, type RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LogContext, type LoggingOptions, type MaybePromise, type QueryResult, type RawQueryFragment, type SessionContext, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
/** Base class for SQL database connections, built on top of Kysely. */
|
|
@@ -50,6 +50,7 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
50
50
|
ctx?: ControlledTransaction<any>;
|
|
51
51
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
52
52
|
loggerContext?: LogContext;
|
|
53
|
+
sessionContext?: SessionContext;
|
|
53
54
|
}): Promise<T>;
|
|
54
55
|
/** Begins a new transaction or creates a savepoint if a transaction context already exists. */
|
|
55
56
|
begin(options?: {
|
|
@@ -58,7 +59,17 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
58
59
|
ctx?: ControlledTransaction<any, any>;
|
|
59
60
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
60
61
|
loggerContext?: LogContext;
|
|
62
|
+
sessionContext?: SessionContext;
|
|
61
63
|
}): Promise<ControlledTransaction<any, any>>;
|
|
64
|
+
/** Applies session variables (`set_config`) and role (`set local role`) for the current transaction. */
|
|
65
|
+
private applySessionContext;
|
|
66
|
+
/**
|
|
67
|
+
* Quotes a role name as a single PostgreSQL identifier. Unlike `platform.quoteIdentifier`, which treats a dot as a
|
|
68
|
+
* schema qualifier, a role like `my.role` must be quoted whole (`"my.role"`) with embedded quotes doubled.
|
|
69
|
+
*/
|
|
70
|
+
protected static quoteRole(role: string): string;
|
|
71
|
+
/** Serializes a session variable for `set_config()`; `Date` values use ISO 8601 so casts like `::timestamptz` parse. */
|
|
72
|
+
protected stringifySessionVariable(value: unknown): string;
|
|
62
73
|
/** Commits the transaction or releases the savepoint. */
|
|
63
74
|
commit(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
64
75
|
/** Rolls back the transaction or rolls back to the savepoint. */
|
|
@@ -74,9 +85,9 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
74
85
|
private waitForIdleTransaction;
|
|
75
86
|
private prepareQuery;
|
|
76
87
|
/** Executes a SQL query and returns the result based on the method: `'all'` for rows, `'get'` for single row, `'run'` for affected count. */
|
|
77
|
-
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[]
|
|
88
|
+
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>, method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
78
89
|
/** Executes a SQL query and returns an async iterable that yields results row by row. */
|
|
79
|
-
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[]
|
|
90
|
+
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>, ctx?: Transaction<Kysely<any>>, loggerContext?: LoggingOptions, chunkSize?: number): AsyncIterableIterator<T>;
|
|
80
91
|
/** @inheritDoc */
|
|
81
92
|
executeDump(dump: string): Promise<void>;
|
|
82
93
|
protected getSql(query: string, formatted: string, context?: LogContext): string;
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CompiledQuery, Kysely } from 'kysely';
|
|
2
|
-
import { Connection, EventType, isRaw, Utils, } from '@mikro-orm/core';
|
|
2
|
+
import { Connection, EventType, isRaw, raw, Utils, } from '@mikro-orm/core';
|
|
3
3
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
4
4
|
/**
|
|
5
5
|
* Pulls cancellation controls out of a `loggerContext` payload, returning the abort options
|
|
@@ -161,9 +161,43 @@ export class AbstractSqlConnection extends Connection {
|
|
|
161
161
|
for (const query of this.platform.getBeginTransactionSQL(options)) {
|
|
162
162
|
this.logQuery(query, options.loggerContext);
|
|
163
163
|
}
|
|
164
|
+
if (options.sessionContext) {
|
|
165
|
+
try {
|
|
166
|
+
await this.applySessionContext(trx, options.sessionContext, options.loggerContext);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
// roll back the freshly opened transaction so the pooled connection is released, not leaked
|
|
170
|
+
await trx.rollback().execute();
|
|
171
|
+
this.logQuery(this.platform.getRollbackTransactionSQL(), options.loggerContext);
|
|
172
|
+
throw e;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
164
175
|
await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
|
|
165
176
|
return trx;
|
|
166
177
|
}
|
|
178
|
+
/** Applies session variables (`set_config`) and role (`set local role`) for the current transaction. */
|
|
179
|
+
async applySessionContext(trx, sessionContext, loggerContext) {
|
|
180
|
+
const variables = Object.entries(sessionContext.variables ?? {});
|
|
181
|
+
if (variables.length > 0) {
|
|
182
|
+
const parts = variables.map(() => 'set_config(?, ?, true)').join(', ');
|
|
183
|
+
const params = variables.flatMap(([key, value]) => [key, this.stringifySessionVariable(value)]);
|
|
184
|
+
await this.execute(`select ${parts}`, params, 'run', trx, loggerContext);
|
|
185
|
+
}
|
|
186
|
+
if (sessionContext.role) {
|
|
187
|
+
await this.execute(`set local role ${AbstractSqlConnection.quoteRole(sessionContext.role)}`, [], 'run', trx, loggerContext);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Quotes a role name as a single PostgreSQL identifier. Unlike `platform.quoteIdentifier`, which treats a dot as a
|
|
192
|
+
* schema qualifier, a role like `my.role` must be quoted whole (`"my.role"`) with embedded quotes doubled.
|
|
193
|
+
*/
|
|
194
|
+
static quoteRole(role) {
|
|
195
|
+
return `"${role.replaceAll('"', '""')}"`;
|
|
196
|
+
}
|
|
197
|
+
/** Serializes a session variable for `set_config()`; `Date` values use ISO 8601 so casts like `::timestamptz` parse. */
|
|
198
|
+
stringifySessionVariable(value) {
|
|
199
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
200
|
+
}
|
|
167
201
|
/** Commits the transaction or releases the savepoint. */
|
|
168
202
|
async commit(ctx, eventBroadcaster, loggerContext) {
|
|
169
203
|
if (ctx.isRolledBack) {
|
|
@@ -209,13 +243,17 @@ export class AbstractSqlConnection extends Connection {
|
|
|
209
243
|
if (query instanceof NativeQueryBuilder) {
|
|
210
244
|
query = query.toRaw();
|
|
211
245
|
}
|
|
246
|
+
if (typeof query === 'string' && !Array.isArray(params)) {
|
|
247
|
+
// plain object params hold named parameters, translate them via the `raw()` helper
|
|
248
|
+
query = raw(query, params);
|
|
249
|
+
}
|
|
212
250
|
if (isRaw(query)) {
|
|
213
251
|
params = query.params;
|
|
214
252
|
query = query.sql;
|
|
215
253
|
}
|
|
216
254
|
query = this.config.get('onQuery')(query, params);
|
|
217
255
|
const formatted = this.platform.formatQuery(query, params);
|
|
218
|
-
return { query, params, formatted };
|
|
256
|
+
return { query, params: params, formatted };
|
|
219
257
|
}
|
|
220
258
|
/** Executes a SQL query and returns the result based on the method: `'all'` for rows, `'get'` for single row, `'run'` for affected count. */
|
|
221
259
|
async execute(query, params = [], method = 'all', ctx, loggerContext) {
|
|
@@ -249,7 +287,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
249
287
|
.stream(compiled, chunkSize ?? 100, abort ? { signal: abort.signal } : undefined);
|
|
250
288
|
this.logQuery(sql, {
|
|
251
289
|
sql,
|
|
252
|
-
params,
|
|
290
|
+
params: q.params,
|
|
253
291
|
...cleanCtx,
|
|
254
292
|
affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
|
|
255
293
|
});
|
|
@@ -260,7 +298,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
260
298
|
}
|
|
261
299
|
}
|
|
262
300
|
catch (e) {
|
|
263
|
-
this.logQuery(sql, { sql, params, ...cleanCtx, level: 'error' });
|
|
301
|
+
this.logQuery(sql, { sql, params: q.params, ...cleanCtx, level: 'error' });
|
|
264
302
|
throw e;
|
|
265
303
|
}
|
|
266
304
|
}
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
100
100
|
*/
|
|
101
101
|
private convertOwnerPksForPivotQuery;
|
|
102
102
|
private getPivotOrderBy;
|
|
103
|
-
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
103
|
+
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[] | Dictionary, method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
104
104
|
stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T, any, any, any>): AsyncIterableIterator<T>;
|
|
105
105
|
/**
|
|
106
106
|
* 1:1 owner side needs to be marked for population so QB auto-joins the owner id
|
|
@@ -118,13 +118,6 @@ 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;
|
|
128
121
|
/**
|
|
129
122
|
* Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
|
|
130
123
|
* @internal
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -183,7 +183,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
183
183
|
if (typeof meta.expression === 'string') {
|
|
184
184
|
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
|
|
185
185
|
}
|
|
186
|
-
|
|
186
|
+
// fork the caller EM so the callback inherits its filters, filter params and session context — a fork never
|
|
187
|
+
// resolves back to the ambient RequestContext EM, which would ignore the transaction/session context set below
|
|
188
|
+
const em = (options.em?.fork() ?? this.createEntityManager(false));
|
|
187
189
|
em.setTransactionContext(options.ctx);
|
|
188
190
|
const res = meta.expression(em, where, options);
|
|
189
191
|
if (typeof res === 'string') {
|
|
@@ -209,7 +211,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
209
211
|
yield* this.wrapVirtualExpressionInSubqueryStream(meta, meta.expression, where, options, QueryType.SELECT);
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
|
-
|
|
214
|
+
// fork the caller EM for the same reason as in `findFromVirtual`
|
|
215
|
+
const em = (options.em?.fork() ?? this.createEntityManager(false));
|
|
213
216
|
em.setTransactionContext(options.ctx);
|
|
214
217
|
const res = meta.expression(em, where, options, true);
|
|
215
218
|
if (typeof res === 'string') {
|
|
@@ -239,7 +242,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
239
242
|
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
240
243
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
241
244
|
const query = native.compile();
|
|
242
|
-
const
|
|
245
|
+
const loggerContext = withAbortContext(options.loggerContext, options);
|
|
246
|
+
// virtual entities execute directly (not via QueryBuilder), so wrap in a short implicit transaction outside an
|
|
247
|
+
// existing one when a session context (row level security) needs to apply — mirrors the QueryBuilder wrap
|
|
248
|
+
const sessionContext = options.ctx ? undefined : options.em?.getTransactionSessionContext();
|
|
249
|
+
const conn = this.getConnection(this.resolveConnectionType(options));
|
|
250
|
+
const res = await (sessionContext
|
|
251
|
+
? conn.transactional(trx => this.execute(query.sql, query.params, 'all', trx, loggerContext), {
|
|
252
|
+
sessionContext,
|
|
253
|
+
loggerContext,
|
|
254
|
+
})
|
|
255
|
+
: this.execute(query.sql, query.params, 'all', options.ctx, loggerContext));
|
|
243
256
|
if (type === QueryType.COUNT) {
|
|
244
257
|
return res[0].count;
|
|
245
258
|
}
|
|
@@ -915,7 +928,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
915
928
|
if (!options.upsert && options.unionWhere?.length) {
|
|
916
929
|
where = (await this.applyUnionWhere(meta, where, options, true));
|
|
917
930
|
}
|
|
918
|
-
if (
|
|
931
|
+
if (options.upsert && meta.tptParent) {
|
|
932
|
+
res = await this.nativeUpdateMany(entityName, [where], [data], options);
|
|
933
|
+
}
|
|
934
|
+
else if (Utils.hasObjectKeys(data) || (meta.inheritanceType === 'tpt' && meta.ownsVersionProperty())) {
|
|
935
|
+
// a TPT table declaring the version property is bumped even when only other tables of the hierarchy changed
|
|
919
936
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
920
937
|
qb.setAbortOptions(pickAbortOptions(options));
|
|
921
938
|
if (options.upsert) {
|
|
@@ -941,7 +958,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
941
958
|
qb.update(data).where(where);
|
|
942
959
|
// reload generated columns and version fields
|
|
943
960
|
const returning = [];
|
|
944
|
-
meta
|
|
961
|
+
this.getTableProps(meta)
|
|
945
962
|
.filter(prop => (prop.generated && !prop.primary) || prop.version)
|
|
946
963
|
.forEach(prop => returning.push(prop.name));
|
|
947
964
|
qb.returning(returning);
|
|
@@ -957,13 +974,38 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
957
974
|
options.convertCustomTypes ??= true;
|
|
958
975
|
const meta = this.metadata.get(entityName);
|
|
959
976
|
if (options.upsert) {
|
|
977
|
+
if (meta.tptParent) {
|
|
978
|
+
// TPT parent tables go first, the PK they provide is the conflict target of this table
|
|
979
|
+
await this.nativeUpdateMany(meta.tptParent.class, where, data, options);
|
|
980
|
+
for (const [i, row] of data.entries()) {
|
|
981
|
+
if (meta.primaryKeys.some(pk => row[pk] == null)) {
|
|
982
|
+
const found = await this.findOne(meta.tptParent.class, where[i], {
|
|
983
|
+
fields: meta.primaryKeys,
|
|
984
|
+
ctx: options.ctx,
|
|
985
|
+
connectionType: 'write',
|
|
986
|
+
schema: options.schema,
|
|
987
|
+
});
|
|
988
|
+
meta.primaryKeys.forEach(pk => (row[pk] = found?.[pk]));
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
options = { ...options, onConflictFields: meta.primaryKeys, onConflictWhere: undefined };
|
|
992
|
+
}
|
|
960
993
|
const uniqueFields = options.onConflictFields ??
|
|
961
994
|
(Utils.isPlainObject(where[0])
|
|
962
995
|
? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
|
|
963
996
|
: meta.primaryKeys);
|
|
964
997
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
965
998
|
qb.setAbortOptions(pickAbortOptions(options));
|
|
966
|
-
|
|
999
|
+
let returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
1000
|
+
if (meta.inheritanceType === 'tpt') {
|
|
1001
|
+
// each TPT table only carries its own columns, the entity is reloaded instead of mapping the returned rows
|
|
1002
|
+
const own = (key) => meta.primaryKeys.includes(key) ||
|
|
1003
|
+
this.getTableProps(meta).some(prop => prop.name === key.split('.')[0]);
|
|
1004
|
+
data = data.map(row => Object.fromEntries(Object.entries(row).filter(([key]) => own(key))));
|
|
1005
|
+
options.onConflictMergeFields = options.onConflictMergeFields?.filter(f => own(f));
|
|
1006
|
+
options.onConflictExcludeFields = options.onConflictExcludeFields?.filter(f => own(f));
|
|
1007
|
+
returning = [];
|
|
1008
|
+
}
|
|
967
1009
|
qb.insert(data)
|
|
968
1010
|
.onConflict(uniqueFields)
|
|
969
1011
|
.returning(returning);
|
|
@@ -977,7 +1019,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
977
1019
|
if (options.onConflictWhere) {
|
|
978
1020
|
qb.where(options.onConflictWhere);
|
|
979
1021
|
}
|
|
980
|
-
|
|
1022
|
+
const res = await this.rethrow(qb.execute('run', false));
|
|
1023
|
+
return meta.inheritanceType === 'tpt' ? { ...res, row: undefined, rows: [] } : res;
|
|
981
1024
|
}
|
|
982
1025
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(meta, d)) : [];
|
|
983
1026
|
const keys = new Set();
|
|
@@ -992,7 +1035,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
992
1035
|
}
|
|
993
1036
|
}
|
|
994
1037
|
// reload generated columns and version fields
|
|
995
|
-
meta.
|
|
1038
|
+
meta.getPrimaryProps().forEach(prop => returning.add(prop.name));
|
|
1039
|
+
this.getTableProps(meta)
|
|
1040
|
+
.filter(prop => prop.generated || prop.version)
|
|
1041
|
+
.forEach(prop => returning.add(prop.name));
|
|
996
1042
|
const pkCond = Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames))
|
|
997
1043
|
.map(pk => `${this.platform.quoteIdentifier(pk)} = ?`)
|
|
998
1044
|
.join(' and ');
|
|
@@ -1050,7 +1096,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1050
1096
|
return sql;
|
|
1051
1097
|
});
|
|
1052
1098
|
}
|
|
1053
|
-
if (meta.
|
|
1099
|
+
if (meta.ownsVersionProperty()) {
|
|
1054
1100
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
1055
1101
|
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
1056
1102
|
sql += `${quotedFieldName} = `;
|
|
@@ -1063,7 +1109,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1063
1109
|
sql += `, `;
|
|
1064
1110
|
}
|
|
1065
1111
|
sql = sql.substring(0, sql.length - 2) + ' where ';
|
|
1066
|
-
const pkProps = meta.primaryKeys.concat(...meta.
|
|
1112
|
+
const pkProps = meta.primaryKeys.concat(...meta.getOwnConcurrencyCheckKeys());
|
|
1067
1113
|
const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
|
|
1068
1114
|
const useTupleIn = pks.length <= 1 || this.platform.allowsComparingTuples();
|
|
1069
1115
|
const condTemplate = useTupleIn
|
|
@@ -1300,7 +1346,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1300
1346
|
],
|
|
1301
1347
|
populateWhere: undefined,
|
|
1302
1348
|
_populateWhere: 'infer',
|
|
1303
|
-
populateFilter: this.wrapPopulateFilter(options,
|
|
1349
|
+
populateFilter: this.wrapPopulateFilter(options, pivotProp1.name),
|
|
1304
1350
|
};
|
|
1305
1351
|
if (pivotFindOptions._partitionLimit) {
|
|
1306
1352
|
pivotFindOptions._partitionLimit.partitionBy = pivotProp2.name;
|
|
@@ -1649,6 +1695,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1649
1695
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
1650
1696
|
return false;
|
|
1651
1697
|
}
|
|
1698
|
+
// Polymorphic to-one flattened from an object embeddable lives inside a JSON column, so the join
|
|
1699
|
+
// conditions would need JSON extraction for both the discriminator and the FK; fall back to SELECT_IN.
|
|
1700
|
+
if (prop.polymorphic && prop.object && prop.embedded) {
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1652
1703
|
if (strategy !== LoadStrategy.JOINED) {
|
|
1653
1704
|
// force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
|
|
1654
1705
|
return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
@@ -1779,7 +1830,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1779
1830
|
// INNER JOINs get nested inside the polymorphic LEFT JOIN by processNestedJoins, which
|
|
1780
1831
|
// keeps the resulting query valid for rows pointing to other polymorphic targets.
|
|
1781
1832
|
if (targetMeta.inheritanceType === 'tpt' && targetMeta.tptParent) {
|
|
1782
|
-
|
|
1833
|
+
qb.addTPTParentJoins(targetMeta, tableAlias, targetPath);
|
|
1783
1834
|
}
|
|
1784
1835
|
// For polymorphic targets that are TPT base classes, also LEFT JOIN
|
|
1785
1836
|
// all descendant tables so child-specific fields can be selected.
|
|
@@ -1827,10 +1878,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1827
1878
|
: JoinType.leftJoin;
|
|
1828
1879
|
const schema = prop.targetMeta.schema === '*' ? (options?.schema ?? this.config.get('schema')) : prop.targetMeta.schema;
|
|
1829
1880
|
qb.join(field, tableAlias, {}, joinType, path, schema);
|
|
1830
|
-
// For relations to TPT child entities, INNER JOIN parent tables (GH #7469)
|
|
1831
|
-
if (meta2.inheritanceType === 'tpt' && meta2.tptParent) {
|
|
1832
|
-
this.addTPTParentJoinsForRelation(qb, meta2, tableAlias, path);
|
|
1833
|
-
}
|
|
1834
1881
|
// For relations to TPT base classes, add LEFT JOINs for all child tables (polymorphic loading)
|
|
1835
1882
|
if (meta2.inheritanceType === 'tpt' && meta2.tptChildren?.length && !ref) {
|
|
1836
1883
|
// Use the registry metadata to ensure allTPTDescendants is available
|
|
@@ -1877,25 +1924,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1877
1924
|
}
|
|
1878
1925
|
return fields;
|
|
1879
1926
|
}
|
|
1880
|
-
/**
|
|
1881
|
-
* Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
|
|
1882
|
-
* Registers the parent aliases in `qb.state.tptAlias` so column resolution finds them
|
|
1883
|
-
* when filter conditions reference parent-table columns.
|
|
1884
|
-
* @internal
|
|
1885
|
-
*/
|
|
1886
|
-
addTPTParentJoinsForRelation(qb, leafMeta, leafAlias, basePath) {
|
|
1887
|
-
let childAlias = leafAlias;
|
|
1888
|
-
let childMeta = leafMeta;
|
|
1889
|
-
while (childMeta.tptParent) {
|
|
1890
|
-
const parentMeta = childMeta.tptParent;
|
|
1891
|
-
const parentAlias = qb.getNextAlias(parentMeta.className);
|
|
1892
|
-
qb.createAlias(parentMeta.class, parentAlias);
|
|
1893
|
-
qb.state.tptAlias[`${leafAlias}:${parentMeta.className}`] = parentAlias;
|
|
1894
|
-
qb.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${basePath}.[tpt]${childMeta.className}`);
|
|
1895
|
-
childAlias = parentAlias;
|
|
1896
|
-
childMeta = parentMeta;
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
1927
|
/**
|
|
1900
1928
|
* Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
|
|
1901
1929
|
* @internal
|
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RawQueryFragment, type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type IsolationLevel, type MikroORM, Platform } from '@mikro-orm/core';
|
|
1
|
+
import { type RawQueryFragment, type Constructor, type EntityManager, type EntityProperty, type EntityRepository, type FormulaColumns, type IDatabaseDriver, type IsolationLevel, type MikroORM, Platform } from '@mikro-orm/core';
|
|
2
2
|
import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
|
|
3
3
|
import { type SchemaHelper } from './schema/SchemaHelper.js';
|
|
4
4
|
import type { IndexDef } from './typings.js';
|
|
@@ -17,6 +17,8 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
17
17
|
getSchemaGenerator(driver: IDatabaseDriver, em?: EntityManager): SqlSchemaGenerator;
|
|
18
18
|
/** @internal */
|
|
19
19
|
createNativeQueryBuilder(): NativeQueryBuilder;
|
|
20
|
+
/** @inheritDoc */
|
|
21
|
+
getThroughRelationFormula(prop: EntityProperty, columns: FormulaColumns<any>): string;
|
|
20
22
|
getBeginTransactionSQL(options?: {
|
|
21
23
|
isolationLevel?: IsolationLevel;
|
|
22
24
|
readOnly?: boolean;
|
|
@@ -28,7 +30,7 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
28
30
|
getReleaseSavepointSQL(savepointName: string): string;
|
|
29
31
|
quoteValue(value: any): string;
|
|
30
32
|
getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | RawQueryFragment;
|
|
31
|
-
getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | RawQueryFragment;
|
|
33
|
+
getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean | string, value?: unknown): string | RawQueryFragment;
|
|
32
34
|
/**
|
|
33
35
|
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
34
36
|
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRaw, JsonProperty, Platform, raw, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { isRaw, JsonProperty, Platform, raw, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
2
|
import { SqlEntityRepository } from './SqlEntityRepository.js';
|
|
3
3
|
import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
@@ -39,6 +39,37 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
39
39
|
createNativeQueryBuilder() {
|
|
40
40
|
return new NativeQueryBuilder(this);
|
|
41
41
|
}
|
|
42
|
+
/** @inheritDoc */
|
|
43
|
+
getThroughRelationFormula(prop, columns) {
|
|
44
|
+
const through = prop.through;
|
|
45
|
+
const driver = this.config.getDriver();
|
|
46
|
+
const alias = `${prop.name}_through`;
|
|
47
|
+
const qb = driver.createQueryBuilder(through.entity, undefined, 'read', true, undefined, alias);
|
|
48
|
+
const throughMeta = driver.getMetadata().get(through.entity);
|
|
49
|
+
const ownerProp = throughMeta.properties[through.ownerProperty];
|
|
50
|
+
const ownerMeta = driver.getMetadata().get(ownerProp.target);
|
|
51
|
+
ownerProp.fieldNames.forEach((fieldName, idx) => {
|
|
52
|
+
const referencedColumn = ownerProp.referencedColumnNames[idx];
|
|
53
|
+
const referencedProp = ownerProp.referencedPKs
|
|
54
|
+
.map(name => ownerMeta.properties[name])
|
|
55
|
+
.find(p => p.fieldNames.includes(referencedColumn));
|
|
56
|
+
// the column mapping resolves the right alias even for TPT owners, only the column name may differ for composite FK properties
|
|
57
|
+
const ownerAlias = columns[referencedProp.name].slice(0, columns[referencedProp.name].lastIndexOf('.'));
|
|
58
|
+
qb.andWhere(raw('?? = ??', [`${alias}.${fieldName}`, `${ownerAlias}.${referencedColumn}`]));
|
|
59
|
+
});
|
|
60
|
+
if (through.where) {
|
|
61
|
+
qb.andWhere(through.where);
|
|
62
|
+
}
|
|
63
|
+
if (through.orderBy) {
|
|
64
|
+
qb.orderBy(through.orderBy);
|
|
65
|
+
}
|
|
66
|
+
qb.select(through.targetProperty ?? throughMeta.primaryKeys[0]).limit(1);
|
|
67
|
+
const sql = qb.getFormattedQuery();
|
|
68
|
+
if (Object.keys(qb.state.joins).length > 0) {
|
|
69
|
+
throw new ValidationError(`The 'where' and 'orderBy' options of the through relation ${prop.name} can only reference own columns of ${throughMeta.className}`);
|
|
70
|
+
}
|
|
71
|
+
return `(${sql})`;
|
|
72
|
+
}
|
|
42
73
|
getBeginTransactionSQL(options) {
|
|
43
74
|
if (options?.isolationLevel) {
|
|
44
75
|
return [`set transaction isolation level ${options.isolationLevel}`, 'begin'];
|
|
@@ -75,6 +106,9 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
75
106
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
76
107
|
const [a, ...b] = path;
|
|
77
108
|
const jsonPath = this.quoteValue(`$.${b.map(this.quoteJsonKey).join('.')}`);
|
|
109
|
+
if (typeof aliased === 'string') {
|
|
110
|
+
return raw(`json_extract(${this.quoteIdentifier(`${aliased}.${a}`)}, ${jsonPath})`);
|
|
111
|
+
}
|
|
78
112
|
if (aliased) {
|
|
79
113
|
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, ${jsonPath})`);
|
|
80
114
|
}
|
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ npm install @mikro-orm/mysql # MySQL
|
|
|
24
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
25
25
|
npm install @mikro-orm/sqlite # SQLite
|
|
26
26
|
npm install @mikro-orm/libsql # libSQL / Turso
|
|
27
|
+
npm install @mikro-orm/sql-js # sql.js (in-memory SQLite in WASM)
|
|
27
28
|
npm install @mikro-orm/mongodb # MongoDB
|
|
28
29
|
npm install @mikro-orm/mssql # MS SQL Server
|
|
29
30
|
npm install @mikro-orm/oracledb # Oracle
|
package/SqlEntityManager.d.ts
CHANGED
|
@@ -60,14 +60,14 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
|
|
|
60
60
|
* `signal` / `inflightQueryAbortStrategy` (set via `em.fork({ signal })`) is applied automatically.
|
|
61
61
|
* For per-call cancellation use the options-bag overload below.
|
|
62
62
|
*/
|
|
63
|
-
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
|
|
63
|
+
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[] | Dictionary, method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
|
|
64
64
|
/**
|
|
65
65
|
* Executes a raw SQL query with an options bag carrying `method`, `loggerContext`, `signal`
|
|
66
66
|
* and `inflightQueryAbortStrategy`. Per-call `signal` / `inflightQueryAbortStrategy` override
|
|
67
67
|
* the fork-level defaults set via `em.fork({ signal })`. The current transaction context is
|
|
68
68
|
* applied automatically.
|
|
69
69
|
*/
|
|
70
|
-
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params: any[], options: EmExecuteOptions): Promise<T>;
|
|
70
|
+
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params: any[] | Dictionary, options: EmExecuteOptions): Promise<T>;
|
|
71
71
|
/**
|
|
72
72
|
* @inheritDoc
|
|
73
73
|
*/
|
package/SqlEntityManager.js
CHANGED
|
@@ -68,7 +68,7 @@ export class SqlEntityManager extends EntityManager {
|
|
|
68
68
|
merged.signal = opts.signal ?? fork?.signal;
|
|
69
69
|
merged.inflightQueryAbortStrategy = opts.inflightQueryAbortStrategy ?? fork?.inflightQueryAbortStrategy;
|
|
70
70
|
}
|
|
71
|
-
return this.getDriver().execute(query, params, opts.method ?? 'all',
|
|
71
|
+
return context.withSessionContext(context.getTransactionContext(), ctx => this.getDriver().execute(query, params, opts.method ?? 'all', ctx, merged));
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* @inheritDoc
|
|
@@ -81,7 +81,9 @@ export class SqlEntityManager extends EntityManager {
|
|
|
81
81
|
const { where: rawWhere, ...countOptions } = options;
|
|
82
82
|
await em.tryFlush(entityName, options);
|
|
83
83
|
const where = await em.processWhere(entityName, rawWhere ?? {}, options, 'read');
|
|
84
|
-
|
|
84
|
+
// match `em.count()` semantics: an active transaction always wins over the requested connection type
|
|
85
|
+
const connectionType = em.getTransactionContext() ? 'write' : options.connectionType;
|
|
86
|
+
const qb = em.createQueryBuilder(meta.class, undefined, connectionType);
|
|
85
87
|
qb
|
|
86
88
|
.select([...fields, raw('count(*) as cnt')])
|
|
87
89
|
.where(where)
|
|
@@ -14,6 +14,8 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
14
14
|
readonly "desc nulls first": 'is not null';
|
|
15
15
|
readonly "desc nulls last": 'is null';
|
|
16
16
|
};
|
|
17
|
+
/** mysql and mariadb treat null as the lowest value when no placement is requested. */
|
|
18
|
+
sortsNullsLowest(): boolean;
|
|
17
19
|
supportsMultiColumnCountDistinct(): boolean;
|
|
18
20
|
/** @internal */
|
|
19
21
|
createNativeQueryBuilder(): MySqlNativeQueryBuilder;
|
|
@@ -18,6 +18,10 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
18
18
|
[QueryOrder.desc_nulls_first]: 'is not null',
|
|
19
19
|
[QueryOrder.desc_nulls_last]: 'is null',
|
|
20
20
|
};
|
|
21
|
+
/** mysql and mariadb treat null as the lowest value when no placement is requested. */
|
|
22
|
+
sortsNullsLowest() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
21
25
|
supportsMultiColumnCountDistinct() {
|
|
22
26
|
return true;
|
|
23
27
|
}
|
|
@@ -16,6 +16,8 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
16
16
|
getEnumArrayCheckConstraintExpression(column: string, items: string[]): string;
|
|
17
17
|
supportsMaterializedViews(): boolean;
|
|
18
18
|
supportsPartitionedTables(): boolean;
|
|
19
|
+
supportsRowLevelSecurity(): boolean;
|
|
20
|
+
getCurrentSettingCast(mappedType: Type<unknown>): string | null;
|
|
19
21
|
supportsCustomPrimaryKeyNames(): boolean;
|
|
20
22
|
getCurrentTimestampSQL(length: number): string;
|
|
21
23
|
getDateTimeTypeDeclarationSQL(column: {
|
|
@@ -88,7 +90,7 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
88
90
|
}): string;
|
|
89
91
|
getBlobDeclarationSQL(): string;
|
|
90
92
|
getJsonDeclarationSQL(): string;
|
|
91
|
-
getSearchJsonPropertyKey(path: string[], type: string | undefined | Type, aliased: boolean, value?: unknown): string | RawQueryFragment;
|
|
93
|
+
getSearchJsonPropertyKey(path: string[], type: string | undefined | Type, aliased: boolean | string, value?: unknown): string | RawQueryFragment;
|
|
92
94
|
getJsonIndexDefinition(index: IndexDef): string[];
|
|
93
95
|
quoteIdentifier(id: string | {
|
|
94
96
|
toString: () => string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT, ARRAY_OPERATORS, raw, RawQueryFragment, Type, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ALIAS_REPLACEMENT, ARRAY_OPERATORS, BigIntType, BooleanType, DateTimeType, DateType, EnumType, IntegerType, raw, RawQueryFragment, SmallIntType, StringType, TextType, TimeType, TinyIntType, Type, Utils, UuidType, } from '@mikro-orm/core';
|
|
2
2
|
import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
|
|
3
3
|
import { PostgreSqlNativeQueryBuilder } from './PostgreSqlNativeQueryBuilder.js';
|
|
4
4
|
import { PostgreSqlSchemaHelper } from './PostgreSqlSchemaHelper.js';
|
|
@@ -33,6 +33,38 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
33
33
|
supportsPartitionedTables() {
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
|
+
supportsRowLevelSecurity() {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
getCurrentSettingCast(mappedType) {
|
|
40
|
+
if (mappedType instanceof UuidType) {
|
|
41
|
+
return '::uuid';
|
|
42
|
+
}
|
|
43
|
+
if (mappedType instanceof BigIntType) {
|
|
44
|
+
return '::bigint';
|
|
45
|
+
}
|
|
46
|
+
// MediumIntType extends IntegerType, so it is covered here too
|
|
47
|
+
if (mappedType instanceof IntegerType || mappedType instanceof SmallIntType || mappedType instanceof TinyIntType) {
|
|
48
|
+
return '::int';
|
|
49
|
+
}
|
|
50
|
+
if (mappedType instanceof BooleanType) {
|
|
51
|
+
return '::boolean';
|
|
52
|
+
}
|
|
53
|
+
if (mappedType instanceof DateTimeType) {
|
|
54
|
+
return '::timestamptz';
|
|
55
|
+
}
|
|
56
|
+
if (mappedType instanceof DateType) {
|
|
57
|
+
return '::date';
|
|
58
|
+
}
|
|
59
|
+
if (mappedType instanceof TimeType) {
|
|
60
|
+
return '::time';
|
|
61
|
+
}
|
|
62
|
+
// current_setting() returns text already, so string-compatible types need no cast (CharacterType extends StringType)
|
|
63
|
+
if (mappedType instanceof StringType || mappedType instanceof TextType || mappedType instanceof EnumType) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
36
68
|
supportsCustomPrimaryKeyNames() {
|
|
37
69
|
return true;
|
|
38
70
|
}
|
|
@@ -285,7 +317,8 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
285
317
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
286
318
|
const first = path.shift();
|
|
287
319
|
const last = path.pop();
|
|
288
|
-
const
|
|
320
|
+
const alias = typeof aliased === 'string' ? aliased : ALIAS_REPLACEMENT;
|
|
321
|
+
const root = this.quoteIdentifier(aliased ? `${alias}.${first}` : first);
|
|
289
322
|
type = typeof type === 'string' ? this.getMappedType(type).runtimeType : String(type);
|
|
290
323
|
const cast = (key) => raw(type in this.#jsonTypeCasts ? `(${key})::${this.#jsonTypeCasts[type]}` : key);
|
|
291
324
|
let lastOperator = '->>';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeadlockException, ExceptionConverter, ForeignKeyConstraintViolationException, InvalidFieldNameException, NonUniqueFieldNameException, NotNullConstraintViolationException, SyntaxErrorException, TableExistsException, TableNotFoundException, UniqueConstraintViolationException, CheckConstraintViolationException, } from '@mikro-orm/core';
|
|
1
|
+
import { DeadlockException, ExceptionConverter, ForeignKeyConstraintViolationException, InvalidFieldNameException, NonUniqueFieldNameException, NotNullConstraintViolationException, RowLevelSecurityViolationException, SyntaxErrorException, TableExistsException, TableNotFoundException, UniqueConstraintViolationException, CheckConstraintViolationException, } from '@mikro-orm/core';
|
|
2
2
|
export class PostgreSqlExceptionConverter extends ExceptionConverter {
|
|
3
3
|
/**
|
|
4
4
|
* @see http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html
|
|
@@ -31,6 +31,13 @@ export class PostgreSqlExceptionConverter extends ExceptionConverter {
|
|
|
31
31
|
return new UniqueConstraintViolationException(exception);
|
|
32
32
|
case '23514':
|
|
33
33
|
return new CheckConstraintViolationException(exception);
|
|
34
|
+
case '42501':
|
|
35
|
+
// 42501 is generic insufficient_privilege; only RLS write violations carry this message — the `routine`
|
|
36
|
+
// field covers servers with a non-english `lc_messages`, where the message check cannot match
|
|
37
|
+
if (exception.message.includes('row-level security policy') || exception.routine === 'ExecWithCheckOptions') {
|
|
38
|
+
return new RowLevelSecurityViolationException(exception);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
34
41
|
case '42601':
|
|
35
42
|
return new SyntaxErrorException(exception);
|
|
36
43
|
case '42702':
|