@mikro-orm/sql 7.0.0-dev.299 → 7.0.0-dev.301
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.js +2 -1
- package/AbstractSqlDriver.js +135 -39
- package/AbstractSqlPlatform.js +2 -3
- package/PivotCollectionPersister.js +2 -2
- package/SqlEntityManager.d.ts +1 -1
- package/SqlEntityManager.js +5 -5
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +3 -4
- package/dialects/mysql/BaseMySqlPlatform.js +1 -2
- package/dialects/mysql/MySqlSchemaHelper.js +21 -10
- package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +55 -45
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.js +4 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +11 -9
- package/package.json +29 -29
- package/plugin/transformer.js +17 -16
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +5 -1
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.js +67 -43
- package/query/QueryBuilder.d.ts +25 -12
- package/query/QueryBuilder.js +105 -49
- package/query/QueryBuilderHelper.js +41 -14
- package/query/ScalarCriteriaNode.js +13 -6
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.js +21 -15
- package/schema/DatabaseTable.js +81 -51
- package/schema/SchemaComparator.js +54 -30
- package/schema/SchemaHelper.js +28 -8
- package/schema/SqlSchemaGenerator.js +13 -7
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +1 -1
package/query/QueryBuilder.js
CHANGED
|
@@ -281,7 +281,8 @@ export class QueryBuilder {
|
|
|
281
281
|
if (populate) {
|
|
282
282
|
populate.children.push(item);
|
|
283
283
|
}
|
|
284
|
-
else {
|
|
284
|
+
else {
|
|
285
|
+
// root entity
|
|
285
286
|
this._populate.push(item);
|
|
286
287
|
}
|
|
287
288
|
this._joinedProps.set(alias, item);
|
|
@@ -384,7 +385,8 @@ export class QueryBuilder {
|
|
|
384
385
|
if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
|
|
385
386
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
386
387
|
for (const key of Object.keys(cond)) {
|
|
387
|
-
if (Utils.isPlainObject(cond[key]) &&
|
|
388
|
+
if (Utils.isPlainObject(cond[key]) &&
|
|
389
|
+
Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every', '$size'].includes(k)))) {
|
|
388
390
|
delete cond[key];
|
|
389
391
|
}
|
|
390
392
|
}
|
|
@@ -435,7 +437,8 @@ export class QueryBuilder {
|
|
|
435
437
|
const topLevel = !op || !(Utils.hasObjectKeys(this._cond) || RawQueryFragment.hasObjectFragments(this._cond));
|
|
436
438
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
|
|
437
439
|
const ignoreBranching = this.__populateWhere === 'infer';
|
|
438
|
-
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
|
|
440
|
+
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
|
|
441
|
+
criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
|
|
439
442
|
// use sub-query to support joining
|
|
440
443
|
this.setFlag(this.type === QueryType.UPDATE ? QueryFlag.UPDATE_SUB_QUERY : QueryFlag.DELETE_SUB_QUERY);
|
|
441
444
|
this.select(this.mainAlias.meta.primaryKeys, true);
|
|
@@ -773,7 +776,7 @@ export class QueryBuilder {
|
|
|
773
776
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables, this._joins);
|
|
774
777
|
}
|
|
775
778
|
this.helper.finalize(this.type, qb, this.mainAlias.meta, this._data, this._returning);
|
|
776
|
-
return this._query.qb = qb;
|
|
779
|
+
return (this._query.qb = qb);
|
|
777
780
|
}
|
|
778
781
|
/**
|
|
779
782
|
* Returns the query with parameters as wildcards.
|
|
@@ -902,7 +905,12 @@ export class QueryBuilder {
|
|
|
902
905
|
this.limit(1);
|
|
903
906
|
}
|
|
904
907
|
const query = this.toQuery();
|
|
905
|
-
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, [
|
|
908
|
+
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, [
|
|
909
|
+
'qb.execute',
|
|
910
|
+
query.sql,
|
|
911
|
+
query.params,
|
|
912
|
+
method,
|
|
913
|
+
]);
|
|
906
914
|
if (cached?.data !== undefined) {
|
|
907
915
|
return cached.data;
|
|
908
916
|
}
|
|
@@ -1059,7 +1067,10 @@ export class QueryBuilder {
|
|
|
1059
1067
|
else {
|
|
1060
1068
|
const qb = (this._type === undefined ? this : this.clone());
|
|
1061
1069
|
qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
|
|
1062
|
-
qb.count(field, distinct ?? qb.hasToManyJoins())
|
|
1070
|
+
qb.count(field, distinct ?? qb.hasToManyJoins())
|
|
1071
|
+
.limit(undefined)
|
|
1072
|
+
.offset(undefined)
|
|
1073
|
+
.orderBy([]);
|
|
1063
1074
|
res = await qb.execute('get', false);
|
|
1064
1075
|
}
|
|
1065
1076
|
return res ? +res.count : 0;
|
|
@@ -1162,12 +1173,35 @@ export class QueryBuilder {
|
|
|
1162
1173
|
reset = reset || [];
|
|
1163
1174
|
// clone array/object properties
|
|
1164
1175
|
const properties = [
|
|
1165
|
-
'flags',
|
|
1166
|
-
'
|
|
1167
|
-
'
|
|
1176
|
+
'flags',
|
|
1177
|
+
'_populate',
|
|
1178
|
+
'_populateWhere',
|
|
1179
|
+
'_populateFilter',
|
|
1180
|
+
'__populateWhere',
|
|
1181
|
+
'_populateMap',
|
|
1182
|
+
'_joins',
|
|
1183
|
+
'_joinedProps',
|
|
1184
|
+
'_cond',
|
|
1185
|
+
'_data',
|
|
1186
|
+
'_orderBy',
|
|
1187
|
+
'_schema',
|
|
1188
|
+
'_indexHint',
|
|
1189
|
+
'_collation',
|
|
1190
|
+
'_cache',
|
|
1191
|
+
'subQueries',
|
|
1192
|
+
'lockMode',
|
|
1193
|
+
'lockTables',
|
|
1194
|
+
'_groupBy',
|
|
1195
|
+
'_having',
|
|
1196
|
+
'_returning',
|
|
1197
|
+
'_comments',
|
|
1198
|
+
'_hintComments',
|
|
1199
|
+
'aliasCounter',
|
|
1200
|
+
'_unionQuery',
|
|
1168
1201
|
];
|
|
1169
1202
|
for (const prop of Object.keys(this)) {
|
|
1170
|
-
if (!preserve?.includes(prop) &&
|
|
1203
|
+
if (!preserve?.includes(prop) &&
|
|
1204
|
+
(reset === true || reset.includes(prop) || ['_helper', '_query'].includes(prop))) {
|
|
1171
1205
|
continue;
|
|
1172
1206
|
}
|
|
1173
1207
|
qb[prop] = properties.includes(prop) ? Utils.copy(this[prop]) : this[prop];
|
|
@@ -1224,9 +1258,10 @@ export class QueryBuilder {
|
|
|
1224
1258
|
addPropertyJoin(prop, ownerAlias, alias, type, path, schema) {
|
|
1225
1259
|
schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
|
|
1226
1260
|
const key = `[tpt]${ownerAlias}#${alias}`;
|
|
1227
|
-
this._joins[key] =
|
|
1228
|
-
|
|
1229
|
-
|
|
1261
|
+
this._joins[key] =
|
|
1262
|
+
prop.kind === ReferenceKind.MANY_TO_ONE
|
|
1263
|
+
? this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, {}, schema)
|
|
1264
|
+
: this.helper.joinOneToReference(prop, ownerAlias, alias, type, {}, schema);
|
|
1230
1265
|
this._joins[key].path = path;
|
|
1231
1266
|
return key;
|
|
1232
1267
|
}
|
|
@@ -1273,7 +1308,7 @@ export class QueryBuilder {
|
|
|
1273
1308
|
}
|
|
1274
1309
|
// For TPT inheritance, owning relations (M:1 and owning 1:1) may have FK columns in a parent table
|
|
1275
1310
|
// Resolve the correct alias for the table that owns the FK column
|
|
1276
|
-
const ownerAlias =
|
|
1311
|
+
const ownerAlias = prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
|
|
1277
1312
|
? this.helper.getTPTAliasForProperty(fromField, fromAlias)
|
|
1278
1313
|
: fromAlias;
|
|
1279
1314
|
this.createAlias(prop.targetMeta.class, alias);
|
|
@@ -1288,7 +1323,7 @@ export class QueryBuilder {
|
|
|
1288
1323
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1289
1324
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1290
1325
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1291
|
-
path ??= `${
|
|
1326
|
+
path ??= `${Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
1292
1327
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
1293
1328
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
1294
1329
|
this._joins[aliasedName].path ??= path;
|
|
@@ -1310,7 +1345,8 @@ export class QueryBuilder {
|
|
|
1310
1345
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1311
1346
|
this._joins[aliasedName].path ??= path;
|
|
1312
1347
|
}
|
|
1313
|
-
else {
|
|
1348
|
+
else {
|
|
1349
|
+
// MANY_TO_ONE
|
|
1314
1350
|
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1315
1351
|
this._joins[aliasedName].path ??= path;
|
|
1316
1352
|
}
|
|
@@ -1361,7 +1397,9 @@ export class QueryBuilder {
|
|
|
1361
1397
|
}
|
|
1362
1398
|
const nest = (prop) => {
|
|
1363
1399
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
1364
|
-
if (childProp.fieldNames &&
|
|
1400
|
+
if (childProp.fieldNames &&
|
|
1401
|
+
(childProp.kind !== ReferenceKind.EMBEDDED || childProp.object) &&
|
|
1402
|
+
childProp.persist !== false) {
|
|
1365
1403
|
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
1366
1404
|
}
|
|
1367
1405
|
else {
|
|
@@ -1412,7 +1450,9 @@ export class QueryBuilder {
|
|
|
1412
1450
|
}
|
|
1413
1451
|
// Start with root alias
|
|
1414
1452
|
let currentAlias = parts[0];
|
|
1415
|
-
let currentMeta = this._aliases[currentAlias]
|
|
1453
|
+
let currentMeta = this._aliases[currentAlias]
|
|
1454
|
+
? this.metadata.get(this._aliases[currentAlias].entityName)
|
|
1455
|
+
: this.mainAlias.meta;
|
|
1416
1456
|
// If first part is not an alias, it's a property of the main entity
|
|
1417
1457
|
if (!this._aliases[currentAlias]) {
|
|
1418
1458
|
currentAlias = this.mainAlias.aliasName;
|
|
@@ -1567,7 +1607,9 @@ export class QueryBuilder {
|
|
|
1567
1607
|
};
|
|
1568
1608
|
lookUpChildren(children, meta.class);
|
|
1569
1609
|
this.andWhere({
|
|
1570
|
-
[meta.root.discriminatorColumn]: children.length > 0
|
|
1610
|
+
[meta.root.discriminatorColumn]: children.length > 0
|
|
1611
|
+
? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
|
|
1612
|
+
: meta.discriminatorValue,
|
|
1571
1613
|
});
|
|
1572
1614
|
}
|
|
1573
1615
|
/**
|
|
@@ -1585,7 +1627,9 @@ export class QueryBuilder {
|
|
|
1585
1627
|
*/
|
|
1586
1628
|
applyTPTJoins() {
|
|
1587
1629
|
const meta = this.mainAlias.meta;
|
|
1588
|
-
if (meta?.inheritanceType !== 'tpt' ||
|
|
1630
|
+
if (meta?.inheritanceType !== 'tpt' ||
|
|
1631
|
+
!meta.tptParent ||
|
|
1632
|
+
![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
|
|
1589
1633
|
return;
|
|
1590
1634
|
}
|
|
1591
1635
|
if (this.tptJoinsApplied) {
|
|
@@ -1609,7 +1653,9 @@ export class QueryBuilder {
|
|
|
1609
1653
|
*/
|
|
1610
1654
|
addTPTParentFields() {
|
|
1611
1655
|
const meta = this.mainAlias.meta;
|
|
1612
|
-
if (meta?.inheritanceType !== 'tpt' ||
|
|
1656
|
+
if (meta?.inheritanceType !== 'tpt' ||
|
|
1657
|
+
!meta.tptParent ||
|
|
1658
|
+
![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
|
|
1613
1659
|
return;
|
|
1614
1660
|
}
|
|
1615
1661
|
if (!this._fields?.includes('*') && !this._fields?.includes(`${this.mainAlias.aliasName}.*`)) {
|
|
@@ -1620,8 +1666,8 @@ export class QueryBuilder {
|
|
|
1620
1666
|
const parentAlias = this._tptAlias[parentMeta.className];
|
|
1621
1667
|
if (parentAlias) {
|
|
1622
1668
|
const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta);
|
|
1623
|
-
parentMeta
|
|
1624
|
-
.filter(prop => this.platform.shouldHaveColumn(prop, []))
|
|
1669
|
+
parentMeta
|
|
1670
|
+
.ownProps.filter(prop => this.platform.shouldHaveColumn(prop, []))
|
|
1625
1671
|
.forEach(prop => this._fields.push(...this.driver.mapPropToFieldNames(this, prop, parentAlias, parentMeta, schema)));
|
|
1626
1672
|
}
|
|
1627
1673
|
parentMeta = parentMeta.tptParent;
|
|
@@ -1648,8 +1694,8 @@ export class QueryBuilder {
|
|
|
1648
1694
|
this.addPropertyJoin(childMeta.tptInverseProp, this.mainAlias.aliasName, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
|
|
1649
1695
|
// Add child fields
|
|
1650
1696
|
const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta);
|
|
1651
|
-
childMeta
|
|
1652
|
-
.filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, []))
|
|
1697
|
+
childMeta
|
|
1698
|
+
.ownProps.filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, []))
|
|
1653
1699
|
.forEach(prop => this._fields.push(...this.driver.mapPropToFieldNames(this, prop, childAlias, childMeta, schema)));
|
|
1654
1700
|
}
|
|
1655
1701
|
// Add computed discriminator (CASE WHEN to determine concrete type)
|
|
@@ -1700,7 +1746,11 @@ export class QueryBuilder {
|
|
|
1700
1746
|
if (!this.flags.has(QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
1701
1747
|
this.flags.add(QueryFlag.PAGINATE);
|
|
1702
1748
|
}
|
|
1703
|
-
if (meta &&
|
|
1749
|
+
if (meta &&
|
|
1750
|
+
!meta.virtual &&
|
|
1751
|
+
this.flags.has(QueryFlag.PAGINATE) &&
|
|
1752
|
+
!this.flags.has(QueryFlag.DISABLE_PAGINATE) &&
|
|
1753
|
+
(this._limit > 0 || this._offset > 0)) {
|
|
1704
1754
|
this.wrapPaginateSubQuery(meta);
|
|
1705
1755
|
}
|
|
1706
1756
|
if (meta && (this.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
@@ -1717,7 +1767,10 @@ export class QueryBuilder {
|
|
|
1717
1767
|
if (meta && this.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
1718
1768
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
1719
1769
|
meta.relations
|
|
1720
|
-
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
1770
|
+
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
1771
|
+
!prop.owner &&
|
|
1772
|
+
!relationsToPopulate.includes(prop.name) &&
|
|
1773
|
+
!relationsToPopulate.includes(`${prop.name}:ref`))
|
|
1721
1774
|
.map(prop => ({ field: `${prop.name}:ref` }))
|
|
1722
1775
|
.forEach(item => this._populate.push(item));
|
|
1723
1776
|
}
|
|
@@ -1734,7 +1787,8 @@ export class QueryBuilder {
|
|
|
1734
1787
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.targetMeta.class);
|
|
1735
1788
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1736
1789
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
|
|
1737
|
-
this._joins[aliasedName].path =
|
|
1790
|
+
this._joins[aliasedName].path =
|
|
1791
|
+
`${Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className}.${prop.name}`;
|
|
1738
1792
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
1739
1793
|
this.createAlias(prop.targetMeta.class, alias);
|
|
1740
1794
|
}
|
|
@@ -1754,9 +1808,7 @@ export class QueryBuilder {
|
|
|
1754
1808
|
join.cond = { ...join.cond };
|
|
1755
1809
|
}
|
|
1756
1810
|
if (typeof this[key] === 'object') {
|
|
1757
|
-
const cond = CriteriaNodeFactory
|
|
1758
|
-
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1759
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1811
|
+
const cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, this[key]).process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1760
1812
|
// there might be new joins created by processing the `populateWhere` object
|
|
1761
1813
|
joins = Object.values(this._joins);
|
|
1762
1814
|
this.mergeOnConditions(joins, cond, filter);
|
|
@@ -1778,9 +1830,11 @@ export class QueryBuilder {
|
|
|
1778
1830
|
// https://stackoverflow.com/a/56815807/3665878
|
|
1779
1831
|
if (parentJoin && !filter) {
|
|
1780
1832
|
const nested = (parentJoin.nested ??= new Set());
|
|
1781
|
-
join.type =
|
|
1782
|
-
|
|
1783
|
-
|
|
1833
|
+
join.type =
|
|
1834
|
+
join.type === JoinType.innerJoin ||
|
|
1835
|
+
[ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(parentJoin.prop.kind)
|
|
1836
|
+
? JoinType.nestedInnerJoin
|
|
1837
|
+
: JoinType.nestedLeftJoin;
|
|
1784
1838
|
nested.add(join);
|
|
1785
1839
|
}
|
|
1786
1840
|
if (join.cond[k]) {
|
|
@@ -1814,18 +1868,14 @@ export class QueryBuilder {
|
|
|
1814
1868
|
join.parent = joins.find(j => j.alias === join.ownerAlias);
|
|
1815
1869
|
// https://stackoverflow.com/a/56815807/3665878
|
|
1816
1870
|
if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
|
|
1817
|
-
const nested = (
|
|
1818
|
-
join.type = join.type === JoinType.innerJoin
|
|
1819
|
-
? JoinType.nestedInnerJoin
|
|
1820
|
-
: JoinType.nestedLeftJoin;
|
|
1871
|
+
const nested = (join.parent.nested ??= new Set());
|
|
1872
|
+
join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1821
1873
|
nested.add(join);
|
|
1822
1874
|
}
|
|
1823
1875
|
else if (join.parent?.type === JoinType.nestedInnerJoin) {
|
|
1824
1876
|
const group = lookupParentGroup(join.parent);
|
|
1825
|
-
const nested = group ?? (
|
|
1826
|
-
join.type = join.type === JoinType.innerJoin
|
|
1827
|
-
? JoinType.nestedInnerJoin
|
|
1828
|
-
: JoinType.nestedLeftJoin;
|
|
1877
|
+
const nested = group ?? (join.parent.nested ??= new Set());
|
|
1878
|
+
join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1829
1879
|
nested.add(join);
|
|
1830
1880
|
}
|
|
1831
1881
|
}
|
|
@@ -1917,7 +1967,9 @@ export class QueryBuilder {
|
|
|
1917
1967
|
// Transfer WHERE conditions to ORDER BY joins (GH #6160)
|
|
1918
1968
|
this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths);
|
|
1919
1969
|
const { sql, params } = subSubQuery.compile();
|
|
1920
|
-
this.select(this._fields).where({
|
|
1970
|
+
this.select(this._fields).where({
|
|
1971
|
+
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
1972
|
+
});
|
|
1921
1973
|
}
|
|
1922
1974
|
/**
|
|
1923
1975
|
* Computes the set of populate paths from the _populate hints.
|
|
@@ -1966,9 +2018,7 @@ export class QueryBuilder {
|
|
|
1966
2018
|
* Removes joins that are not used for population or ordering to improve performance.
|
|
1967
2019
|
*/
|
|
1968
2020
|
pruneJoinsForPagination(meta, populatePaths) {
|
|
1969
|
-
const orderByAliases = this._orderBy
|
|
1970
|
-
.flatMap(hint => Object.keys(hint))
|
|
1971
|
-
.map(k => k.split('.')[0]);
|
|
2021
|
+
const orderByAliases = this._orderBy.flatMap(hint => Object.keys(hint)).map(k => k.split('.')[0]);
|
|
1972
2022
|
const joins = Object.entries(this._joins);
|
|
1973
2023
|
const rootAlias = this.alias;
|
|
1974
2024
|
function addParentAlias(alias) {
|
|
@@ -2097,8 +2147,12 @@ export class QueryBuilder {
|
|
|
2097
2147
|
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
|
2098
2148
|
const object = { ...this };
|
|
2099
2149
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'type'];
|
|
2100
|
-
Object.keys(object)
|
|
2101
|
-
|
|
2150
|
+
Object.keys(object)
|
|
2151
|
+
.filter(k => k.startsWith('_'))
|
|
2152
|
+
.forEach(k => delete object[k]);
|
|
2153
|
+
Object.keys(object)
|
|
2154
|
+
.filter(k => object[k] == null)
|
|
2155
|
+
.forEach(k => delete object[k]);
|
|
2102
2156
|
hidden.forEach(k => delete object[k]);
|
|
2103
2157
|
let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
|
|
2104
2158
|
if (this._data) {
|
|
@@ -2117,7 +2171,9 @@ export class QueryBuilder {
|
|
|
2117
2171
|
if (!Utils.isEmpty(this._orderBy)) {
|
|
2118
2172
|
object.orderBy = this._orderBy;
|
|
2119
2173
|
}
|
|
2120
|
-
const name = this._mainAlias
|
|
2174
|
+
const name = this._mainAlias
|
|
2175
|
+
? `${prefix}QueryBuilder<${Utils.className(this._mainAlias?.entityName)}>`
|
|
2176
|
+
: 'QueryBuilder';
|
|
2121
2177
|
const ret = inspect(object, { depth });
|
|
2122
2178
|
return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
|
|
2123
2179
|
}
|
|
@@ -174,13 +174,25 @@ export class QueryBuilderHelper {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
return {
|
|
177
|
-
prop,
|
|
178
|
-
|
|
177
|
+
prop,
|
|
178
|
+
type,
|
|
179
|
+
cond,
|
|
180
|
+
ownerAlias,
|
|
181
|
+
alias,
|
|
182
|
+
table,
|
|
183
|
+
schema,
|
|
184
|
+
joinColumns,
|
|
185
|
+
inverseJoinColumns,
|
|
186
|
+
primaryKeys,
|
|
179
187
|
};
|
|
180
188
|
}
|
|
181
189
|
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
182
190
|
return {
|
|
183
|
-
prop,
|
|
191
|
+
prop,
|
|
192
|
+
type,
|
|
193
|
+
cond,
|
|
194
|
+
ownerAlias,
|
|
195
|
+
alias,
|
|
184
196
|
table: this.getTableName(prop.targetMeta.class),
|
|
185
197
|
schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
186
198
|
joinColumns: prop.referencedColumnNames,
|
|
@@ -193,7 +205,9 @@ export class QueryBuilderHelper {
|
|
|
193
205
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
194
206
|
const ret = {
|
|
195
207
|
[`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
|
|
196
|
-
prop,
|
|
208
|
+
prop,
|
|
209
|
+
type,
|
|
210
|
+
ownerAlias,
|
|
197
211
|
alias: pivotAlias,
|
|
198
212
|
inverseAlias: alias,
|
|
199
213
|
joinColumns: prop.joinColumns,
|
|
@@ -255,7 +269,9 @@ export class QueryBuilderHelper {
|
|
|
255
269
|
conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
|
|
256
270
|
});
|
|
257
271
|
}
|
|
258
|
-
if (join.prop.targetMeta?.root.inheritanceType === 'sti' &&
|
|
272
|
+
if (join.prop.targetMeta?.root.inheritanceType === 'sti' &&
|
|
273
|
+
join.prop.targetMeta?.discriminatorValue &&
|
|
274
|
+
!join.path?.endsWith('[pivot]')) {
|
|
259
275
|
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
260
276
|
const alias = join.inverseAlias ?? join.alias;
|
|
261
277
|
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
@@ -454,7 +470,7 @@ export class QueryBuilderHelper {
|
|
|
454
470
|
// grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
|
|
455
471
|
if (size > 1) {
|
|
456
472
|
const subCondition = Object.entries(value).map(([subKey, subValue]) => {
|
|
457
|
-
return
|
|
473
|
+
return { [key]: { [subKey]: subValue } };
|
|
458
474
|
});
|
|
459
475
|
for (const sub of subCondition) {
|
|
460
476
|
this.append(() => this._appendQueryCondition(type, sub, '$and'), parts, params);
|
|
@@ -520,7 +536,7 @@ export class QueryBuilderHelper {
|
|
|
520
536
|
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
521
537
|
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
522
538
|
}
|
|
523
|
-
else if (value[op] instanceof Raw ||
|
|
539
|
+
else if (value[op] instanceof Raw || typeof value[op]?.toRaw === 'function') {
|
|
524
540
|
const query = value[op] instanceof Raw ? value[op] : value[op].toRaw();
|
|
525
541
|
const mappedKey = this.mapper(key, type, query, null);
|
|
526
542
|
let sql = query.sql;
|
|
@@ -548,7 +564,11 @@ export class QueryBuilderHelper {
|
|
|
548
564
|
return `(${tmp.join(', ')})`;
|
|
549
565
|
}
|
|
550
566
|
if (prop?.customType instanceof ArrayType) {
|
|
551
|
-
const item = prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
567
|
+
const item = prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
568
|
+
fromQuery: true,
|
|
569
|
+
key,
|
|
570
|
+
mode: 'query',
|
|
571
|
+
});
|
|
552
572
|
params.push(item);
|
|
553
573
|
}
|
|
554
574
|
else {
|
|
@@ -628,11 +648,14 @@ export class QueryBuilderHelper {
|
|
|
628
648
|
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || Raw.isKnownFragment(f);
|
|
629
649
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
630
650
|
/* v8 ignore next */
|
|
631
|
-
const rawColumn = typeof column === 'string'
|
|
651
|
+
const rawColumn = typeof column === 'string'
|
|
652
|
+
? column
|
|
653
|
+
.split('.')
|
|
654
|
+
.map(e => this.platform.quoteIdentifier(e))
|
|
655
|
+
.join('.')
|
|
656
|
+
: column;
|
|
632
657
|
const customOrder = prop?.customOrder;
|
|
633
|
-
let colPart = customOrder
|
|
634
|
-
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
635
|
-
: rawColumn;
|
|
658
|
+
let colPart = customOrder ? this.platform.generateCustomOrder(rawColumn, customOrder) : rawColumn;
|
|
636
659
|
if (isRaw(colPart)) {
|
|
637
660
|
colPart = this.platform.formatQuery(colPart.sql, colPart.params);
|
|
638
661
|
}
|
|
@@ -671,7 +694,9 @@ export class QueryBuilderHelper {
|
|
|
671
694
|
const fields = returningProps.flatMap((prop) => {
|
|
672
695
|
if (prop.hasConvertToJSValueSQL) {
|
|
673
696
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
674
|
-
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
|
|
697
|
+
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
|
|
698
|
+
' as ' +
|
|
699
|
+
this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
675
700
|
return [raw(sql)];
|
|
676
701
|
}
|
|
677
702
|
return prop.fieldNames;
|
|
@@ -769,7 +794,9 @@ export class QueryBuilderHelper {
|
|
|
769
794
|
// skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
|
|
770
795
|
const keys = Utils.getObjectQueryKeys(sub);
|
|
771
796
|
const val = sub[keys[0]];
|
|
772
|
-
const simple = !Utils.isPlainObject(val) ||
|
|
797
|
+
const simple = !Utils.isPlainObject(val) ||
|
|
798
|
+
Utils.getObjectKeysSize(val) === 1 ||
|
|
799
|
+
Object.keys(val).every(k => !Utils.isOperator(k));
|
|
773
800
|
if (keys.length === 1 && simple) {
|
|
774
801
|
this.append(() => this._appendQueryCondition(type, sub, operator), parts, params);
|
|
775
802
|
continue;
|
|
@@ -7,7 +7,8 @@ import { QueryBuilder } from './QueryBuilder.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export class ScalarCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
-
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
10
|
+
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
11
|
+
(this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
11
12
|
const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
|
|
12
13
|
if (this.shouldJoin(qb, nestedAlias)) {
|
|
13
14
|
const path = this.getPath();
|
|
@@ -36,14 +37,20 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
36
37
|
return this.shouldJoin(qb, alias);
|
|
37
38
|
}
|
|
38
39
|
shouldJoin(qb, nestedAlias) {
|
|
39
|
-
if (!this.parent ||
|
|
40
|
+
if (!this.parent ||
|
|
41
|
+
!this.prop ||
|
|
42
|
+
(nestedAlias && [QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT))) {
|
|
40
43
|
return false;
|
|
41
44
|
}
|
|
42
45
|
switch (this.prop.kind) {
|
|
43
|
-
case ReferenceKind.ONE_TO_MANY:
|
|
44
|
-
|
|
45
|
-
case ReferenceKind.
|
|
46
|
-
|
|
46
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
47
|
+
return true;
|
|
48
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
49
|
+
return true;
|
|
50
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
51
|
+
return !this.prop.owner;
|
|
52
|
+
default:
|
|
53
|
+
return false; // SCALAR, MANY_TO_ONE
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
}
|
package/query/raw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { raw as raw_, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { raw as raw_, Utils, } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
4
4
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -81,8 +81,11 @@ export class DatabaseSchema {
|
|
|
81
81
|
const parts = config.get('migrations').tableName.split('.');
|
|
82
82
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
83
83
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
84
|
-
const tables = allTables.filter(t => this.isTableNameAllowed(t.table_name, takeTables, skipTables) &&
|
|
85
|
-
|
|
84
|
+
const tables = allTables.filter(t => this.isTableNameAllowed(t.table_name, takeTables, skipTables) &&
|
|
85
|
+
(t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName)));
|
|
86
|
+
await platform
|
|
87
|
+
.getSchemaHelper()
|
|
88
|
+
.loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
86
89
|
// Load views from database
|
|
87
90
|
await platform.getSchemaHelper().loadViews(schema, connection);
|
|
88
91
|
// Load materialized views (PostgreSQL only)
|
|
@@ -140,9 +143,7 @@ export class DatabaseSchema {
|
|
|
140
143
|
table.comment = meta.comment;
|
|
141
144
|
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
|
142
145
|
// For all other entities (including TPT root), use all props
|
|
143
|
-
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps
|
|
144
|
-
? meta.ownProps
|
|
145
|
-
: meta.props;
|
|
146
|
+
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
|
|
146
147
|
for (const prop of propsToProcess) {
|
|
147
148
|
if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
|
|
148
149
|
continue;
|
|
@@ -177,7 +178,9 @@ export class DatabaseSchema {
|
|
|
177
178
|
table.addIndex(meta, { properties: pkPropsForIndex.map(prop => prop.name) }, 'primary');
|
|
178
179
|
for (const check of meta.checks) {
|
|
179
180
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
180
|
-
const expression = isRaw(check.expression)
|
|
181
|
+
const expression = isRaw(check.expression)
|
|
182
|
+
? platform.formatQuery(check.expression.sql, check.expression.params)
|
|
183
|
+
: check.expression;
|
|
181
184
|
table.addCheck({
|
|
182
185
|
name: check.name,
|
|
183
186
|
expression,
|
|
@@ -229,7 +232,7 @@ export class DatabaseSchema {
|
|
|
229
232
|
const pkColumnNames = meta.primaryKeys.flatMap(pk => meta.properties[pk].fieldNames);
|
|
230
233
|
const parentPkColumnNames = parent.primaryKeys.flatMap(pk => parent.properties[pk].fieldNames);
|
|
231
234
|
// Determine the parent table name with schema
|
|
232
|
-
const parentSchema = parent.schema === '*' ? undefined : parent.schema ?? config.get('schema', platform.getDefaultSchemaName());
|
|
235
|
+
const parentSchema = parent.schema === '*' ? undefined : (parent.schema ?? config.get('schema', platform.getDefaultSchemaName()));
|
|
233
236
|
const parentTableName = parentSchema ? `${parentSchema}.${parent.tableName}` : parent.tableName;
|
|
234
237
|
// Create FK constraint name
|
|
235
238
|
const constraintName = platform.getIndexName(table.name, pkColumnNames, 'foreign');
|
|
@@ -284,7 +287,8 @@ export class DatabaseSchema {
|
|
|
284
287
|
if (rootProp.kind === ReferenceKind.EMBEDDED) {
|
|
285
288
|
return prop === rootProp || !rootProp.object;
|
|
286
289
|
}
|
|
287
|
-
return [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE].includes(prop.kind) ||
|
|
290
|
+
return ([ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE].includes(prop.kind) ||
|
|
291
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
288
292
|
}
|
|
289
293
|
toJSON() {
|
|
290
294
|
const { platform, namespaces, ...rest } = this;
|
|
@@ -293,19 +297,21 @@ export class DatabaseSchema {
|
|
|
293
297
|
prune(schema, wildcardSchemaTables) {
|
|
294
298
|
const hasWildcardSchema = wildcardSchemaTables.length > 0;
|
|
295
299
|
this.tables = this.tables.filter(table => {
|
|
296
|
-
return (!schema && !hasWildcardSchema) // no schema specified and we don't have any multi-schema entity
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
return ((!schema && !hasWildcardSchema) || // no schema specified and we don't have any multi-schema entity
|
|
301
|
+
table.schema === schema || // specified schema matches the table's one
|
|
302
|
+
(!schema && !wildcardSchemaTables.includes(table.name))); // no schema specified and the table has fixed one provided
|
|
299
303
|
});
|
|
300
304
|
this.views = this.views.filter(view => {
|
|
301
305
|
/* v8 ignore next */
|
|
302
|
-
return (!schema && !hasWildcardSchema)
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
return ((!schema && !hasWildcardSchema) ||
|
|
307
|
+
view.schema === schema ||
|
|
308
|
+
(!schema && !wildcardSchemaTables.includes(view.name)));
|
|
305
309
|
});
|
|
306
310
|
// remove namespaces of ignored tables and views
|
|
307
311
|
for (const ns of this.namespaces) {
|
|
308
|
-
if (!this.tables.some(t => t.schema === ns) &&
|
|
312
|
+
if (!this.tables.some(t => t.schema === ns) &&
|
|
313
|
+
!this.views.some(v => v.schema === ns) &&
|
|
314
|
+
!Object.values(this.nativeEnums).some(e => e.schema === ns)) {
|
|
309
315
|
this.namespaces.delete(ns);
|
|
310
316
|
}
|
|
311
317
|
}
|