@mikro-orm/knex 7.0.0-dev.3 → 7.0.0-dev.30
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 +6 -4
- package/AbstractSqlConnection.js +33 -19
- package/AbstractSqlDriver.d.ts +7 -7
- package/AbstractSqlDriver.js +169 -163
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +6 -2
- package/README.md +1 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +48 -5
- package/dialects/mysql/MySqlPlatform.js +2 -1
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -1
- package/dialects/sqlite/BaseSqliteConnection.js +8 -2
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +0 -1
- package/dialects/sqlite/BaseSqlitePlatform.js +0 -4
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/package.json +4 -4
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +5 -9
- package/query/CriteriaNodeFactory.js +10 -5
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.js +30 -7
- package/query/QueryBuilder.d.ts +15 -1
- package/query/QueryBuilder.js +92 -17
- package/query/QueryBuilderHelper.js +4 -10
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +7 -5
- package/schema/DatabaseSchema.js +18 -2
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +39 -6
- package/schema/SchemaComparator.js +1 -1
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +9 -5
- package/schema/SqlSchemaGenerator.d.ts +6 -1
- package/schema/SqlSchemaGenerator.js +25 -5
- package/typings.d.ts +7 -2
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { type AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
export declare class PivotCollectionPersister<Entity extends object> {
|
|
4
4
|
private readonly meta;
|
|
5
5
|
private readonly driver;
|
|
6
6
|
private readonly ctx?;
|
|
7
7
|
private readonly schema?;
|
|
8
|
+
private readonly loggerContext?;
|
|
8
9
|
private readonly platform;
|
|
9
10
|
private readonly inserts;
|
|
10
11
|
private readonly deletes;
|
|
11
12
|
private readonly batchSize;
|
|
12
13
|
private order;
|
|
13
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined);
|
|
14
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined, loggerContext?: Dictionary | undefined);
|
|
14
15
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[]): void;
|
|
15
16
|
private enqueueInsert;
|
|
16
17
|
private enqueueDelete;
|
|
@@ -38,16 +38,18 @@ export class PivotCollectionPersister {
|
|
|
38
38
|
driver;
|
|
39
39
|
ctx;
|
|
40
40
|
schema;
|
|
41
|
+
loggerContext;
|
|
41
42
|
platform;
|
|
42
43
|
inserts = new Map();
|
|
43
44
|
deletes = new Map();
|
|
44
45
|
batchSize;
|
|
45
46
|
order = 0;
|
|
46
|
-
constructor(meta, driver, ctx, schema) {
|
|
47
|
+
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
47
48
|
this.meta = meta;
|
|
48
49
|
this.driver = driver;
|
|
49
50
|
this.ctx = ctx;
|
|
50
51
|
this.schema = schema;
|
|
52
|
+
this.loggerContext = loggerContext;
|
|
51
53
|
this.platform = this.driver.getPlatform();
|
|
52
54
|
this.batchSize = this.driver.config.get('batchSize');
|
|
53
55
|
}
|
|
@@ -99,6 +101,7 @@ export class PivotCollectionPersister {
|
|
|
99
101
|
await this.driver.nativeDelete(this.meta.className, cond, {
|
|
100
102
|
ctx: this.ctx,
|
|
101
103
|
schema: this.schema,
|
|
104
|
+
loggerContext: this.loggerContext,
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
|
@@ -118,13 +121,14 @@ export class PivotCollectionPersister {
|
|
|
118
121
|
schema: this.schema,
|
|
119
122
|
convertCustomTypes: false,
|
|
120
123
|
processCollections: false,
|
|
124
|
+
loggerContext: this.loggerContext,
|
|
121
125
|
});
|
|
122
126
|
}
|
|
123
127
|
/* v8 ignore start */
|
|
124
128
|
}
|
|
125
129
|
else {
|
|
126
130
|
await Utils.runSerial(items, item => {
|
|
127
|
-
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write')
|
|
131
|
+
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write', false, this.loggerContext)
|
|
128
132
|
.withSchema(this.schema)
|
|
129
133
|
.insert(item)
|
|
130
134
|
.execute('run', false);
|
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-or
|
|
|
11
11
|
[](https://discord.gg/w8bjxFHS7X)
|
|
12
12
|
[](https://www.npmjs.com/package/@mikro-orm/core)
|
|
13
13
|
[](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
|
|
14
|
-
[](https://codeclimate.com/github/mikro-orm/mikro-orm/maintainability)
|
|
15
14
|
[](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
|
|
16
15
|
|
|
17
16
|
## 🤔 Unit of What?
|
|
@@ -141,7 +140,7 @@ There is also auto-generated [CHANGELOG.md](CHANGELOG.md) file based on commit m
|
|
|
141
140
|
- [Composite and Foreign Keys as Primary Key](https://mikro-orm.io/docs/composite-keys)
|
|
142
141
|
- [Filters](https://mikro-orm.io/docs/filters)
|
|
143
142
|
- [Using `QueryBuilder`](https://mikro-orm.io/docs/query-builder)
|
|
144
|
-
- [
|
|
143
|
+
- [Populating relations](https://mikro-orm.io/docs/populating-relations)
|
|
145
144
|
- [Property Validation](https://mikro-orm.io/docs/property-validation)
|
|
146
145
|
- [Lifecycle Hooks](https://mikro-orm.io/docs/events#hooks)
|
|
147
146
|
- [Vanilla JS Support](https://mikro-orm.io/docs/usage-with-js)
|
|
@@ -5,6 +5,8 @@ export declare class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
5
5
|
sql: string;
|
|
6
6
|
params: unknown[];
|
|
7
7
|
};
|
|
8
|
+
protected compileInsert(): void;
|
|
9
|
+
private appendOutputTable;
|
|
8
10
|
private compileUpsert;
|
|
9
11
|
protected compileSelect(): void;
|
|
10
12
|
protected addLockClause(): void;
|
|
@@ -12,6 +12,10 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
12
12
|
if (this.options.flags?.has(QueryFlag.IDENTITY_INSERT)) {
|
|
13
13
|
this.parts.push(`set identity_insert ${this.getTableName()} on;`);
|
|
14
14
|
}
|
|
15
|
+
const { prefix, suffix } = this.appendOutputTable();
|
|
16
|
+
if (prefix) {
|
|
17
|
+
this.parts.push(prefix);
|
|
18
|
+
}
|
|
15
19
|
if (this.options.comment) {
|
|
16
20
|
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
|
|
17
21
|
}
|
|
@@ -37,7 +41,11 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
37
41
|
this.compileTruncate();
|
|
38
42
|
break;
|
|
39
43
|
}
|
|
40
|
-
if (
|
|
44
|
+
if (suffix) {
|
|
45
|
+
this.parts[this.parts.length - 1] += ';';
|
|
46
|
+
this.parts.push(suffix);
|
|
47
|
+
}
|
|
48
|
+
else if ([QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE].includes(this.type)) {
|
|
41
49
|
this.parts[this.parts.length - 1] += '; select @@rowcount;';
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -46,6 +54,37 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
46
54
|
}
|
|
47
55
|
return this.combineParts();
|
|
48
56
|
}
|
|
57
|
+
compileInsert() {
|
|
58
|
+
if (!this.options.data) {
|
|
59
|
+
throw new Error('No data provided');
|
|
60
|
+
}
|
|
61
|
+
this.parts.push('insert');
|
|
62
|
+
this.addHintComment();
|
|
63
|
+
this.parts.push(`into ${this.getTableName()}`);
|
|
64
|
+
if (Object.keys(this.options.data).length === 0) {
|
|
65
|
+
this.addOutputClause('inserted');
|
|
66
|
+
this.parts.push('default values');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const parts = this.processInsertData();
|
|
70
|
+
if (this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
|
|
71
|
+
this.parts[this.parts.length - 2] += ' into #out ';
|
|
72
|
+
}
|
|
73
|
+
this.parts.push(parts.join(', '));
|
|
74
|
+
}
|
|
75
|
+
appendOutputTable() {
|
|
76
|
+
if (!this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
|
|
77
|
+
return { prefix: '', suffix: '' };
|
|
78
|
+
}
|
|
79
|
+
const returningFields = this.options.returning;
|
|
80
|
+
const selections = returningFields
|
|
81
|
+
.map(field => `[t].${this.platform.quoteIdentifier(field)}`)
|
|
82
|
+
.join(',');
|
|
83
|
+
return {
|
|
84
|
+
prefix: `select top(0) ${selections} into #out from ${this.getTableName()} as t left join ${this.getTableName()} on 0 = 1;`,
|
|
85
|
+
suffix: `select ${selections} from #out as t; drop table #out`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
49
88
|
compileUpsert() {
|
|
50
89
|
const clause = this.options.onConflict;
|
|
51
90
|
const dataAsArray = Utils.asArray(this.options.data);
|
|
@@ -65,9 +104,11 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
65
104
|
this.params.push(...clause.fields.params);
|
|
66
105
|
}
|
|
67
106
|
else if (clause.fields.length > 0) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
107
|
+
const fields = clause.fields.map(field => {
|
|
108
|
+
const col = this.quote(field);
|
|
109
|
+
return `${this.getTableName()}.${col} = tsource.${col}`;
|
|
110
|
+
});
|
|
111
|
+
this.parts.push(`on ${fields.join(' and ')}`);
|
|
71
112
|
}
|
|
72
113
|
const sourceColumns = keys.map(field => `tsource.${this.quote(field)}`).join(', ');
|
|
73
114
|
const destinationColumns = keys.map(field => this.quote(field)).join(', ');
|
|
@@ -80,7 +121,9 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
80
121
|
}
|
|
81
122
|
this.parts.push('then update set');
|
|
82
123
|
if (!clause.merge || Array.isArray(clause.merge)) {
|
|
83
|
-
const parts =
|
|
124
|
+
const parts = (clause.merge || keys)
|
|
125
|
+
.filter(field => !Array.isArray(clause.fields) || !clause.fields.includes(field))
|
|
126
|
+
.map((column) => `${this.quote(column)} = tsource.${this.quote(column)}`);
|
|
84
127
|
this.parts.push(parts.join(', '));
|
|
85
128
|
}
|
|
86
129
|
else if (typeof clause.merge === 'object') {
|
|
@@ -81,7 +81,8 @@ export class MySqlPlatform extends AbstractSqlPlatform {
|
|
|
81
81
|
}
|
|
82
82
|
const indexName = super.getIndexName(tableName, columns, type);
|
|
83
83
|
if (indexName.length > 64) {
|
|
84
|
-
|
|
84
|
+
const hashAlgorithm = this.config.get('hashAlgorithm');
|
|
85
|
+
return `${indexName.substring(0, 56 - type.length)}_${Utils.hash(indexName, 5, hashAlgorithm)}_${type}`;
|
|
85
86
|
}
|
|
86
87
|
return indexName;
|
|
87
88
|
}
|
|
@@ -3,9 +3,15 @@ import { CompiledQuery } from 'kysely';
|
|
|
3
3
|
import { Utils } from '@mikro-orm/core';
|
|
4
4
|
import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
|
|
5
5
|
export class BaseSqliteConnection extends AbstractSqlConnection {
|
|
6
|
-
async connect() {
|
|
6
|
+
async connect(simple = false) {
|
|
7
7
|
await super.connect();
|
|
8
|
-
|
|
8
|
+
if (simple) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const dbName = this.config.get('dbName');
|
|
12
|
+
if (dbName && dbName !== ':memory:') {
|
|
13
|
+
Utils.ensureDir(dirname(this.config.get('dbName')));
|
|
14
|
+
}
|
|
9
15
|
await this.client.executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
|
|
10
16
|
}
|
|
11
17
|
getClientUrl() {
|
|
@@ -63,7 +63,6 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
63
63
|
*/
|
|
64
64
|
processDateProperty(value: unknown): string | number | Date;
|
|
65
65
|
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
|
|
66
|
-
supportsDownMigrations(): boolean;
|
|
67
66
|
supportsDeferredUniqueConstraints(): boolean;
|
|
68
67
|
getFullTextWhereClause(): string;
|
|
69
68
|
quoteVersionValue(value: Date | number, prop: EntityProperty): Date | string | number;
|
|
@@ -84,10 +84,6 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
84
84
|
}
|
|
85
85
|
return super.getIndexName(tableName, columns, type);
|
|
86
86
|
}
|
|
87
|
-
// TODO enable once tests are green
|
|
88
|
-
supportsDownMigrations() {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
87
|
supportsDeferredUniqueConstraints() {
|
|
92
88
|
return false;
|
|
93
89
|
}
|
|
@@ -167,7 +167,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
167
167
|
if (col.hidden > 1) {
|
|
168
168
|
/* v8 ignore next */
|
|
169
169
|
const storage = col.hidden === 2 ? 'virtual' : 'stored';
|
|
170
|
-
const re = `(generated always)? as \\((.*)\\)( ${storage})
|
|
170
|
+
const re = new RegExp(`(generated always)? as \\((.*)\\)( ${storage})?$`, 'i');
|
|
171
171
|
const match = columnDefinitions[col.name].definition.match(re);
|
|
172
172
|
if (match) {
|
|
173
173
|
generated = `${match[2]} ${storage}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.30",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"kysely": "
|
|
53
|
+
"kysely": "0.28.7",
|
|
54
54
|
"sqlstring": "2.3.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^6.
|
|
57
|
+
"@mikro-orm/core": "^6.5.7"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.0.0-dev.30"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/query/CriteriaNode.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class CriteriaNode<T extends object> implements ICriteriaNode<T>
|
|
|
20
20
|
shouldInline(payload: any): boolean;
|
|
21
21
|
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
22
22
|
shouldRename(payload: any): boolean;
|
|
23
|
-
renameFieldToPK<T>(qb: IQueryBuilder<T
|
|
23
|
+
renameFieldToPK<T>(qb: IQueryBuilder<T>, ownerAlias?: string): string;
|
|
24
24
|
getPath(addIndex?: boolean): string;
|
|
25
25
|
private isPivotJoin;
|
|
26
26
|
getPivotPath(path: string): string;
|
package/query/CriteriaNode.js
CHANGED
|
@@ -65,20 +65,16 @@ export class CriteriaNode {
|
|
|
65
65
|
default: return false;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
renameFieldToPK(qb) {
|
|
69
|
-
|
|
68
|
+
renameFieldToPK(qb, ownerAlias) {
|
|
69
|
+
const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
70
70
|
if (!joinAlias && this.parent && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) && this.prop.owner) {
|
|
71
|
-
|
|
72
|
-
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${
|
|
71
|
+
const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
|
|
72
|
+
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
73
73
|
}
|
|
74
|
-
const alias = joinAlias ?? qb.alias;
|
|
74
|
+
const alias = joinAlias ?? ownerAlias ?? qb.alias;
|
|
75
75
|
if (this.prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
76
76
|
return Utils.getPrimaryKeyHash(this.prop.inverseJoinColumns.map(col => `${alias}.${col}`));
|
|
77
77
|
}
|
|
78
|
-
// if we found a matching join, we need to use the target table column names, as we use that alias instead of the root
|
|
79
|
-
if (!joinAlias && this.prop.owner && this.prop.joinColumns.length > 1) {
|
|
80
|
-
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
81
|
-
}
|
|
82
78
|
return Utils.getPrimaryKeyHash(this.prop.referencedColumnNames.map(col => `${alias}.${col}`));
|
|
83
79
|
}
|
|
84
80
|
getPath(addIndex = false) {
|
|
@@ -46,10 +46,14 @@ export class CriteriaNodeFactory {
|
|
|
46
46
|
static createObjectItemNode(metadata, entityName, node, payload, key, meta) {
|
|
47
47
|
const prop = meta?.properties[key];
|
|
48
48
|
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR ? prop.type : entityName;
|
|
49
|
-
|
|
49
|
+
const isNotEmbedded = prop?.kind !== ReferenceKind.EMBEDDED;
|
|
50
|
+
if (isNotEmbedded && prop?.customType instanceof JsonType) {
|
|
50
51
|
return this.createScalarNode(metadata, childEntity, payload[key], node, key);
|
|
51
52
|
}
|
|
52
|
-
if (prop?.kind
|
|
53
|
+
if (prop?.kind === ReferenceKind.SCALAR && payload[key] != null && Object.keys(payload[key]).some(f => Utils.isGroupOperator(f))) {
|
|
54
|
+
throw ValidationError.cannotUseGroupOperatorsInsideScalars(entityName, prop.name, payload);
|
|
55
|
+
}
|
|
56
|
+
if (isNotEmbedded) {
|
|
53
57
|
return this.createNode(metadata, childEntity, payload[key], node, key);
|
|
54
58
|
}
|
|
55
59
|
if (payload[key] == null) {
|
|
@@ -66,11 +70,12 @@ export class CriteriaNodeFactory {
|
|
|
66
70
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
|
|
67
71
|
}
|
|
68
72
|
const map = Object.keys(payload[key]).reduce((oo, k) => {
|
|
69
|
-
|
|
73
|
+
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
|
|
74
|
+
if (!embeddedProp && !allowedOperators.includes(k)) {
|
|
70
75
|
throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
|
|
71
76
|
}
|
|
72
|
-
if (
|
|
73
|
-
oo[
|
|
77
|
+
if (embeddedProp) {
|
|
78
|
+
oo[embeddedProp.name] = payload[key][k];
|
|
74
79
|
}
|
|
75
80
|
else if (typeof payload[key][k] === 'object') {
|
|
76
81
|
oo[k] = JSON.stringify(payload[key][k]);
|
|
@@ -261,7 +261,7 @@ export class NativeQueryBuilder {
|
|
|
261
261
|
}
|
|
262
262
|
if (this.options.where?.sql.trim()) {
|
|
263
263
|
this.parts.push(`where ${this.options.where.sql}`);
|
|
264
|
-
this.params.
|
|
264
|
+
this.options.where.params.forEach(p => this.params.push(p));
|
|
265
265
|
}
|
|
266
266
|
if (this.options.groupBy) {
|
|
267
267
|
const fields = this.options.groupBy.map(field => this.quote(field));
|
|
@@ -6,7 +6,8 @@ import { JoinType, QueryType } from './enums.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export class ObjectCriteriaNode extends CriteriaNode {
|
|
8
8
|
process(qb, options) {
|
|
9
|
-
const
|
|
9
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
10
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
10
11
|
const ownerAlias = options?.alias || qb.alias;
|
|
11
12
|
const keys = Object.keys(this.payload);
|
|
12
13
|
let alias = options?.alias;
|
|
@@ -55,7 +56,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
55
56
|
}
|
|
56
57
|
return { $and };
|
|
57
58
|
}
|
|
58
|
-
alias = this.autoJoin(qb, ownerAlias);
|
|
59
|
+
alias = this.autoJoin(qb, ownerAlias, options);
|
|
59
60
|
}
|
|
60
61
|
return keys.reduce((o, field) => {
|
|
61
62
|
const childNode = this.payload[field];
|
|
@@ -66,13 +67,14 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
66
67
|
const virtual = childNode.prop?.persist === false && !childNode.prop?.formula;
|
|
67
68
|
// if key is missing, we are inside group operator and we need to prefix with alias
|
|
68
69
|
const primaryKey = this.key && this.metadata.find(this.entityName)?.primaryKeys.includes(field);
|
|
70
|
+
const isToOne = childNode.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(childNode.prop.kind);
|
|
69
71
|
if (childNode.shouldInline(payload)) {
|
|
70
|
-
const childAlias = qb.getAliasForJoinPath(childNode.getPath(), options);
|
|
72
|
+
const childAlias = qb.getAliasForJoinPath(childNode.getPath(), { preferNoBranch: isToOne, ...options });
|
|
71
73
|
const a = qb.helper.isTableNameAliasRequired(qb.type) ? alias : undefined;
|
|
72
74
|
this.inlineChildPayload(o, payload, field, a, childAlias);
|
|
73
75
|
}
|
|
74
76
|
else if (childNode.shouldRename(payload)) {
|
|
75
|
-
this.inlineCondition(childNode.renameFieldToPK(qb), o, payload);
|
|
77
|
+
this.inlineCondition(childNode.renameFieldToPK(qb, alias), o, payload);
|
|
76
78
|
}
|
|
77
79
|
else if (isRawField) {
|
|
78
80
|
const rawField = RawQueryFragment.getKnownFragment(field);
|
|
@@ -193,23 +195,44 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
193
195
|
});
|
|
194
196
|
return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
|
|
195
197
|
}
|
|
196
|
-
autoJoin(qb, alias) {
|
|
198
|
+
autoJoin(qb, alias, options) {
|
|
197
199
|
const nestedAlias = qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
198
200
|
const customExpression = RawQueryFragment.isKnownFragment(this.key);
|
|
199
201
|
const scalar = Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || customExpression;
|
|
200
202
|
const operator = Utils.isPlainObject(this.payload) && Object.keys(this.payload).every(k => Utils.isOperator(k, false));
|
|
201
203
|
const field = `${alias}.${this.prop.name}`;
|
|
202
204
|
const method = qb.hasFlag(QueryFlag.INFER_POPULATE) ? 'joinAndSelect' : 'join';
|
|
205
|
+
const path = this.getPath();
|
|
203
206
|
if (this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator)) {
|
|
204
|
-
qb.join(field, nestedAlias, undefined, JoinType.pivotJoin,
|
|
207
|
+
qb.join(field, nestedAlias, undefined, JoinType.pivotJoin, path);
|
|
205
208
|
}
|
|
206
209
|
else {
|
|
207
210
|
const prev = qb._fields?.slice();
|
|
208
|
-
|
|
211
|
+
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
212
|
+
const joinType = toOneProperty && !this.prop.nullable
|
|
213
|
+
? JoinType.innerJoin
|
|
214
|
+
: JoinType.leftJoin;
|
|
215
|
+
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
216
|
+
// if the property is nullable, we need to use left join, so we mimic the inner join behaviour
|
|
217
|
+
// with an exclusive condition on the join columns:
|
|
218
|
+
// - if the owning column is null, the row is missing, we don't apply the filter
|
|
219
|
+
// - if the target column is not null, the row is matched, we apply the filter
|
|
220
|
+
if (toOneProperty && this.prop.nullable && options?.filter) {
|
|
221
|
+
const key = this.prop.owner ? this.prop.name : this.prop.referencedPKs;
|
|
222
|
+
qb.andWhere({
|
|
223
|
+
$or: [
|
|
224
|
+
{ [alias + '.' + key]: null },
|
|
225
|
+
{ [nestedAlias + '.' + Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
}
|
|
209
229
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
210
230
|
qb._fields = prev;
|
|
211
231
|
}
|
|
212
232
|
}
|
|
233
|
+
if (!options || options.type !== 'orderBy') {
|
|
234
|
+
qb.scheduleFilterCheck(path);
|
|
235
|
+
}
|
|
213
236
|
return nestedAlias;
|
|
214
237
|
}
|
|
215
238
|
isPrefixed(field) {
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityManager, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
|
|
3
3
|
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
5
5
|
import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
@@ -141,6 +141,15 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
141
141
|
* Apply filters to the QB where condition.
|
|
142
142
|
*/
|
|
143
143
|
applyFilters(filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
|
|
144
|
+
private readonly autoJoinedPaths;
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
scheduleFilterCheck(path: string): void;
|
|
149
|
+
/**
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
applyJoinedFilters(em: EntityManager, filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
|
|
144
153
|
withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
|
|
145
154
|
where(cond: QBFilterQuery<Entity>, operator?: keyof typeof GroupOperator): this;
|
|
146
155
|
where(cond: string, params?: any[], operator?: keyof typeof GroupOperator): this;
|
|
@@ -281,6 +290,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
281
290
|
processPopulateHint(): void;
|
|
282
291
|
private processPopulateWhere;
|
|
283
292
|
private mergeOnConditions;
|
|
293
|
+
/**
|
|
294
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
295
|
+
* otherwise the inner join could discard rows of the root table.
|
|
296
|
+
*/
|
|
297
|
+
private processNestedJoins;
|
|
284
298
|
private hasToManyJoins;
|
|
285
299
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
286
300
|
private pruneExtraJoins;
|