@mikro-orm/knex 7.0.0-dev.9 → 7.0.0-dev.90
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 +11 -5
- package/AbstractSqlConnection.js +78 -32
- package/AbstractSqlDriver.d.ts +9 -5
- package/AbstractSqlDriver.js +267 -227
- package/AbstractSqlPlatform.js +5 -5
- package/PivotCollectionPersister.d.ts +8 -4
- package/PivotCollectionPersister.js +55 -31
- package/README.md +3 -2
- package/SqlEntityManager.d.ts +10 -2
- package/SqlEntityManager.js +11 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +42 -3
- package/dialects/mysql/MySqlExceptionConverter.d.ts +3 -3
- package/dialects/mysql/MySqlExceptionConverter.js +4 -5
- package/dialects/mysql/MySqlSchemaHelper.js +2 -2
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -2
- package/dialects/sqlite/BaseSqliteConnection.js +2 -14
- package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +2 -2
- package/dialects/sqlite/SqliteExceptionConverter.js +6 -4
- package/dialects/sqlite/SqliteSchemaHelper.js +5 -6
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/package.json +5 -5
- package/plugin/index.d.ts +53 -0
- package/plugin/index.js +42 -0
- package/plugin/transformer.d.ts +115 -0
- package/plugin/transformer.js +883 -0
- package/query/ArrayCriteriaNode.d.ts +1 -0
- package/query/ArrayCriteriaNode.js +3 -0
- package/query/CriteriaNode.d.ts +4 -5
- package/query/CriteriaNode.js +13 -9
- package/query/CriteriaNodeFactory.js +12 -7
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +35 -8
- package/query/QueryBuilder.d.ts +59 -10
- package/query/QueryBuilder.js +166 -50
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +20 -14
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +9 -7
- package/query/index.d.ts +1 -0
- package/query/index.js +1 -0
- package/query/raw.d.ts +59 -0
- package/query/raw.js +68 -0
- package/query/rawKnex.d.ts +58 -0
- package/query/rawKnex.js +72 -0
- package/schema/DatabaseSchema.js +25 -4
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +65 -34
- package/schema/SchemaComparator.js +5 -6
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +14 -10
- package/schema/SqlSchemaGenerator.d.ts +13 -6
- package/schema/SqlSchemaGenerator.js +41 -20
- package/typings.d.ts +85 -3
package/query/QueryBuilder.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { inspect } from '
|
|
2
|
-
import { helper, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core';
|
|
1
|
+
import { helper, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, inspect, } from '@mikro-orm/core';
|
|
3
2
|
import { JoinType, QueryType } from './enums.js';
|
|
4
3
|
import { QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
5
4
|
import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
|
|
@@ -179,10 +178,10 @@ export class QueryBuilder {
|
|
|
179
178
|
subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
|
|
180
179
|
field = field[0];
|
|
181
180
|
}
|
|
182
|
-
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
181
|
+
const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
183
182
|
const [fromAlias] = this.helper.splitField(field);
|
|
184
183
|
if (subquery) {
|
|
185
|
-
this._joins[
|
|
184
|
+
this._joins[key].subquery = subquery;
|
|
186
185
|
}
|
|
187
186
|
const populate = this._joinedProps.get(fromAlias);
|
|
188
187
|
const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
|
|
@@ -253,7 +252,7 @@ export class QueryBuilder {
|
|
|
253
252
|
* Apply filters to the QB where condition.
|
|
254
253
|
*/
|
|
255
254
|
async applyFilters(filterOptions = {}) {
|
|
256
|
-
/* v8 ignore next
|
|
255
|
+
/* v8 ignore next */
|
|
257
256
|
if (!this.em) {
|
|
258
257
|
throw new Error('Cannot apply filters, this QueryBuilder is not attached to an EntityManager');
|
|
259
258
|
}
|
|
@@ -270,12 +269,13 @@ export class QueryBuilder {
|
|
|
270
269
|
/**
|
|
271
270
|
* @internal
|
|
272
271
|
*/
|
|
273
|
-
async applyJoinedFilters(em, filterOptions
|
|
272
|
+
async applyJoinedFilters(em, filterOptions) {
|
|
274
273
|
for (const path of this.autoJoinedPaths) {
|
|
275
274
|
const join = this.getJoinForPath(path);
|
|
276
275
|
if (join.type === JoinType.pivotJoin) {
|
|
277
276
|
continue;
|
|
278
277
|
}
|
|
278
|
+
filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
|
|
279
279
|
const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
|
|
280
280
|
if (Utils.hasObjectKeys(cond)) {
|
|
281
281
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
@@ -285,7 +285,7 @@ export class QueryBuilder {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
if (Utils.hasObjectKeys(join.cond)) {
|
|
288
|
-
/*
|
|
288
|
+
/* v8 ignore next */
|
|
289
289
|
join.cond = { $and: [join.cond, cond] };
|
|
290
290
|
}
|
|
291
291
|
else {
|
|
@@ -312,7 +312,7 @@ export class QueryBuilder {
|
|
|
312
312
|
cond = { [raw(`(${sql})`)]: Utils.asArray(params) };
|
|
313
313
|
operator ??= '$and';
|
|
314
314
|
}
|
|
315
|
-
else if (
|
|
315
|
+
else if (typeof cond === 'string') {
|
|
316
316
|
cond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
|
|
317
317
|
operator ??= '$and';
|
|
318
318
|
}
|
|
@@ -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,
|
|
@@ -383,7 +391,7 @@ export class QueryBuilder {
|
|
|
383
391
|
}
|
|
384
392
|
having(cond = {}, params, operator) {
|
|
385
393
|
this.ensureNotFinalized();
|
|
386
|
-
if (
|
|
394
|
+
if (typeof cond === 'string') {
|
|
387
395
|
cond = { [raw(`(${cond})`, params)]: [] };
|
|
388
396
|
}
|
|
389
397
|
cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
|
|
@@ -468,7 +476,7 @@ export class QueryBuilder {
|
|
|
468
476
|
}
|
|
469
477
|
setLockMode(mode, tables) {
|
|
470
478
|
this.ensureNotFinalized();
|
|
471
|
-
if (mode != null &&
|
|
479
|
+
if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) {
|
|
472
480
|
throw ValidationError.transactionRequired();
|
|
473
481
|
}
|
|
474
482
|
this.lockMode = mode;
|
|
@@ -562,7 +570,7 @@ export class QueryBuilder {
|
|
|
562
570
|
Utils.runIfNotEmpty(() => qb.hintComment(this._hintComments), this._hintComments);
|
|
563
571
|
Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(QueryType.UPSERT, this._onConflict, qb), this._onConflict);
|
|
564
572
|
if (this.lockMode) {
|
|
565
|
-
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
573
|
+
this.helper.getLockSQL(qb, this.lockMode, this.lockTables, this._joins);
|
|
566
574
|
}
|
|
567
575
|
this.helper.finalize(this.type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
568
576
|
this.clearRawFragmentsCache();
|
|
@@ -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) {
|
|
@@ -684,13 +692,11 @@ export class QueryBuilder {
|
|
|
684
692
|
}
|
|
685
693
|
const query = this.toQuery();
|
|
686
694
|
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ['qb.execute', query.sql, query.params, method]);
|
|
687
|
-
if (cached?.data) {
|
|
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;
|
|
@@ -827,7 +902,7 @@ export class QueryBuilder {
|
|
|
827
902
|
qb[prop] = properties.includes(prop) ? Utils.copy(this[prop]) : this[prop];
|
|
828
903
|
}
|
|
829
904
|
delete RawQueryFragment.cloneRegistry;
|
|
830
|
-
/* v8 ignore next
|
|
905
|
+
/* v8 ignore next */
|
|
831
906
|
if (this._fields && !reset.includes('_fields')) {
|
|
832
907
|
qb._fields = [...this._fields];
|
|
833
908
|
}
|
|
@@ -882,7 +957,8 @@ export class QueryBuilder {
|
|
|
882
957
|
if (field instanceof RawQueryFragment) {
|
|
883
958
|
field = this.platform.formatQuery(field.sql, field.params);
|
|
884
959
|
}
|
|
885
|
-
|
|
960
|
+
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
961
|
+
this._joins[key] = {
|
|
886
962
|
prop,
|
|
887
963
|
alias,
|
|
888
964
|
type,
|
|
@@ -891,7 +967,7 @@ export class QueryBuilder {
|
|
|
891
967
|
subquery: field.toString(),
|
|
892
968
|
ownerAlias: this.alias,
|
|
893
969
|
};
|
|
894
|
-
return prop;
|
|
970
|
+
return { prop, key };
|
|
895
971
|
}
|
|
896
972
|
if (!subquery && type.includes('lateral')) {
|
|
897
973
|
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
@@ -916,10 +992,13 @@ export class QueryBuilder {
|
|
|
916
992
|
aliasMap: this.getAliasMap(),
|
|
917
993
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
918
994
|
});
|
|
995
|
+
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.className, cond);
|
|
996
|
+
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
919
997
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
920
998
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
921
999
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
922
1000
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
1001
|
+
this._joins[aliasedName].path ??= path;
|
|
923
1002
|
}
|
|
924
1003
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
925
1004
|
let pivotAlias = alias;
|
|
@@ -931,17 +1010,18 @@ export class QueryBuilder {
|
|
|
931
1010
|
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
932
1011
|
Object.assign(this._joins, joins);
|
|
933
1012
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
1013
|
+
this._joins[aliasedName].path ??= path;
|
|
1014
|
+
aliasedName = Object.keys(joins)[1];
|
|
934
1015
|
}
|
|
935
1016
|
else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
936
1017
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
1018
|
+
this._joins[aliasedName].path ??= path;
|
|
937
1019
|
}
|
|
938
1020
|
else { // MANY_TO_ONE
|
|
939
1021
|
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
1022
|
+
this._joins[aliasedName].path ??= path;
|
|
940
1023
|
}
|
|
941
|
-
|
|
942
|
-
this._joins[aliasedName].path = path;
|
|
943
|
-
}
|
|
944
|
-
return prop;
|
|
1024
|
+
return { prop, key: aliasedName };
|
|
945
1025
|
}
|
|
946
1026
|
prepareFields(fields, type = 'where') {
|
|
947
1027
|
const ret = [];
|
|
@@ -954,7 +1034,7 @@ export class QueryBuilder {
|
|
|
954
1034
|
ret.push(rawField);
|
|
955
1035
|
return;
|
|
956
1036
|
}
|
|
957
|
-
if (
|
|
1037
|
+
if (typeof field !== 'string') {
|
|
958
1038
|
ret.push(field);
|
|
959
1039
|
return;
|
|
960
1040
|
}
|
|
@@ -965,7 +1045,7 @@ export class QueryBuilder {
|
|
|
965
1045
|
}
|
|
966
1046
|
const [a, f] = this.helper.splitField(field);
|
|
967
1047
|
const prop = this.helper.getProperty(f, a);
|
|
968
|
-
/* v8 ignore next
|
|
1048
|
+
/* v8 ignore next */
|
|
969
1049
|
if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
970
1050
|
return;
|
|
971
1051
|
}
|
|
@@ -1116,6 +1196,7 @@ export class QueryBuilder {
|
|
|
1116
1196
|
const meta = this.mainAlias.metadata;
|
|
1117
1197
|
this.applyDiscriminatorCondition();
|
|
1118
1198
|
this.processPopulateHint();
|
|
1199
|
+
this.processNestedJoins();
|
|
1119
1200
|
if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1120
1201
|
meta.props
|
|
1121
1202
|
.filter(prop => prop.formula && (!prop.lazy || this.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
@@ -1139,7 +1220,7 @@ export class QueryBuilder {
|
|
|
1139
1220
|
if (!this.flags.has(QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
1140
1221
|
this.flags.add(QueryFlag.PAGINATE);
|
|
1141
1222
|
}
|
|
1142
|
-
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)) {
|
|
1143
1224
|
this.wrapPaginateSubQuery(meta);
|
|
1144
1225
|
}
|
|
1145
1226
|
if (meta && (this.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
@@ -1175,6 +1256,7 @@ export class QueryBuilder {
|
|
|
1175
1256
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
|
|
1176
1257
|
this._joins[aliasedName].path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className)}.${prop.name}`;
|
|
1177
1258
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
1259
|
+
this.createAlias(prop.type, alias);
|
|
1178
1260
|
}
|
|
1179
1261
|
});
|
|
1180
1262
|
this.processPopulateWhere(false);
|
|
@@ -1194,7 +1276,7 @@ export class QueryBuilder {
|
|
|
1194
1276
|
if (typeof this[key] === 'object') {
|
|
1195
1277
|
const cond = CriteriaNodeFactory
|
|
1196
1278
|
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1197
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1279
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1198
1280
|
// there might be new joins created by processing the `populateWhere` object
|
|
1199
1281
|
joins = Object.values(this._joins);
|
|
1200
1282
|
this.mergeOnConditions(joins, cond, filter);
|
|
@@ -1235,6 +1317,40 @@ export class QueryBuilder {
|
|
|
1235
1317
|
}
|
|
1236
1318
|
}
|
|
1237
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
1322
|
+
* otherwise the inner join could discard rows of the root table.
|
|
1323
|
+
*/
|
|
1324
|
+
processNestedJoins() {
|
|
1325
|
+
if (this.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const joins = Object.values(this._joins);
|
|
1329
|
+
const lookupParentGroup = (j) => {
|
|
1330
|
+
return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
|
|
1331
|
+
};
|
|
1332
|
+
for (const join of joins) {
|
|
1333
|
+
if (join.type === JoinType.innerJoin) {
|
|
1334
|
+
join.parent = joins.find(j => j.alias === join.ownerAlias);
|
|
1335
|
+
// https://stackoverflow.com/a/56815807/3665878
|
|
1336
|
+
if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
|
|
1337
|
+
const nested = ((join.parent).nested ??= new Set());
|
|
1338
|
+
join.type = join.type === JoinType.innerJoin
|
|
1339
|
+
? JoinType.nestedInnerJoin
|
|
1340
|
+
: JoinType.nestedLeftJoin;
|
|
1341
|
+
nested.add(join);
|
|
1342
|
+
}
|
|
1343
|
+
else if (join.parent?.type === JoinType.nestedInnerJoin) {
|
|
1344
|
+
const group = lookupParentGroup(join.parent);
|
|
1345
|
+
const nested = group ?? ((join.parent).nested ??= new Set());
|
|
1346
|
+
join.type = join.type === JoinType.innerJoin
|
|
1347
|
+
? JoinType.nestedInnerJoin
|
|
1348
|
+
: JoinType.nestedLeftJoin;
|
|
1349
|
+
nested.add(join);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1238
1354
|
hasToManyJoins() {
|
|
1239
1355
|
return Object.values(this._joins).some(join => {
|
|
1240
1356
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
@@ -1291,7 +1407,7 @@ export class QueryBuilder {
|
|
|
1291
1407
|
}
|
|
1292
1408
|
return false;
|
|
1293
1409
|
});
|
|
1294
|
-
/* v8 ignore next
|
|
1410
|
+
/* v8 ignore next */
|
|
1295
1411
|
if (field instanceof RawQueryFragment) {
|
|
1296
1412
|
innerQuery.select(field);
|
|
1297
1413
|
}
|
|
@@ -1400,7 +1516,7 @@ export class QueryBuilder {
|
|
|
1400
1516
|
return new QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this._aliases, this.subQueries, this.driver);
|
|
1401
1517
|
}
|
|
1402
1518
|
ensureFromClause() {
|
|
1403
|
-
/* v8 ignore next
|
|
1519
|
+
/* v8 ignore next */
|
|
1404
1520
|
if (!this._mainAlias) {
|
|
1405
1521
|
throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
|
|
1406
1522
|
}
|
|
@@ -1410,9 +1526,9 @@ export class QueryBuilder {
|
|
|
1410
1526
|
throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
|
|
1411
1527
|
}
|
|
1412
1528
|
}
|
|
1413
|
-
/* v8 ignore start */
|
|
1414
1529
|
/** @ignore */
|
|
1415
|
-
|
|
1530
|
+
/* v8 ignore next */
|
|
1531
|
+
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
|
1416
1532
|
const object = { ...this };
|
|
1417
1533
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'type'];
|
|
1418
1534
|
Object.keys(object).filter(k => k.startsWith('_')).forEach(k => delete object[k]);
|
|
@@ -49,7 +49,7 @@ export declare class QueryBuilderHelper {
|
|
|
49
49
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
|
|
50
50
|
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
|
|
51
51
|
splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
|
|
52
|
-
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
|
|
52
|
+
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
|
|
53
53
|
updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
|
|
54
54
|
private prefix;
|
|
55
55
|
private appendGroupCondition;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, RawQueryFragment, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
|
|
3
2
|
import { JoinType, QueryType } from './enums.js';
|
|
4
3
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
5
4
|
/**
|
|
@@ -26,7 +25,7 @@ export class QueryBuilderHelper {
|
|
|
26
25
|
if (isRaw(field)) {
|
|
27
26
|
return raw(field.sql, field.params);
|
|
28
27
|
}
|
|
29
|
-
/* v8 ignore next
|
|
28
|
+
/* v8 ignore next */
|
|
30
29
|
if (typeof field !== 'string') {
|
|
31
30
|
return field;
|
|
32
31
|
}
|
|
@@ -75,10 +74,10 @@ export class QueryBuilderHelper {
|
|
|
75
74
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
76
75
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
77
76
|
}
|
|
78
|
-
if (
|
|
77
|
+
if (a === prop?.embedded?.[0]) {
|
|
79
78
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
80
79
|
}
|
|
81
|
-
const noPrefix = prop
|
|
80
|
+
const noPrefix = prop?.persist === false;
|
|
82
81
|
if (prop?.fieldNameRaw) {
|
|
83
82
|
return raw(this.prefix(field, isTableNameAliasRequired));
|
|
84
83
|
}
|
|
@@ -358,7 +357,7 @@ export class QueryBuilderHelper {
|
|
|
358
357
|
return;
|
|
359
358
|
}
|
|
360
359
|
parts.push(operator === '$or' ? `(${res.sql})` : res.sql);
|
|
361
|
-
params.push(
|
|
360
|
+
res.params.forEach(p => params.push(p));
|
|
362
361
|
}
|
|
363
362
|
appendQuerySubCondition(type, cond, key) {
|
|
364
363
|
const parts = [];
|
|
@@ -420,9 +419,9 @@ export class QueryBuilderHelper {
|
|
|
420
419
|
}
|
|
421
420
|
// operators
|
|
422
421
|
const op = Object.keys(QueryOperator).find(op => op in value);
|
|
423
|
-
/* v8 ignore next
|
|
422
|
+
/* v8 ignore next */
|
|
424
423
|
if (!op) {
|
|
425
|
-
throw
|
|
424
|
+
throw ValidationError.invalidQueryCondition(cond);
|
|
426
425
|
}
|
|
427
426
|
const replacement = this.getOperatorReplacement(op, value);
|
|
428
427
|
const fields = Utils.splitPrimaryKeys(key);
|
|
@@ -456,7 +455,7 @@ export class QueryBuilderHelper {
|
|
|
456
455
|
const [a, f] = this.splitField(key);
|
|
457
456
|
const prop = this.getProperty(f, a);
|
|
458
457
|
if (op === '$fulltext') {
|
|
459
|
-
/* v8 ignore next
|
|
458
|
+
/* v8 ignore next */
|
|
460
459
|
if (!prop) {
|
|
461
460
|
throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
|
|
462
461
|
}
|
|
@@ -502,7 +501,7 @@ export class QueryBuilderHelper {
|
|
|
502
501
|
params.push(item);
|
|
503
502
|
}
|
|
504
503
|
else {
|
|
505
|
-
params.push(
|
|
504
|
+
value.forEach(v => params.push(v));
|
|
506
505
|
}
|
|
507
506
|
return `(${value.map(() => '?').join(', ')})`;
|
|
508
507
|
}
|
|
@@ -539,7 +538,7 @@ export class QueryBuilderHelper {
|
|
|
539
538
|
const ret = [];
|
|
540
539
|
for (const key of Object.keys(orderBy)) {
|
|
541
540
|
const direction = orderBy[key];
|
|
542
|
-
const order =
|
|
541
|
+
const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
|
|
543
542
|
const raw = RawQueryFragment.getKnownFragment(key);
|
|
544
543
|
if (raw) {
|
|
545
544
|
ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
|
|
@@ -550,10 +549,10 @@ export class QueryBuilderHelper {
|
|
|
550
549
|
let [alias, field] = this.splitField(f, true);
|
|
551
550
|
alias = populate[alias] || alias;
|
|
552
551
|
const prop = this.getProperty(field, alias);
|
|
553
|
-
const noPrefix = (prop
|
|
552
|
+
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || RawQueryFragment.isKnownFragment(f);
|
|
554
553
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
555
554
|
/* v8 ignore next */
|
|
556
|
-
const rawColumn =
|
|
555
|
+
const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
|
|
557
556
|
const customOrder = prop?.customOrder;
|
|
558
557
|
let colPart = customOrder
|
|
559
558
|
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
@@ -622,11 +621,18 @@ export class QueryBuilderHelper {
|
|
|
622
621
|
const fromField = parts.join('.');
|
|
623
622
|
return [fromAlias, fromField, ref];
|
|
624
623
|
}
|
|
625
|
-
getLockSQL(qb, lockMode, lockTables = []) {
|
|
624
|
+
getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
|
|
626
625
|
const meta = this.metadata.find(this.entityName);
|
|
627
626
|
if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
|
|
628
627
|
throw OptimisticLockError.lockFailed(this.entityName);
|
|
629
628
|
}
|
|
629
|
+
if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
|
|
630
|
+
const joins = Object.values(joinsMap);
|
|
631
|
+
const innerJoins = joins.filter(join => [JoinType.innerJoin, JoinType.innerJoinLateral, JoinType.nestedInnerJoin].includes(join.type));
|
|
632
|
+
if (joins.length > innerJoins.length) {
|
|
633
|
+
lockTables.push(this.alias, ...innerJoins.map(join => join.alias));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
630
636
|
qb.lockMode(lockMode, lockTables);
|
|
631
637
|
}
|
|
632
638
|
updateVersionProperty(qb, data) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
6
|
export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
|
-
willAutoJoin
|
|
9
|
-
shouldJoin
|
|
8
|
+
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
9
|
+
private shouldJoin;
|
|
10
10
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ARRAY_OPERATORS, ReferenceKind } from '@mikro-orm/core';
|
|
2
2
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
3
|
-
import { JoinType } from './enums.js';
|
|
3
|
+
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
import { QueryBuilder } from './QueryBuilder.js';
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
export class ScalarCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
-
|
|
10
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
11
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
12
|
+
if (this.shouldJoin(qb, nestedAlias)) {
|
|
11
13
|
const path = this.getPath();
|
|
12
14
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
13
15
|
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
@@ -23,7 +25,7 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
23
25
|
return this.payload.getNativeQuery().toRaw();
|
|
24
26
|
}
|
|
25
27
|
if (this.payload && typeof this.payload === 'object') {
|
|
26
|
-
const keys = Object.keys(this.payload).filter(key =>
|
|
28
|
+
const keys = Object.keys(this.payload).filter(key => ARRAY_OPERATORS.includes(key) && Array.isArray(this.payload[key]));
|
|
27
29
|
for (const key of keys) {
|
|
28
30
|
this.payload[key] = JSON.stringify(this.payload[key]);
|
|
29
31
|
}
|
|
@@ -31,10 +33,10 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
31
33
|
return this.payload;
|
|
32
34
|
}
|
|
33
35
|
willAutoJoin(qb, alias, options) {
|
|
34
|
-
return this.shouldJoin();
|
|
36
|
+
return this.shouldJoin(qb, alias);
|
|
35
37
|
}
|
|
36
|
-
shouldJoin() {
|
|
37
|
-
if (!this.parent || !this.prop) {
|
|
38
|
+
shouldJoin(qb, nestedAlias) {
|
|
39
|
+
if (!this.parent || !this.prop || (nestedAlias && [QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT))) {
|
|
38
40
|
return false;
|
|
39
41
|
}
|
|
40
42
|
switch (this.prop.kind) {
|
package/query/index.d.ts
CHANGED
package/query/index.js
CHANGED