@mikro-orm/knex 6.0.0-rc.0 → 6.0.0-rc.2
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 -0
- package/AbstractSqlConnection.js +18 -0
- package/AbstractSqlDriver.d.ts +15 -5
- package/AbstractSqlDriver.js +214 -73
- package/SqlEntityRepository.js +1 -1
- package/package.json +2 -2
- package/query/ArrayCriteriaNode.d.ts +3 -2
- package/query/ArrayCriteriaNode.js +7 -2
- package/query/CriteriaNode.d.ts +5 -2
- package/query/CriteriaNode.js +11 -2
- package/query/CriteriaNodeFactory.js +17 -4
- package/query/ObjectCriteriaNode.d.ts +3 -2
- package/query/ObjectCriteriaNode.js +51 -15
- package/query/QueryBuilder.d.ts +13 -5
- package/query/QueryBuilder.js +83 -34
- package/query/QueryBuilderHelper.d.ts +3 -3
- package/query/QueryBuilderHelper.js +16 -15
- package/query/ScalarCriteriaNode.d.ts +2 -2
- package/query/ScalarCriteriaNode.js +3 -3
- package/schema/DatabaseSchema.js +1 -1
- package/schema/DatabaseTable.d.ts +10 -4
- package/schema/DatabaseTable.js +320 -30
- package/schema/SchemaComparator.d.ts +2 -2
- package/schema/SchemaComparator.js +11 -7
- package/schema/SchemaHelper.d.ts +1 -1
- package/schema/SchemaHelper.js +7 -4
- package/schema/SqlSchemaGenerator.js +2 -2
- package/typings.d.ts +15 -3
|
@@ -10,8 +10,22 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
10
10
|
/** @inheritDoc */
|
|
11
11
|
connect(): void | Promise<void>;
|
|
12
12
|
getKnex(): Knex;
|
|
13
|
+
/**
|
|
14
|
+
* @inheritDoc
|
|
15
|
+
*/
|
|
13
16
|
close(force?: boolean): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* @inheritDoc
|
|
19
|
+
*/
|
|
14
20
|
isConnected(): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* @inheritDoc
|
|
23
|
+
*/
|
|
24
|
+
checkConnection(): Promise<{
|
|
25
|
+
ok: boolean;
|
|
26
|
+
reason?: string;
|
|
27
|
+
error?: Error;
|
|
28
|
+
}>;
|
|
15
29
|
transactional<T>(cb: (trx: Transaction<Knex.Transaction>) => Promise<T>, options?: {
|
|
16
30
|
isolationLevel?: IsolationLevel;
|
|
17
31
|
readOnly?: boolean;
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -26,10 +26,16 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
26
26
|
}
|
|
27
27
|
return this.client;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* @inheritDoc
|
|
31
|
+
*/
|
|
29
32
|
async close(force) {
|
|
30
33
|
await super.close(force);
|
|
31
34
|
await this.getKnex().destroy();
|
|
32
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* @inheritDoc
|
|
38
|
+
*/
|
|
33
39
|
async isConnected() {
|
|
34
40
|
try {
|
|
35
41
|
await this.getKnex().raw('select 1');
|
|
@@ -39,6 +45,18 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
39
45
|
return false;
|
|
40
46
|
}
|
|
41
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* @inheritDoc
|
|
50
|
+
*/
|
|
51
|
+
async checkConnection() {
|
|
52
|
+
try {
|
|
53
|
+
await this.getKnex().raw('select 1');
|
|
54
|
+
return { ok: true };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return { ok: false, reason: error.message, error };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
42
60
|
async transactional(cb, options = {}) {
|
|
43
61
|
const trx = await this.begin(options);
|
|
44
62
|
try {
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
|
-
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 IDatabaseDriver, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type OrderDefinition, type PopulateOptions, type Primary, type QueryOrderMap, type QueryResult, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
|
|
2
|
+
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 IDatabaseDriver, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type Options, type OrderDefinition, type PopulateOptions, type Primary, type QueryOrderMap, type QueryResult, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlConnection } from './AbstractSqlConnection';
|
|
4
4
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
|
|
5
5
|
import { QueryBuilder, QueryType } from './query';
|
|
@@ -15,6 +15,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
15
15
|
createEntityManager<D extends IDatabaseDriver = IDatabaseDriver>(useContext?: boolean): D[typeof EntityManagerType];
|
|
16
16
|
find<T extends object, P extends string = never, F extends string = '*'>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T, P, F>): Promise<EntityData<T>[]>;
|
|
17
17
|
findOne<T extends object, P extends string = never, F extends string = '*'>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T, P, F>): Promise<EntityData<T> | null>;
|
|
18
|
+
protected hasToManyJoins<T extends object>(hint: PopulateOptions<T>, meta: EntityMetadata<T>): boolean;
|
|
18
19
|
findVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any, any>): Promise<EntityData<T>[]>;
|
|
19
20
|
countVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: CountOptions<T, any>): Promise<number>;
|
|
20
21
|
protected findFromVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
|
|
@@ -38,12 +39,17 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
38
39
|
/**
|
|
39
40
|
* @internal
|
|
40
41
|
*/
|
|
41
|
-
joinedProps<T>(meta: EntityMetadata, populate: PopulateOptions<T>[]
|
|
42
|
+
joinedProps<T>(meta: EntityMetadata, populate: PopulateOptions<T>[], options?: {
|
|
43
|
+
strategy?: Options['loadStrategy'];
|
|
44
|
+
}): PopulateOptions<T>[];
|
|
42
45
|
/**
|
|
43
46
|
* @internal
|
|
44
47
|
*/
|
|
45
48
|
mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
|
|
46
|
-
protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, explicitFields?: Field<T>[], populate?: PopulateOptions<T>[],
|
|
49
|
+
protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, explicitFields?: Field<T>[], populate?: PopulateOptions<T>[], options?: {
|
|
50
|
+
strategy?: Options['loadStrategy'];
|
|
51
|
+
populateWhere?: FindOptions<any>['populateWhere'];
|
|
52
|
+
}, parentTableAlias?: string, parentJoinPath?: string): Field<T>[];
|
|
47
53
|
/**
|
|
48
54
|
* @internal
|
|
49
55
|
*/
|
|
@@ -57,8 +63,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
57
63
|
protected extractManyToMany<T>(entityName: string, data: EntityDictionary<T>): EntityData<T>;
|
|
58
64
|
protected processManyToMany<T extends object>(meta: EntityMetadata<T> | undefined, pks: Primary<T>[], collections: EntityData<T>, clear: boolean, options?: DriverMethodOptions): Promise<void>;
|
|
59
65
|
lockPessimistic<T extends object>(entity: T, options: LockOptions): Promise<void>;
|
|
60
|
-
protected
|
|
66
|
+
protected buildOrderBy<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>): QueryOrderMap<T>[];
|
|
67
|
+
protected buildPopulateOrderBy<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, parentAlias?: string): QueryOrderMap<T>[];
|
|
68
|
+
protected buildJoinedPropsOrderBy<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options?: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>, parentPath?: string): QueryOrderMap<T>[];
|
|
61
69
|
protected normalizeFields<T extends object>(fields: Field<T>[], prefix?: string): string[];
|
|
62
70
|
protected processField<T extends object>(meta: EntityMetadata<T>, prop: EntityProperty<T> | undefined, field: string, ret: Field<T>[], populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T>): void;
|
|
63
|
-
protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T>, alias: string, fields?: Field<T>[]
|
|
71
|
+
protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T>, alias: string, fields?: Field<T>[], options?: {
|
|
72
|
+
strategy?: Options['loadStrategy'];
|
|
73
|
+
}): Field<T>[];
|
|
64
74
|
}
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -29,17 +29,19 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
29
29
|
return this.findVirtual(entityName, where, options);
|
|
30
30
|
}
|
|
31
31
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
32
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
32
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
33
33
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
34
|
-
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options.fields);
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options.fields, options);
|
|
35
|
+
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
36
|
+
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
37
37
|
if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
|
|
38
38
|
where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
|
|
39
39
|
}
|
|
40
40
|
const { first, last, before, after } = options;
|
|
41
41
|
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
42
|
+
qb.__populateWhere = options._populateWhere;
|
|
42
43
|
qb.select(fields)
|
|
44
|
+
// only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
|
|
43
45
|
.populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
|
|
44
46
|
.where(where)
|
|
45
47
|
.groupBy(options.groupBy)
|
|
@@ -55,13 +57,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
55
57
|
else {
|
|
56
58
|
qb.orderBy(orderBy);
|
|
57
59
|
}
|
|
58
|
-
if (options.limit
|
|
60
|
+
if (options.limit != null || options.offset != null) {
|
|
59
61
|
qb.limit(options.limit, options.offset);
|
|
60
62
|
}
|
|
61
63
|
if (options.lockMode) {
|
|
62
64
|
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
63
65
|
}
|
|
64
|
-
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
65
66
|
const result = await this.rethrow(qb.execute('all'));
|
|
66
67
|
if (isCursorPagination && !first && !!last) {
|
|
67
68
|
result.reverse();
|
|
@@ -72,8 +73,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
72
73
|
const opts = { populate: [], ...(options || {}) };
|
|
73
74
|
const meta = this.metadata.find(entityName);
|
|
74
75
|
const populate = this.autoJoinOneToOneOwner(meta, opts.populate, opts.fields);
|
|
75
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
76
|
-
|
|
76
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
77
|
+
const hasToManyJoins = joinedProps.some(hint => this.hasToManyJoins(hint, meta));
|
|
78
|
+
if (joinedProps.length === 0 || !hasToManyJoins) {
|
|
77
79
|
opts.limit = 1;
|
|
78
80
|
}
|
|
79
81
|
if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
|
|
@@ -83,6 +85,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
83
85
|
const res = await this.find(entityName, where, opts);
|
|
84
86
|
return res[0] || null;
|
|
85
87
|
}
|
|
88
|
+
hasToManyJoins(hint, meta) {
|
|
89
|
+
const [propName] = hint.field.split(':', 2);
|
|
90
|
+
const prop = meta.properties[propName];
|
|
91
|
+
if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (hint.children && prop.targetMeta) {
|
|
95
|
+
return hint.children.some(hint => this.hasToManyJoins(hint, prop.targetMeta));
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
86
99
|
async findVirtual(entityName, where, options) {
|
|
87
100
|
return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
|
|
88
101
|
}
|
|
@@ -155,34 +168,61 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
155
168
|
}
|
|
156
169
|
mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
|
|
157
170
|
const joinedProps = this.joinedProps(meta, populate);
|
|
158
|
-
joinedProps.forEach(
|
|
159
|
-
const
|
|
171
|
+
joinedProps.forEach(hint => {
|
|
172
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
173
|
+
const prop = meta.properties[propName];
|
|
160
174
|
/* istanbul ignore next */
|
|
161
|
-
if (!
|
|
175
|
+
if (!prop) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
179
|
+
const meta2 = this.metadata.find(prop.type);
|
|
180
|
+
let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
|
|
181
|
+
if (!parentJoinPath) {
|
|
182
|
+
path = '[populate]' + path;
|
|
183
|
+
}
|
|
184
|
+
if (pivotRefJoin) {
|
|
185
|
+
path += '[pivot]';
|
|
186
|
+
}
|
|
187
|
+
const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
|
|
188
|
+
// pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
|
|
189
|
+
if (pivotRefJoin) {
|
|
190
|
+
let item;
|
|
191
|
+
if (prop.inverseJoinColumns.length > 1) { // composite keys
|
|
192
|
+
item = prop.inverseJoinColumns.map(name => root[`${relationAlias}__${name}`]);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const alias = `${relationAlias}__${prop.inverseJoinColumns[0]}`;
|
|
196
|
+
item = root[alias];
|
|
197
|
+
}
|
|
198
|
+
prop.joinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
199
|
+
prop.inverseJoinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
200
|
+
result[prop.name] ??= [];
|
|
201
|
+
if (item) {
|
|
202
|
+
result[prop.name].push(item);
|
|
203
|
+
}
|
|
162
204
|
return;
|
|
163
205
|
}
|
|
164
|
-
const meta2 = this.metadata.find(relation.type);
|
|
165
|
-
const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
|
|
166
|
-
const relationAlias = qb.getAliasForJoinPath(path);
|
|
167
|
-
const relationPojo = {};
|
|
168
206
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
169
207
|
// and therefore we don't return any record (since all values would be null)
|
|
170
208
|
const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
|
|
171
209
|
return root[`${relationAlias}__${name}`] != null;
|
|
172
210
|
}));
|
|
173
211
|
if (!hasPK) {
|
|
174
|
-
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(
|
|
175
|
-
result[
|
|
212
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
|
213
|
+
result[prop.name] ??= [];
|
|
176
214
|
}
|
|
177
|
-
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(
|
|
178
|
-
result[
|
|
215
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
216
|
+
result[prop.name] ??= null;
|
|
179
217
|
}
|
|
180
218
|
return;
|
|
181
219
|
}
|
|
182
|
-
const
|
|
220
|
+
const relationPojo = {};
|
|
183
221
|
meta2.props
|
|
184
222
|
.filter(prop => prop.persist === false && prop.fieldNames)
|
|
223
|
+
.filter(prop => !prop.lazy || populate.some(p => p.field === prop.name || p.all))
|
|
185
224
|
.forEach(prop => {
|
|
225
|
+
/* istanbul ignore if */
|
|
186
226
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
187
227
|
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
188
228
|
}
|
|
@@ -192,11 +232,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
192
232
|
}
|
|
193
233
|
});
|
|
194
234
|
meta2.props
|
|
195
|
-
.filter(prop => this.platform.shouldHaveColumn(prop,
|
|
235
|
+
.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []))
|
|
196
236
|
.forEach(prop => {
|
|
197
237
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
198
|
-
|
|
238
|
+
const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
199
239
|
prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
|
|
240
|
+
relationPojo[prop.name] = core_1.Utils.mapFlatCompositePrimaryKey(fk, prop);
|
|
200
241
|
}
|
|
201
242
|
else if (prop.runtimeType === 'Date') {
|
|
202
243
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
@@ -209,14 +250,14 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
209
250
|
delete root[alias];
|
|
210
251
|
}
|
|
211
252
|
});
|
|
212
|
-
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(
|
|
213
|
-
result[
|
|
214
|
-
result[
|
|
253
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
|
254
|
+
result[prop.name] ??= [];
|
|
255
|
+
result[prop.name].push(relationPojo);
|
|
215
256
|
}
|
|
216
257
|
else {
|
|
217
|
-
result[
|
|
258
|
+
result[prop.name] = relationPojo;
|
|
218
259
|
}
|
|
219
|
-
const populateChildren =
|
|
260
|
+
const populateChildren = hint.children || [];
|
|
220
261
|
this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
|
|
221
262
|
});
|
|
222
263
|
}
|
|
@@ -388,10 +429,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
388
429
|
}
|
|
389
430
|
else {
|
|
390
431
|
qb.update(data).where(where);
|
|
391
|
-
// reload generated columns
|
|
432
|
+
// reload generated columns and version fields
|
|
392
433
|
const returning = [];
|
|
393
434
|
meta?.props
|
|
394
|
-
.filter(prop => prop.generated && !prop.primary)
|
|
435
|
+
.filter(prop => (prop.generated && !prop.primary) || prop.version)
|
|
395
436
|
.forEach(prop => returning.push(prop.name));
|
|
396
437
|
qb.returning(returning);
|
|
397
438
|
}
|
|
@@ -407,7 +448,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
407
448
|
options.convertCustomTypes ??= true;
|
|
408
449
|
const meta = this.metadata.get(entityName);
|
|
409
450
|
if (options.upsert) {
|
|
410
|
-
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
|
|
451
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
411
452
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
412
453
|
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
413
454
|
qb.insert(data)
|
|
@@ -420,7 +461,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
420
461
|
if (options.onConflictAction === 'ignore') {
|
|
421
462
|
qb.ignore();
|
|
422
463
|
}
|
|
423
|
-
return qb.execute('run', false);
|
|
464
|
+
return this.rethrow(qb.execute('run', false));
|
|
424
465
|
}
|
|
425
466
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
426
467
|
const keys = new Set();
|
|
@@ -433,9 +474,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
433
474
|
}
|
|
434
475
|
});
|
|
435
476
|
});
|
|
436
|
-
// reload generated columns
|
|
477
|
+
// reload generated columns and version fields
|
|
437
478
|
meta?.props
|
|
438
|
-
.filter(prop => prop.generated && !prop.primary)
|
|
479
|
+
.filter(prop => (prop.generated && !prop.primary) || prop.version)
|
|
439
480
|
.forEach(prop => returning.add(prop.name));
|
|
440
481
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
441
482
|
const params = [];
|
|
@@ -627,7 +668,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
627
668
|
const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
628
669
|
const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
|
|
629
670
|
qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
|
|
630
|
-
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields);
|
|
671
|
+
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields, options);
|
|
631
672
|
for (const field of targetFields) {
|
|
632
673
|
const f = field.toString();
|
|
633
674
|
fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
@@ -638,7 +679,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
638
679
|
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
639
680
|
// eslint-disable-next-line dot-notation
|
|
640
681
|
Object.values(qb['_joins']).forEach(join => {
|
|
641
|
-
|
|
682
|
+
const [propName] = hint.field.split(':', 2);
|
|
683
|
+
if (join.alias === alias && join.prop.name === propName) {
|
|
642
684
|
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
643
685
|
}
|
|
644
686
|
});
|
|
@@ -702,16 +744,21 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
702
744
|
const toPopulate = meta.relations
|
|
703
745
|
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
704
746
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
705
|
-
.map(prop => ({ field: prop.name
|
|
747
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
|
|
706
748
|
return [...populate, ...toPopulate];
|
|
707
749
|
}
|
|
708
750
|
/**
|
|
709
751
|
* @internal
|
|
710
752
|
*/
|
|
711
|
-
joinedProps(meta, populate) {
|
|
712
|
-
return populate.filter(
|
|
713
|
-
const
|
|
714
|
-
|
|
753
|
+
joinedProps(meta, populate, options) {
|
|
754
|
+
return populate.filter(hint => {
|
|
755
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
756
|
+
const prop = meta.properties[propName] || {};
|
|
757
|
+
if (ref) {
|
|
758
|
+
// keep only pivot ref joins here, as that only triggers actual join
|
|
759
|
+
return prop.kind === core_1.ReferenceKind.MANY_TO_MANY;
|
|
760
|
+
}
|
|
761
|
+
return (prop.strategy || options?.strategy || hint.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
|
|
715
762
|
});
|
|
716
763
|
}
|
|
717
764
|
/**
|
|
@@ -724,18 +771,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
724
771
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
725
772
|
if (map[pk]) {
|
|
726
773
|
for (const hint of joinedProps) {
|
|
727
|
-
const
|
|
728
|
-
|
|
774
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
775
|
+
const prop = meta.properties[propName];
|
|
776
|
+
if (!item[propName]) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
780
|
+
map[pk][propName] = [...map[pk][propName], ...item[propName]];
|
|
729
781
|
continue;
|
|
730
782
|
}
|
|
731
783
|
switch (prop.kind) {
|
|
732
784
|
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
733
785
|
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
734
|
-
map[pk][
|
|
786
|
+
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
735
787
|
break;
|
|
736
788
|
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
737
789
|
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
738
|
-
map[pk][
|
|
790
|
+
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
739
791
|
break;
|
|
740
792
|
}
|
|
741
793
|
}
|
|
@@ -747,9 +799,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
747
799
|
}
|
|
748
800
|
return res;
|
|
749
801
|
}
|
|
750
|
-
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
802
|
+
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], options, parentTableAlias, parentJoinPath) {
|
|
751
803
|
const fields = [];
|
|
752
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
804
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
753
805
|
if (explicitFields?.includes('*')) {
|
|
754
806
|
fields.push('*');
|
|
755
807
|
}
|
|
@@ -762,25 +814,39 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
762
814
|
}
|
|
763
815
|
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
764
816
|
};
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
817
|
+
const populateWhereAll = options?._populateWhere === 'all' || core_1.Utils.isEmpty(options?._populateWhere);
|
|
818
|
+
// root entity is already handled, skip that
|
|
819
|
+
if (parentJoinPath) {
|
|
820
|
+
// alias all fields in the primary table
|
|
821
|
+
meta.props
|
|
822
|
+
.filter(prop => shouldHaveColumn(prop, populate, explicitFields))
|
|
823
|
+
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
|
|
824
|
+
}
|
|
825
|
+
joinedProps.forEach(hint => {
|
|
826
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
771
827
|
const prop = meta.properties[propName];
|
|
772
828
|
const meta2 = this.metadata.find(prop.type);
|
|
829
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
773
830
|
const tableAlias = qb.getNextAlias(prop.name);
|
|
774
831
|
const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
|
|
775
|
-
|
|
776
|
-
|
|
832
|
+
let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
|
|
833
|
+
if (!parentJoinPath && populateWhereAll && !path.startsWith('[populate]')) {
|
|
834
|
+
path = '[populate]' + path;
|
|
835
|
+
}
|
|
836
|
+
const joinType = pivotRefJoin ? query_1.JoinType.pivotJoin : query_1.JoinType.leftJoin;
|
|
837
|
+
qb.join(field, tableAlias, {}, joinType, path);
|
|
838
|
+
if (pivotRefJoin) {
|
|
839
|
+
fields.push(...prop.joinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)), ...prop.inverseJoinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
840
|
+
}
|
|
777
841
|
const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
|
|
778
842
|
explicitFields?.forEach(f => {
|
|
779
843
|
if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
|
|
780
844
|
childExplicitFields.push(f.substring(prop.name.length + 1));
|
|
781
845
|
}
|
|
782
846
|
});
|
|
783
|
-
|
|
847
|
+
if (!ref) {
|
|
848
|
+
fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, hint.children, options, tableAlias, path));
|
|
849
|
+
}
|
|
784
850
|
});
|
|
785
851
|
return fields;
|
|
786
852
|
}
|
|
@@ -790,7 +856,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
790
856
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
791
857
|
const knex = this.connection.getKnex();
|
|
792
858
|
const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
|
|
793
|
-
if (prop.
|
|
859
|
+
if (tableAlias && prop.customTypes?.some(type => type.convertToJSValueSQL)) {
|
|
860
|
+
return prop.fieldNames.map((col, idx) => {
|
|
861
|
+
if (!prop.customTypes[idx].convertToJSValueSQL) {
|
|
862
|
+
return col;
|
|
863
|
+
}
|
|
864
|
+
const prefixed = knex.ref(col).withSchema(tableAlias).toString();
|
|
865
|
+
const aliased = knex.ref(`${tableAlias}__${col}`).toString();
|
|
866
|
+
return (0, core_1.raw)(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
if (tableAlias && prop.customType?.convertToJSValueSQL) {
|
|
794
870
|
const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
|
|
795
871
|
return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
796
872
|
}
|
|
@@ -857,25 +933,83 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
857
933
|
qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
858
934
|
await this.rethrow(qb.execute());
|
|
859
935
|
}
|
|
860
|
-
|
|
936
|
+
buildOrderBy(qb, meta, populate, options) {
|
|
937
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
938
|
+
// `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
|
|
939
|
+
// as `options.populateWhere` will be always recomputed to respect filters
|
|
940
|
+
const populateWhereAll = options._populateWhere !== 'infer' && !core_1.Utils.isEmpty(options._populateWhere);
|
|
941
|
+
const path = (populateWhereAll ? '[populate]' : '') + meta.className;
|
|
942
|
+
const populateOrderBy = this.buildPopulateOrderBy(qb, meta, core_1.Utils.asArray(options.populateOrderBy ?? options.orderBy), path);
|
|
943
|
+
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
|
|
944
|
+
return [...core_1.Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy];
|
|
945
|
+
}
|
|
946
|
+
buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, parentAlias = qb.alias) {
|
|
861
947
|
const orderBy = [];
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
const
|
|
948
|
+
for (let i = 0; i < populateOrderBy.length; i++) {
|
|
949
|
+
const orderHint = populateOrderBy[i];
|
|
950
|
+
for (const propName of core_1.Utils.keys(orderHint)) {
|
|
951
|
+
const prop = meta.properties[propName];
|
|
952
|
+
const meta2 = this.metadata.find(prop.type);
|
|
953
|
+
const childOrder = orderHint[prop.name];
|
|
954
|
+
let path = `${parentPath}.${propName}`;
|
|
955
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
956
|
+
path += '[pivot]';
|
|
957
|
+
}
|
|
958
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
959
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
960
|
+
if (!join && parentAlias === qb.alias) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
964
|
+
const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, propAlias);
|
|
965
|
+
orderBy.push(...children);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
|
|
969
|
+
if (prop.fixedOrderColumn) {
|
|
970
|
+
orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
for (const col of prop.inverseJoinColumns) {
|
|
974
|
+
orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
|
|
980
|
+
orderBy.push({ [`${propAlias}.${propName}`]: order });
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return orderBy;
|
|
984
|
+
}
|
|
985
|
+
buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
|
|
986
|
+
const orderBy = [];
|
|
987
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
988
|
+
for (const hint of joinedProps) {
|
|
989
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
865
990
|
const prop = meta.properties[propName];
|
|
866
991
|
const propOrderBy = prop.orderBy;
|
|
867
|
-
|
|
868
|
-
|
|
992
|
+
let path = `${parentPath}.${propName}`;
|
|
993
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
994
|
+
path += '[pivot]';
|
|
995
|
+
}
|
|
996
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
997
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
|
|
998
|
+
const meta2 = this.metadata.find(prop.type);
|
|
999
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
|
|
1000
|
+
const alias = ref ? propAlias : join.ownerAlias;
|
|
1001
|
+
orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC });
|
|
1002
|
+
}
|
|
869
1003
|
if (propOrderBy) {
|
|
870
1004
|
core_1.Utils.keys(propOrderBy).forEach(field => {
|
|
871
1005
|
orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
|
|
872
1006
|
});
|
|
873
1007
|
}
|
|
874
|
-
if (
|
|
875
|
-
const
|
|
876
|
-
orderBy.push(...
|
|
1008
|
+
if (hint.children) {
|
|
1009
|
+
const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
|
|
1010
|
+
orderBy.push(...buildJoinedPropsOrderBy);
|
|
877
1011
|
}
|
|
878
|
-
}
|
|
1012
|
+
}
|
|
879
1013
|
return orderBy;
|
|
880
1014
|
}
|
|
881
1015
|
normalizeFields(fields, prefix = '') {
|
|
@@ -913,17 +1047,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
913
1047
|
}
|
|
914
1048
|
ret.push(prop.name);
|
|
915
1049
|
}
|
|
916
|
-
buildFields(meta, populate, joinedProps, qb, alias, fields) {
|
|
1050
|
+
buildFields(meta, populate, joinedProps, qb, alias, fields, options) {
|
|
917
1051
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
918
1052
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
919
1053
|
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
920
1054
|
const hasExplicitFields = !!fields;
|
|
921
1055
|
const ret = [];
|
|
922
1056
|
let addFormulas = false;
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
else if (fields) {
|
|
1057
|
+
// handle root entity properties first, this is used for both strategies in the same way
|
|
1058
|
+
if (fields) {
|
|
927
1059
|
for (const field of this.normalizeFields(fields)) {
|
|
928
1060
|
if (field === '*') {
|
|
929
1061
|
ret.push('*');
|
|
@@ -940,7 +1072,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
940
1072
|
});
|
|
941
1073
|
this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
|
|
942
1074
|
}
|
|
943
|
-
|
|
1075
|
+
if (!fields.includes('*') && !fields.includes(`${qb.alias}.*`)) {
|
|
1076
|
+
ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
|
|
1077
|
+
}
|
|
944
1078
|
}
|
|
945
1079
|
else if (lazyProps.filter(p => !p.formula).length > 0) {
|
|
946
1080
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
|
|
@@ -951,6 +1085,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
951
1085
|
ret.push('*');
|
|
952
1086
|
addFormulas = true;
|
|
953
1087
|
}
|
|
1088
|
+
else {
|
|
1089
|
+
ret.push('*');
|
|
1090
|
+
}
|
|
954
1091
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
955
1092
|
meta.props
|
|
956
1093
|
.filter(prop => prop.formula && !lazyProps.includes(prop))
|
|
@@ -963,7 +1100,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
963
1100
|
.filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
|
|
964
1101
|
.forEach(prop => ret.push(prop.name));
|
|
965
1102
|
}
|
|
966
|
-
|
|
1103
|
+
// add joined relations after the root entity fields
|
|
1104
|
+
if (joinedProps.length > 0) {
|
|
1105
|
+
ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate, options, alias));
|
|
1106
|
+
}
|
|
1107
|
+
return core_1.Utils.unique(ret);
|
|
967
1108
|
}
|
|
968
1109
|
}
|
|
969
1110
|
exports.AbstractSqlDriver = AbstractSqlDriver;
|
package/SqlEntityRepository.js
CHANGED