@mikro-orm/knex 7.0.0-dev.34 → 7.0.0-dev.36
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 +2 -0
- package/AbstractSqlConnection.js +42 -12
- package/AbstractSqlDriver.d.ts +7 -2
- package/AbstractSqlDriver.js +117 -57
- package/package.json +3 -3
- package/query/QueryBuilder.d.ts +51 -4
- package/query/QueryBuilder.js +101 -25
- package/schema/DatabaseSchema.js +7 -2
- package/schema/DatabaseTable.js +27 -26
- package/schema/SchemaComparator.js +1 -1
- package/schema/SchemaHelper.js +1 -1
- package/typings.d.ts +0 -1
|
@@ -42,7 +42,9 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
42
42
|
}): Promise<ControlledTransaction<any, any>>;
|
|
43
43
|
commit(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
44
44
|
rollback(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
45
|
+
private prepareQuery;
|
|
45
46
|
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>;
|
|
47
|
+
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], ctx?: Transaction<Kysely<any>>, loggerContext?: LoggingOptions): AsyncIterableIterator<T>;
|
|
46
48
|
/**
|
|
47
49
|
* Execute raw SQL queries from file
|
|
48
50
|
*/
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CompiledQuery, Kysely
|
|
1
|
+
import { CompiledQuery, Kysely } from 'kysely';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { Connection, EventType, RawQueryFragment, } from '@mikro-orm/core';
|
|
3
|
+
import { Connection, EventType, RawQueryFragment, Utils, } from '@mikro-orm/core';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export class AbstractSqlConnection extends Connection {
|
|
6
6
|
client;
|
|
@@ -135,8 +135,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
135
135
|
}
|
|
136
136
|
await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
await this.ensureConnection();
|
|
138
|
+
prepareQuery(query, params = []) {
|
|
140
139
|
if (query instanceof NativeQueryBuilder) {
|
|
141
140
|
query = query.toRaw();
|
|
142
141
|
}
|
|
@@ -146,16 +145,47 @@ export class AbstractSqlConnection extends Connection {
|
|
|
146
145
|
}
|
|
147
146
|
query = this.config.get('onQuery')(query, params);
|
|
148
147
|
const formatted = this.platform.formatQuery(query, params);
|
|
149
|
-
|
|
148
|
+
return { query, params, formatted };
|
|
149
|
+
}
|
|
150
|
+
async execute(query, params = [], method = 'all', ctx, loggerContext) {
|
|
151
|
+
await this.ensureConnection();
|
|
152
|
+
const q = this.prepareQuery(query, params);
|
|
153
|
+
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
150
154
|
return this.executeQuery(sql, async () => {
|
|
151
|
-
const compiled = CompiledQuery.raw(formatted);
|
|
152
|
-
|
|
153
|
-
const res = await ctx.executeQuery(compiled);
|
|
154
|
-
return this.transformRawResult(res, method);
|
|
155
|
-
}
|
|
156
|
-
const res = await this.client.executeQuery(compiled);
|
|
155
|
+
const compiled = CompiledQuery.raw(q.formatted);
|
|
156
|
+
const res = await (ctx ?? this.client).executeQuery(compiled);
|
|
157
157
|
return this.transformRawResult(res, method);
|
|
158
|
-
}, {
|
|
158
|
+
}, { ...q, ...loggerContext });
|
|
159
|
+
}
|
|
160
|
+
async *stream(query, params = [], ctx, loggerContext) {
|
|
161
|
+
await this.ensureConnection();
|
|
162
|
+
const q = this.prepareQuery(query, params);
|
|
163
|
+
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
164
|
+
// construct the compiled query manually with `kind: 'SelectQueryNode'` to avoid sqlite validation for select queries when streaming
|
|
165
|
+
const compiled = {
|
|
166
|
+
query: {
|
|
167
|
+
kind: 'SelectQueryNode',
|
|
168
|
+
},
|
|
169
|
+
sql: q.formatted,
|
|
170
|
+
parameters: [],
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
const res = (ctx ?? this.client).getExecutor().stream(compiled, 1);
|
|
174
|
+
this.logQuery(sql, {
|
|
175
|
+
sql, params,
|
|
176
|
+
...loggerContext,
|
|
177
|
+
affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
|
|
178
|
+
});
|
|
179
|
+
for await (const items of res) {
|
|
180
|
+
for (const row of this.transformRawResult(items, 'all')) {
|
|
181
|
+
yield row;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
this.logQuery(sql, { sql, params, ...loggerContext, level: 'error' });
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
159
189
|
}
|
|
160
190
|
/**
|
|
161
191
|
* Execute raw SQL queries from file
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type ObjectQuery, type Options, type OrderDefinition, type PopulateOptions, type PopulatePath, type Primary, type QueryOrderMap, type QueryResult, RawQueryFragment, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
|
|
1
|
+
import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type ObjectQuery, type Options, type OrderDefinition, type PopulateOptions, type PopulatePath, type Primary, type QueryOrderMap, type QueryResult, RawQueryFragment, type StreamOptions, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlConnection } from './AbstractSqlConnection.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
|
|
4
4
|
import { QueryBuilder } from './query/QueryBuilder.js';
|
|
@@ -14,13 +14,16 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
14
14
|
protected constructor(config: Configuration, platform: Platform, connection: Constructor<Connection>, connector: string[]);
|
|
15
15
|
getPlatform(): Platform;
|
|
16
16
|
createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
|
|
17
|
+
private createQueryBuilderFromOptions;
|
|
17
18
|
find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
|
|
18
19
|
findOne<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
|
|
19
20
|
protected hasToManyJoins<T extends object>(hint: PopulateOptions<T>, meta: EntityMetadata<T>): boolean;
|
|
20
21
|
findVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
|
|
21
22
|
countVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: CountOptions<T, any>): Promise<number>;
|
|
22
23
|
protected findFromVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
|
|
24
|
+
protected streamFromVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T, any>): AsyncIterableIterator<EntityData<T>>;
|
|
23
25
|
protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
|
|
26
|
+
protected wrapVirtualExpressionInSubqueryStream<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>, type: QueryType.SELECT): AsyncIterableIterator<T>;
|
|
24
27
|
mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
|
|
25
28
|
private mapJoinedProps;
|
|
26
29
|
count<T extends object>(entityName: string, where: any, options?: CountOptions<T>): Promise<number>;
|
|
@@ -39,6 +42,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
39
42
|
loadFromPivotTable<T extends object, O extends object>(prop: EntityProperty, owners: Primary<O>[][], where?: FilterQuery<any>, orderBy?: OrderDefinition<T>, ctx?: Transaction, options?: FindOptions<T, any, any, any>, pivotJoin?: boolean): Promise<Dictionary<T[]>>;
|
|
40
43
|
private getPivotOrderBy;
|
|
41
44
|
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>;
|
|
45
|
+
stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T, any, any, any>): AsyncIterableIterator<T>;
|
|
42
46
|
/**
|
|
43
47
|
* 1:1 owner side needs to be marked for population so QB auto-joins the owner id
|
|
44
48
|
*/
|
|
@@ -53,11 +57,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
53
57
|
* @internal
|
|
54
58
|
*/
|
|
55
59
|
mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
|
|
60
|
+
protected shouldHaveColumn<T, U>(meta: EntityMetadata<T>, prop: EntityProperty<U>, populate: readonly PopulateOptions<U>[], fields?: readonly Field<U>[], exclude?: readonly Field<U>[]): boolean;
|
|
56
61
|
protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): Field<T>[];
|
|
57
62
|
/**
|
|
58
63
|
* @internal
|
|
59
64
|
*/
|
|
60
|
-
mapPropToFieldNames<T extends object>(qb: QueryBuilder<T, any, any, any>, prop: EntityProperty<T>, tableAlias: string): Field<T>[];
|
|
65
|
+
mapPropToFieldNames<T extends object>(qb: QueryBuilder<T, any, any, any>, prop: EntityProperty<T>, tableAlias: string, explicitFields?: readonly Field<T>[]): Field<T>[];
|
|
61
66
|
/** @internal */
|
|
62
67
|
createQueryBuilder<T extends object>(entityName: EntityName<T> | QueryBuilder<T, any, any, any>, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggingOptions, alias?: string, em?: SqlEntityManager): QueryBuilder<T, any, any, any>;
|
|
63
68
|
protected resolveConnectionType(args: {
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT_RE, DatabaseDriver, EntityManagerType, getOnConflictFields, getOnConflictReturningFields, helper, isRaw, LoadStrategy, parseJsonSafe, QueryFlag, QueryHelper, QueryOrder, raw, RawQueryFragment, ReferenceKind, Utils,
|
|
1
|
+
import { ALIAS_REPLACEMENT_RE, DatabaseDriver, EntityManagerType, getLoadingStrategy, getOnConflictFields, getOnConflictReturningFields, helper, isRaw, LoadStrategy, parseJsonSafe, QueryFlag, QueryHelper, QueryOrder, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { QueryBuilder } from './query/QueryBuilder.js';
|
|
3
3
|
import { JoinType, QueryType } from './query/enums.js';
|
|
4
4
|
import { SqlEntityManager } from './SqlEntityManager.js';
|
|
@@ -21,15 +21,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
21
21
|
const EntityManagerClass = this.config.get('entityManager', SqlEntityManager);
|
|
22
22
|
return new EntityManagerClass(this.config, this, this.metadata, useContext);
|
|
23
23
|
}
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
-
const meta = this.metadata.find(entityName);
|
|
27
|
-
if (meta?.virtual) {
|
|
28
|
-
return this.findVirtual(entityName, where, options);
|
|
29
|
-
}
|
|
24
|
+
async createQueryBuilderFromOptions(meta, where, options = {}) {
|
|
25
|
+
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
30
26
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
31
27
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
32
|
-
const qb = this.createQueryBuilder(
|
|
28
|
+
const qb = this.createQueryBuilder(meta.className, options.ctx, connectionType, false, options.logging, undefined, options.em);
|
|
33
29
|
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
|
|
34
30
|
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
35
31
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
@@ -66,8 +62,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
66
62
|
if (options.em) {
|
|
67
63
|
await qb.applyJoinedFilters(options.em, options.filters);
|
|
68
64
|
}
|
|
65
|
+
return qb;
|
|
66
|
+
}
|
|
67
|
+
async find(entityName, where, options = {}) {
|
|
68
|
+
options = { populate: [], orderBy: [], ...options };
|
|
69
|
+
const meta = this.metadata.find(entityName);
|
|
70
|
+
if (meta?.virtual) {
|
|
71
|
+
return this.findVirtual(entityName, where, options);
|
|
72
|
+
}
|
|
73
|
+
const qb = await this.createQueryBuilderFromOptions(meta, where, options);
|
|
69
74
|
const result = await this.rethrow(qb.execute('all'));
|
|
70
|
-
if (
|
|
75
|
+
if (options.last && !options.first) {
|
|
71
76
|
result.reverse();
|
|
72
77
|
}
|
|
73
78
|
return result;
|
|
@@ -127,35 +132,49 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
127
132
|
const expr = this.platform.formatQuery(res.sql, res.params);
|
|
128
133
|
return this.wrapVirtualExpressionInSubquery(meta, expr, where, options, type);
|
|
129
134
|
}
|
|
130
|
-
/* v8 ignore next */
|
|
135
|
+
/* v8 ignore next 2 */
|
|
131
136
|
return res;
|
|
132
137
|
}
|
|
133
|
-
async
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
qb.where(where);
|
|
139
|
-
const { first, last, before, after } = options;
|
|
140
|
-
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
141
|
-
if (type !== QueryType.COUNT) {
|
|
142
|
-
if (options.orderBy) {
|
|
143
|
-
if (isCursorPagination) {
|
|
144
|
-
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, options.orderBy);
|
|
145
|
-
qb.andWhere(where).orderBy(newOrderBy);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
qb.orderBy(options.orderBy);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
qb.limit(options?.limit, options?.offset);
|
|
138
|
+
async *streamFromVirtual(entityName, where, options) {
|
|
139
|
+
const meta = this.metadata.get(entityName);
|
|
140
|
+
/* v8 ignore next 3 */
|
|
141
|
+
if (!meta.expression) {
|
|
142
|
+
return;
|
|
152
143
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
if (typeof meta.expression === 'string') {
|
|
145
|
+
yield* this.wrapVirtualExpressionInSubqueryStream(meta, meta.expression, where, options, QueryType.SELECT);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const em = this.createEntityManager();
|
|
149
|
+
em.setTransactionContext(options.ctx);
|
|
150
|
+
const res = meta.expression(em, where, options, true);
|
|
151
|
+
if (typeof res === 'string') {
|
|
152
|
+
yield* this.wrapVirtualExpressionInSubqueryStream(meta, res, where, options, QueryType.SELECT);
|
|
153
|
+
return;
|
|
156
154
|
}
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
if (res instanceof QueryBuilder) {
|
|
156
|
+
yield* this.wrapVirtualExpressionInSubqueryStream(meta, res.getFormattedQuery(), where, options, QueryType.SELECT);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (res instanceof RawQueryFragment) {
|
|
160
|
+
const expr = this.platform.formatQuery(res.sql, res.params);
|
|
161
|
+
yield* this.wrapVirtualExpressionInSubqueryStream(meta, expr, where, options, QueryType.SELECT);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
/* v8 ignore next 2 */
|
|
165
|
+
yield* res;
|
|
166
|
+
}
|
|
167
|
+
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
|
|
168
|
+
const qb = await this.createQueryBuilderFromOptions(meta, where, options);
|
|
169
|
+
qb.setFlag(QueryFlag.DISABLE_PAGINATE);
|
|
170
|
+
const isCursorPagination = [options.first, options.last, options.before, options.after].some(v => v != null);
|
|
171
|
+
const native = qb.getNativeQuery(false);
|
|
172
|
+
if (type === QueryType.COUNT) {
|
|
173
|
+
native
|
|
174
|
+
.clear('select')
|
|
175
|
+
.clear('limit')
|
|
176
|
+
.clear('offset')
|
|
177
|
+
.count();
|
|
159
178
|
}
|
|
160
179
|
native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
|
|
161
180
|
const query = native.compile();
|
|
@@ -163,11 +182,23 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
163
182
|
if (type === QueryType.COUNT) {
|
|
164
183
|
return res[0].count;
|
|
165
184
|
}
|
|
166
|
-
if (isCursorPagination && !first && !!last) {
|
|
185
|
+
if (isCursorPagination && !options.first && !!options.last) {
|
|
167
186
|
res.reverse();
|
|
168
187
|
}
|
|
169
188
|
return res.map(row => this.mapResult(row, meta));
|
|
170
189
|
}
|
|
190
|
+
async *wrapVirtualExpressionInSubqueryStream(meta, expression, where, options, type) {
|
|
191
|
+
const qb = await this.createQueryBuilderFromOptions(meta, where, options);
|
|
192
|
+
qb.unsetFlag(QueryFlag.DISABLE_PAGINATE);
|
|
193
|
+
const native = qb.getNativeQuery(false);
|
|
194
|
+
native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
|
|
195
|
+
const query = native.compile();
|
|
196
|
+
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
197
|
+
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, options.loggerContext);
|
|
198
|
+
for await (const row of res) {
|
|
199
|
+
yield this.mapResult(row, meta);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
171
202
|
mapResult(result, meta, populate = [], qb, map = {}) {
|
|
172
203
|
const ret = super.mapResult(result, meta);
|
|
173
204
|
/* v8 ignore next 3 */
|
|
@@ -221,9 +252,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
221
252
|
}
|
|
222
253
|
return;
|
|
223
254
|
}
|
|
255
|
+
const mapToPk = !!(ref || prop.mapToPk);
|
|
256
|
+
const targetProps = mapToPk
|
|
257
|
+
? meta2.getPrimaryProps()
|
|
258
|
+
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
224
259
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
225
260
|
// and therefore we don't return any record (since all values would be null)
|
|
226
|
-
const hasPK = meta2.
|
|
261
|
+
const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => {
|
|
227
262
|
return root[`${relationAlias}__${name}`] != null;
|
|
228
263
|
}));
|
|
229
264
|
if (!hasPK) {
|
|
@@ -233,6 +268,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
233
268
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
234
269
|
result[prop.name] = null;
|
|
235
270
|
}
|
|
271
|
+
for (const prop of targetProps) {
|
|
272
|
+
for (const name of prop.fieldNames) {
|
|
273
|
+
delete root[`${relationAlias}__${name}`];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
236
276
|
return;
|
|
237
277
|
}
|
|
238
278
|
let relationPojo = {};
|
|
@@ -248,10 +288,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
248
288
|
relationPojo[prop.name] = root[alias];
|
|
249
289
|
}
|
|
250
290
|
});
|
|
251
|
-
const mapToPk = !!(ref || prop.mapToPk);
|
|
252
|
-
const targetProps = mapToPk
|
|
253
|
-
? meta2.getPrimaryProps()
|
|
254
|
-
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
255
291
|
const tz = this.platform.getTimezone();
|
|
256
292
|
for (const prop of targetProps) {
|
|
257
293
|
if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
|
|
@@ -292,7 +328,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
292
328
|
// properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
|
|
293
329
|
// so we need to delete them after everything is mapped from given level
|
|
294
330
|
for (const prop of targetProps) {
|
|
295
|
-
|
|
331
|
+
for (const name of prop.fieldNames) {
|
|
332
|
+
delete root[`${relationAlias}__${name}`];
|
|
333
|
+
}
|
|
296
334
|
}
|
|
297
335
|
if (mapToPk) {
|
|
298
336
|
const tmp = Object.values(relationPojo);
|
|
@@ -825,6 +863,24 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
825
863
|
async execute(query, params = [], method = 'all', ctx, loggerContext) {
|
|
826
864
|
return this.rethrow(this.connection.execute(query, params, method, ctx, loggerContext));
|
|
827
865
|
}
|
|
866
|
+
async *stream(entityName, where, options) {
|
|
867
|
+
options = { populate: [], orderBy: [], ...options };
|
|
868
|
+
const meta = this.metadata.find(entityName);
|
|
869
|
+
if (meta?.virtual) {
|
|
870
|
+
yield* this.streamFromVirtual(entityName, where, options);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const qb = await this.createQueryBuilderFromOptions(meta, where, options);
|
|
874
|
+
try {
|
|
875
|
+
const result = qb.stream(options);
|
|
876
|
+
for await (const item of result) {
|
|
877
|
+
yield item;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
catch (e) {
|
|
881
|
+
throw this.convertException(e);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
828
884
|
/**
|
|
829
885
|
* 1:1 owner side needs to be marked for population so QB auto-joins the owner id
|
|
830
886
|
*/
|
|
@@ -919,26 +975,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
919
975
|
}
|
|
920
976
|
return res;
|
|
921
977
|
}
|
|
978
|
+
shouldHaveColumn(meta, prop, populate, fields, exclude) {
|
|
979
|
+
if (!this.platform.shouldHaveColumn(prop, populate, exclude)) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
986
|
+
}
|
|
922
987
|
getFieldsForJoinedLoad(qb, meta, options) {
|
|
923
988
|
const fields = [];
|
|
924
989
|
const populate = options.populate ?? [];
|
|
925
990
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
926
|
-
const shouldHaveColumn = (meta, prop, populate, fields) => {
|
|
927
|
-
if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
|
|
931
|
-
return true;
|
|
932
|
-
}
|
|
933
|
-
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
934
|
-
};
|
|
935
991
|
const populateWhereAll = options?._populateWhere === 'all' || Utils.isEmpty(options?._populateWhere);
|
|
936
992
|
// root entity is already handled, skip that
|
|
937
993
|
if (options.parentJoinPath) {
|
|
938
994
|
// alias all fields in the primary table
|
|
939
995
|
meta.props
|
|
940
|
-
.filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
|
|
941
|
-
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias)));
|
|
996
|
+
.filter(prop => this.shouldHaveColumn(meta, prop, populate, options.explicitFields, options.exclude))
|
|
997
|
+
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias, options.explicitFields)));
|
|
942
998
|
}
|
|
943
999
|
for (const hint of joinedProps) {
|
|
944
1000
|
const [propName, ref] = hint.field.split(':', 2);
|
|
@@ -1003,14 +1059,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1003
1059
|
/**
|
|
1004
1060
|
* @internal
|
|
1005
1061
|
*/
|
|
1006
|
-
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
1062
|
+
mapPropToFieldNames(qb, prop, tableAlias, explicitFields) {
|
|
1007
1063
|
if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
|
|
1008
|
-
return Object.
|
|
1009
|
-
|
|
1064
|
+
return Object.entries(prop.embeddedProps).flatMap(([name, childProp]) => {
|
|
1065
|
+
const childFields = explicitFields ? Utils.extractChildElements(explicitFields, prop.name) : [];
|
|
1066
|
+
if (childFields.length > 0 && !this.shouldHaveColumn(prop.targetMeta, { ...childProp, name }, [], childFields)) {
|
|
1067
|
+
return [];
|
|
1068
|
+
}
|
|
1069
|
+
return this.mapPropToFieldNames(qb, childProp, tableAlias, childFields);
|
|
1010
1070
|
});
|
|
1011
1071
|
}
|
|
1012
1072
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
|
|
1013
|
-
if (prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
|
|
1073
|
+
if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
|
|
1014
1074
|
return prop.fieldNames.map((col, idx) => {
|
|
1015
1075
|
if (!prop.customTypes[idx]?.convertToJSValueSQL) {
|
|
1016
1076
|
return col;
|
|
@@ -1295,7 +1355,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1295
1355
|
}
|
|
1296
1356
|
}
|
|
1297
1357
|
else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
|
|
1298
|
-
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
|
|
1358
|
+
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false, false));
|
|
1299
1359
|
ret.push(...props.filter(p => !lazyProps.includes(p)).map(p => p.name));
|
|
1300
1360
|
addFormulas = true;
|
|
1301
1361
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.36",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"sqlstring": "2.3.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^6.5.
|
|
57
|
+
"@mikro-orm/core": "^6.5.8"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.0.0-dev.36"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -11,6 +11,30 @@ export interface ExecuteOptions {
|
|
|
11
11
|
mapResults?: boolean;
|
|
12
12
|
mergeResults?: boolean;
|
|
13
13
|
}
|
|
14
|
+
export interface QBStreamOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Results are mapped to entities, if you set `mapResults: false` you will get POJOs instead.
|
|
17
|
+
*
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
mapResults?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* When populating to-many relations, the ORM streams fully merged entities instead of yielding every row.
|
|
23
|
+
* You can opt out of this behavior by specifying `mergeResults: false`. This will yield every row from
|
|
24
|
+
* the SQL result, but still mapped to entities, meaning that to-many collections will contain at most
|
|
25
|
+
* one item, and you will get duplicate root entities when they have multiple items in the populated
|
|
26
|
+
* collection.
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
mergeResults?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* When enabled, the driver will return the raw database results without renaming the fields to match the entity property names.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
rawResults?: boolean;
|
|
37
|
+
}
|
|
14
38
|
type AnyString = string & {};
|
|
15
39
|
type Compute<T> = {
|
|
16
40
|
[K in keyof T]: T[K];
|
|
@@ -158,6 +182,8 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
158
182
|
orWhere(cond: QBFilterQuery<Entity>): this;
|
|
159
183
|
orWhere(cond: string, params?: any[]): this;
|
|
160
184
|
orderBy(orderBy: QBQueryOrderMap<Entity> | QBQueryOrderMap<Entity>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context>;
|
|
185
|
+
andOrderBy(orderBy: QBQueryOrderMap<Entity> | QBQueryOrderMap<Entity>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context>;
|
|
186
|
+
private processOrderBy;
|
|
161
187
|
groupBy(fields: EntityKeyOrString<Entity> | readonly EntityKeyOrString<Entity>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context>;
|
|
162
188
|
having(cond?: QBFilterQuery | string, params?: any[], operator?: keyof typeof GroupOperator): SelectQueryBuilder<Entity, RootAlias, Hint, Context>;
|
|
163
189
|
andHaving(cond?: QBFilterQuery | string, params?: any[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context>;
|
|
@@ -182,17 +208,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
182
208
|
/**
|
|
183
209
|
* Adds index hint to the FROM clause.
|
|
184
210
|
*/
|
|
185
|
-
indexHint(sql: string): this;
|
|
211
|
+
indexHint(sql: string | undefined): this;
|
|
186
212
|
/**
|
|
187
213
|
* Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
|
|
188
214
|
*/
|
|
189
|
-
comment(comment: string | string[]): this;
|
|
215
|
+
comment(comment: string | string[] | undefined): this;
|
|
190
216
|
/**
|
|
191
217
|
* Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
|
|
192
218
|
* Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
|
|
193
219
|
* are ignored as simple comments.
|
|
194
220
|
*/
|
|
195
|
-
hintComment(comment: string | string[]): this;
|
|
221
|
+
hintComment(comment: string | string[] | undefined): this;
|
|
196
222
|
/**
|
|
197
223
|
* Specifies FROM which entity's table select/update/delete will be executed, removing all previously set FROM-s.
|
|
198
224
|
* Allows setting a main string alias of the selection data.
|
|
@@ -245,14 +271,35 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
245
271
|
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
246
272
|
*/
|
|
247
273
|
execute<U = any>(method?: 'all' | 'get' | 'run', options?: ExecuteOptions | boolean): Promise<U>;
|
|
274
|
+
private getConnection;
|
|
275
|
+
/**
|
|
276
|
+
* Executes the query and returns an async iterable (async generator) that yields results one by one.
|
|
277
|
+
* By default, the results are merged and mapped to entity instances, without adding them to the identity map.
|
|
278
|
+
* You can disable merging and mapping by passing the options `{ mergeResults: false, mapResults: false }`.
|
|
279
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
280
|
+
*
|
|
281
|
+
* ```ts
|
|
282
|
+
* const qb = em.createQueryBuilder(Book, 'b');
|
|
283
|
+
* qb.select('*').where({ title: '1984' }).leftJoinAndSelect('b.author', 'a');
|
|
284
|
+
*
|
|
285
|
+
* for await (const book of qb.stream()) {
|
|
286
|
+
* // book is an instance of Book entity
|
|
287
|
+
* console.log(book.title, book.author.name);
|
|
288
|
+
* }
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
stream(options?: QBStreamOptions): AsyncIterableIterator<Loaded<Entity, Hint>>;
|
|
248
292
|
/**
|
|
249
293
|
* Alias for `qb.getResultList()`
|
|
250
294
|
*/
|
|
251
295
|
getResult(): Promise<Loaded<Entity, Hint>[]>;
|
|
252
296
|
/**
|
|
253
|
-
* Executes the query, returning array of results
|
|
297
|
+
* Executes the query, returning array of results mapped to entity instances.
|
|
254
298
|
*/
|
|
255
299
|
getResultList(limit?: number): Promise<Loaded<Entity, Hint>[]>;
|
|
300
|
+
private propagatePopulateHint;
|
|
301
|
+
private mapResult;
|
|
302
|
+
private mapResults;
|
|
256
303
|
/**
|
|
257
304
|
* Executes the query, returning the first result or null
|
|
258
305
|
*/
|
package/query/QueryBuilder.js
CHANGED
|
@@ -359,8 +359,16 @@ export class QueryBuilder {
|
|
|
359
359
|
return this.where(cond, params, '$or');
|
|
360
360
|
}
|
|
361
361
|
orderBy(orderBy) {
|
|
362
|
+
return this.processOrderBy(orderBy, true);
|
|
363
|
+
}
|
|
364
|
+
andOrderBy(orderBy) {
|
|
365
|
+
return this.processOrderBy(orderBy, false);
|
|
366
|
+
}
|
|
367
|
+
processOrderBy(orderBy, reset = true) {
|
|
362
368
|
this.ensureNotFinalized();
|
|
363
|
-
|
|
369
|
+
if (reset) {
|
|
370
|
+
this._orderBy = [];
|
|
371
|
+
}
|
|
364
372
|
Utils.asArray(orderBy).forEach(o => {
|
|
365
373
|
const processed = QueryHelper.processWhere({
|
|
366
374
|
where: o,
|
|
@@ -676,7 +684,7 @@ export class QueryBuilder {
|
|
|
676
684
|
options.mapResults ??= true;
|
|
677
685
|
const isRunType = [QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE, QueryType.TRUNCATE].includes(this.type);
|
|
678
686
|
method ??= isRunType ? 'run' : 'all';
|
|
679
|
-
if (!this.connectionType && isRunType) {
|
|
687
|
+
if (!this.connectionType && (isRunType || this.context)) {
|
|
680
688
|
this.connectionType = 'write';
|
|
681
689
|
}
|
|
682
690
|
if (!this.finalized && method === 'get' && this.type === QueryType.SELECT) {
|
|
@@ -687,10 +695,8 @@ export class QueryBuilder {
|
|
|
687
695
|
if (cached?.data !== undefined) {
|
|
688
696
|
return cached.data;
|
|
689
697
|
}
|
|
690
|
-
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
691
|
-
const type = this.connectionType || (write ? 'write' : 'read');
|
|
692
698
|
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
693
|
-
const res = await this.
|
|
699
|
+
const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
|
|
694
700
|
const meta = this.mainAlias.metadata;
|
|
695
701
|
if (!options.mapResults || !meta) {
|
|
696
702
|
await this.em?.storeCache(this._cache, cached, res);
|
|
@@ -718,6 +724,64 @@ export class QueryBuilder {
|
|
|
718
724
|
await this.em?.storeCache(this._cache, cached, mapped);
|
|
719
725
|
return mapped;
|
|
720
726
|
}
|
|
727
|
+
getConnection() {
|
|
728
|
+
const write = !this.platform.getConfig().get('preferReadReplicas');
|
|
729
|
+
const type = this.connectionType || (write ? 'write' : 'read');
|
|
730
|
+
return this.driver.getConnection(type);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Executes the query and returns an async iterable (async generator) that yields results one by one.
|
|
734
|
+
* By default, the results are merged and mapped to entity instances, without adding them to the identity map.
|
|
735
|
+
* You can disable merging and mapping by passing the options `{ mergeResults: false, mapResults: false }`.
|
|
736
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
737
|
+
*
|
|
738
|
+
* ```ts
|
|
739
|
+
* const qb = em.createQueryBuilder(Book, 'b');
|
|
740
|
+
* qb.select('*').where({ title: '1984' }).leftJoinAndSelect('b.author', 'a');
|
|
741
|
+
*
|
|
742
|
+
* for await (const book of qb.stream()) {
|
|
743
|
+
* // book is an instance of Book entity
|
|
744
|
+
* console.log(book.title, book.author.name);
|
|
745
|
+
* }
|
|
746
|
+
* ```
|
|
747
|
+
*/
|
|
748
|
+
async *stream(options) {
|
|
749
|
+
options ??= {};
|
|
750
|
+
options.mergeResults ??= true;
|
|
751
|
+
options.mapResults ??= true;
|
|
752
|
+
const query = this.toQuery();
|
|
753
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
754
|
+
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
|
|
755
|
+
const meta = this.mainAlias.metadata;
|
|
756
|
+
if (options.rawResults || !meta) {
|
|
757
|
+
yield* res;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const joinedProps = this.driver.joinedProps(meta, this._populate);
|
|
761
|
+
const stack = [];
|
|
762
|
+
const hash = (data) => {
|
|
763
|
+
return Utils.getPrimaryKeyHash(meta.primaryKeys.map(pk => data[pk]));
|
|
764
|
+
};
|
|
765
|
+
for await (const row of res) {
|
|
766
|
+
const mapped = this.driver.mapResult(row, meta, this._populate, this);
|
|
767
|
+
if (!options.mergeResults || joinedProps.length === 0) {
|
|
768
|
+
yield this.mapResult(mapped, options.mapResults);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
if (stack.length > 0 && hash(stack[stack.length - 1]) !== hash(mapped)) {
|
|
772
|
+
const res = this.driver.mergeJoinedResult(stack, this.mainAlias.metadata, joinedProps);
|
|
773
|
+
for (const row of res) {
|
|
774
|
+
yield this.mapResult(row, options.mapResults);
|
|
775
|
+
}
|
|
776
|
+
stack.length = 0;
|
|
777
|
+
}
|
|
778
|
+
stack.push(mapped);
|
|
779
|
+
}
|
|
780
|
+
if (stack.length > 0) {
|
|
781
|
+
const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.metadata, joinedProps);
|
|
782
|
+
yield this.mapResult(merged[0], options.mapResults);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
721
785
|
/**
|
|
722
786
|
* Alias for `qb.getResultList()`
|
|
723
787
|
*/
|
|
@@ -725,29 +789,40 @@ export class QueryBuilder {
|
|
|
725
789
|
return this.getResultList();
|
|
726
790
|
}
|
|
727
791
|
/**
|
|
728
|
-
* Executes the query, returning array of results
|
|
792
|
+
* Executes the query, returning array of results mapped to entity instances.
|
|
729
793
|
*/
|
|
730
794
|
async getResultList(limit) {
|
|
731
795
|
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
732
796
|
const res = await this.execute('all', true);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
797
|
+
return this.mapResults(res, limit);
|
|
798
|
+
}
|
|
799
|
+
propagatePopulateHint(entity, hint) {
|
|
800
|
+
helper(entity).__serializationContext.populate = hint.concat(helper(entity).__serializationContext.populate ?? []);
|
|
801
|
+
hint.forEach(hint => {
|
|
802
|
+
const [propName] = hint.field.split(':', 2);
|
|
803
|
+
const value = Reference.unwrapReference(entity[propName]);
|
|
804
|
+
if (Utils.isEntity(value)) {
|
|
805
|
+
this.propagatePopulateHint(value, hint.children ?? []);
|
|
806
|
+
}
|
|
807
|
+
else if (Utils.isCollection(value)) {
|
|
808
|
+
value.populated();
|
|
809
|
+
value.getItems(false).forEach(item => this.propagatePopulateHint(item, hint.children ?? []));
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
mapResult(row, map = true) {
|
|
814
|
+
if (!map) {
|
|
815
|
+
return row;
|
|
747
816
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
817
|
+
const entity = this.em.map(this.mainAlias.entityName, row, { schema: this._schema });
|
|
818
|
+
this.propagatePopulateHint(entity, this._populate);
|
|
819
|
+
return entity;
|
|
820
|
+
}
|
|
821
|
+
mapResults(res, limit) {
|
|
822
|
+
const entities = [];
|
|
823
|
+
for (const row of res) {
|
|
824
|
+
const entity = this.mapResult(row);
|
|
825
|
+
this.propagatePopulateHint(entity, this._populate);
|
|
751
826
|
entities.push(entity);
|
|
752
827
|
if (limit != null && --limit === 0) {
|
|
753
828
|
break;
|
|
@@ -1145,7 +1220,7 @@ export class QueryBuilder {
|
|
|
1145
1220
|
if (!this.flags.has(QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
1146
1221
|
this.flags.add(QueryFlag.PAGINATE);
|
|
1147
1222
|
}
|
|
1148
|
-
if (meta && this.flags.has(QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
1223
|
+
if (meta && this.flags.has(QueryFlag.PAGINATE) && !this.flags.has(QueryFlag.DISABLE_PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
1149
1224
|
this.wrapPaginateSubQuery(meta);
|
|
1150
1225
|
}
|
|
1151
1226
|
if (meta && (this.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
@@ -1267,10 +1342,11 @@ export class QueryBuilder {
|
|
|
1267
1342
|
}
|
|
1268
1343
|
else if (join.parent?.type === JoinType.nestedInnerJoin) {
|
|
1269
1344
|
const group = lookupParentGroup(join.parent);
|
|
1345
|
+
const nested = group ?? ((join.parent).nested ??= new Set());
|
|
1270
1346
|
join.type = join.type === JoinType.innerJoin
|
|
1271
1347
|
? JoinType.nestedInnerJoin
|
|
1272
1348
|
: JoinType.nestedLeftJoin;
|
|
1273
|
-
|
|
1349
|
+
nested.add(join);
|
|
1274
1350
|
}
|
|
1275
1351
|
}
|
|
1276
1352
|
}
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReferenceKind } from '@mikro-orm/core';
|
|
1
|
+
import { ReferenceKind, RawQueryFragment, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
@@ -108,9 +108,14 @@ export class DatabaseSchema {
|
|
|
108
108
|
table.addIndex(meta, { properties: meta.props.filter(prop => prop.primary).map(prop => prop.name) }, 'primary');
|
|
109
109
|
for (const check of meta.checks) {
|
|
110
110
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
111
|
+
let expression = check.expression;
|
|
112
|
+
const raw = RawQueryFragment.getKnownFragment(expression);
|
|
113
|
+
if (raw) {
|
|
114
|
+
expression = platform.formatQuery(raw.sql, raw.params);
|
|
115
|
+
}
|
|
111
116
|
table.addCheck({
|
|
112
117
|
name: check.name,
|
|
113
|
-
expression
|
|
118
|
+
expression,
|
|
114
119
|
definition: `check (${check.expression})`,
|
|
115
120
|
columnName,
|
|
116
121
|
});
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -109,33 +109,34 @@ export class DatabaseTable {
|
|
|
109
109
|
if (prop.referencedTableName.includes('.')) {
|
|
110
110
|
schema = undefined;
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
constraintName
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
112
|
+
if (prop.createForeignKeyConstraint) {
|
|
113
|
+
this.foreignKeys[constraintName] = {
|
|
114
|
+
constraintName,
|
|
115
|
+
columnNames: prop.fieldNames,
|
|
116
|
+
localTableName: this.getShortestName(false),
|
|
117
|
+
referencedColumnNames: prop.referencedColumnNames,
|
|
118
|
+
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
119
|
+
};
|
|
120
|
+
const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
|
|
121
|
+
if (prop.deleteRule || cascade || prop.nullable) {
|
|
122
|
+
this.foreignKeys[constraintName].deleteRule = prop.deleteRule || (cascade ? 'cascade' : 'set null');
|
|
123
|
+
}
|
|
124
|
+
if (prop.updateRule) {
|
|
125
|
+
this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
|
|
126
|
+
}
|
|
127
|
+
if ((prop.cascade.includes(Cascade.PERSIST) || prop.cascade.includes(Cascade.ALL))) {
|
|
128
|
+
const hasCascadePath = Object.values(this.foreignKeys).some(fk => {
|
|
129
|
+
return fk.constraintName !== constraintName
|
|
130
|
+
&& ((fk.updateRule && fk.updateRule !== 'no action') || (fk.deleteRule && fk.deleteRule !== 'no action'))
|
|
131
|
+
&& fk.referencedTableName === this.foreignKeys[constraintName].referencedTableName;
|
|
132
|
+
});
|
|
133
|
+
if (!hasCascadePath || this.platform.supportsMultipleCascadePaths()) {
|
|
134
|
+
this.foreignKeys[constraintName].updateRule ??= 'cascade';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (prop.deferMode) {
|
|
138
|
+
this.foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
135
139
|
}
|
|
136
|
-
}
|
|
137
|
-
if (prop.deferMode) {
|
|
138
|
-
this.foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
if (prop.index) {
|
|
@@ -269,7 +269,7 @@ export class SchemaComparator {
|
|
|
269
269
|
}
|
|
270
270
|
for (const toConstraint of Object.values(toForeignKeys)) {
|
|
271
271
|
tableDifferences.addedForeignKeys[toConstraint.constraintName] = toConstraint;
|
|
272
|
-
this.log(`FK constraint ${toConstraint.constraintName} added
|
|
272
|
+
this.log(`FK constraint ${toConstraint.constraintName} added to table ${tableDifferences.name}`, { constraint: toConstraint });
|
|
273
273
|
changes++;
|
|
274
274
|
}
|
|
275
275
|
return changes ? tableDifferences : false;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -426,7 +426,7 @@ export class SchemaHelper {
|
|
|
426
426
|
return `alter table ${table.getQuotedName()} comment = ${this.platform.quoteValue(comment ?? '')}`;
|
|
427
427
|
}
|
|
428
428
|
createForeignKey(table, foreignKey, alterTable = true, inline = false) {
|
|
429
|
-
if (!this.options.createForeignKeyConstraints
|
|
429
|
+
if (!this.options.createForeignKeyConstraints) {
|
|
430
430
|
return '';
|
|
431
431
|
}
|
|
432
432
|
const constraintName = this.quote(foreignKey.constraintName);
|