@mikro-orm/sql 7.0.0-rc.2 → 7.0.0-rc.3
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.d.ts +18 -12
- package/AbstractSqlDriver.js +187 -38
- package/AbstractSqlPlatform.d.ts +1 -0
- package/AbstractSqlPlatform.js +5 -3
- package/PivotCollectionPersister.js +2 -2
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -5
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -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 +63 -47
- 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 +30 -30
- package/plugin/transformer.js +17 -16
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +5 -1
- package/query/NativeQueryBuilder.d.ts +25 -0
- package/query/NativeQueryBuilder.js +61 -1
- package/query/ObjectCriteriaNode.js +71 -27
- package/query/QueryBuilder.d.ts +151 -48
- package/query/QueryBuilder.js +233 -54
- package/query/QueryBuilderHelper.d.ts +4 -3
- package/query/QueryBuilderHelper.js +47 -17
- package/query/ScalarCriteriaNode.js +14 -7
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.js +21 -15
- package/schema/DatabaseTable.js +114 -54
- package/schema/SchemaComparator.js +56 -32
- package/schema/SchemaHelper.js +28 -8
- package/schema/SqlSchemaGenerator.js +13 -7
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +6 -4
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
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
|
-
"type": "module",
|
|
6
|
-
"exports": {
|
|
7
|
-
"./package.json": "./package.json",
|
|
8
|
-
".": "./index.js"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
|
|
13
|
-
},
|
|
14
5
|
"keywords": [
|
|
15
|
-
"
|
|
6
|
+
"data-mapper",
|
|
7
|
+
"ddd",
|
|
8
|
+
"entity",
|
|
9
|
+
"identity-map",
|
|
10
|
+
"javascript",
|
|
11
|
+
"js",
|
|
12
|
+
"mariadb",
|
|
13
|
+
"mikro-orm",
|
|
16
14
|
"mongo",
|
|
17
15
|
"mongodb",
|
|
18
16
|
"mysql",
|
|
19
|
-
"
|
|
17
|
+
"orm",
|
|
20
18
|
"postgresql",
|
|
21
19
|
"sqlite",
|
|
22
20
|
"sqlite3",
|
|
23
21
|
"ts",
|
|
24
22
|
"typescript",
|
|
25
|
-
"
|
|
26
|
-
"javascript",
|
|
27
|
-
"entity",
|
|
28
|
-
"ddd",
|
|
29
|
-
"mikro-orm",
|
|
30
|
-
"unit-of-work",
|
|
31
|
-
"data-mapper",
|
|
32
|
-
"identity-map"
|
|
23
|
+
"unit-of-work"
|
|
33
24
|
],
|
|
34
|
-
"
|
|
35
|
-
"license": "MIT",
|
|
25
|
+
"homepage": "https://mikro-orm.io",
|
|
36
26
|
"bugs": {
|
|
37
27
|
"url": "https://github.com/mikro-orm/mikro-orm/issues"
|
|
38
28
|
},
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Martin Adámek",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": "./package.json",
|
|
38
|
+
".": "./index.js"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "yarn compile && yarn copy",
|
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
"compile": "yarn run -T tsc -p tsconfig.build.json",
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
|
-
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
51
|
-
},
|
|
52
49
|
"dependencies": {
|
|
53
50
|
"kysely": "0.28.11"
|
|
54
51
|
},
|
|
55
52
|
"devDependencies": {
|
|
56
|
-
"@mikro-orm/core": "^6.6.
|
|
53
|
+
"@mikro-orm/core": "^6.6.8"
|
|
57
54
|
},
|
|
58
55
|
"peerDependencies": {
|
|
59
|
-
"@mikro-orm/core": "7.0.0-rc.
|
|
56
|
+
"@mikro-orm/core": "7.0.0-rc.3"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">= 22.17.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/plugin/transformer.js
CHANGED
|
@@ -79,12 +79,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
const nodeWithHooks = this.options.processOnCreateHooks && entityMeta
|
|
83
|
-
|
|
84
|
-
: node;
|
|
85
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
86
|
-
? this.processInsertValues(nodeWithHooks, entityMeta)
|
|
87
|
-
: nodeWithHooks;
|
|
82
|
+
const nodeWithHooks = this.options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
|
|
83
|
+
const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
88
84
|
// Handle ON CONFLICT clause
|
|
89
85
|
let finalNode = nodeWithConvertedValues;
|
|
90
86
|
if (node.onConflict?.updates && entityMeta) {
|
|
@@ -150,12 +146,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
150
146
|
this.processJoinNode(join, currentContext);
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
|
-
const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta
|
|
154
|
-
|
|
155
|
-
: node;
|
|
156
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
157
|
-
? this.processUpdateValues(nodeWithHooks, entityMeta)
|
|
158
|
-
: nodeWithHooks;
|
|
149
|
+
const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
|
|
150
|
+
const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
159
151
|
return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
|
|
160
152
|
}
|
|
161
153
|
finally {
|
|
@@ -217,7 +209,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
217
209
|
}
|
|
218
210
|
// Transform column names when columnNamingStrategy is 'property'
|
|
219
211
|
// Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
|
|
220
|
-
if (this.options.columnNamingStrategy === 'property' &&
|
|
212
|
+
if (this.options.columnNamingStrategy === 'property' &&
|
|
213
|
+
parent &&
|
|
214
|
+
(ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
|
|
221
215
|
const ownerMeta = this.findOwnerEntityInContext();
|
|
222
216
|
if (ownerMeta) {
|
|
223
217
|
const prop = ownerMeta.properties[node.name];
|
|
@@ -480,7 +474,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
480
474
|
}
|
|
481
475
|
}
|
|
482
476
|
if (prop.customType && !isRaw(value)) {
|
|
483
|
-
return prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
477
|
+
return prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
478
|
+
fromQuery: true,
|
|
479
|
+
key: prop.name,
|
|
480
|
+
mode: 'query-data',
|
|
481
|
+
});
|
|
484
482
|
}
|
|
485
483
|
if (value instanceof Date) {
|
|
486
484
|
return this.platform.processDateProperty(value);
|
|
@@ -723,7 +721,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
723
721
|
*/
|
|
724
722
|
transformResult(rows, entityMap) {
|
|
725
723
|
// Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
|
|
726
|
-
if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) ||
|
|
724
|
+
if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) ||
|
|
725
|
+
!rows ||
|
|
726
|
+
rows.length === 0) {
|
|
727
727
|
return rows;
|
|
728
728
|
}
|
|
729
729
|
// If no entities found (e.g. raw query without known tables), return rows as is
|
|
@@ -870,7 +870,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
870
870
|
}
|
|
871
871
|
// For non-local timezone, check if value already has timezone info
|
|
872
872
|
// Number (timestamp) doesn't need timezone handling, string needs check
|
|
873
|
-
if (typeof value === 'number' ||
|
|
873
|
+
if (typeof value === 'number' ||
|
|
874
|
+
(typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
|
|
874
875
|
return this.platform.parseDate(value);
|
|
875
876
|
}
|
|
876
877
|
// Append timezone if not present (only for string values)
|
package/query/CriteriaNode.js
CHANGED
|
@@ -53,22 +53,34 @@ export class CriteriaNode {
|
|
|
53
53
|
const type = this.prop ? this.prop.kind : null;
|
|
54
54
|
const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
|
|
55
55
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
56
|
-
const scalar = payload === null ||
|
|
56
|
+
const scalar = payload === null ||
|
|
57
|
+
Utils.isPrimaryKey(payload) ||
|
|
58
|
+
payload instanceof RegExp ||
|
|
59
|
+
payload instanceof Date ||
|
|
60
|
+
rawField;
|
|
57
61
|
const operator = Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
|
|
58
62
|
if (composite) {
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
switch (type) {
|
|
62
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
63
|
-
|
|
64
|
-
case ReferenceKind.
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
case ReferenceKind.MANY_TO_ONE:
|
|
67
|
+
return false;
|
|
68
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
69
|
+
return !this.prop.owner;
|
|
70
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
71
|
+
return scalar || operator;
|
|
72
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
73
|
+
return scalar || operator;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
renameFieldToPK(qb, ownerAlias) {
|
|
70
79
|
const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
71
|
-
if (!joinAlias &&
|
|
80
|
+
if (!joinAlias &&
|
|
81
|
+
this.parent &&
|
|
82
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
|
|
83
|
+
this.prop.owner) {
|
|
72
84
|
const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
|
|
73
85
|
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
74
86
|
}
|
|
@@ -84,7 +96,9 @@ export class CriteriaNode {
|
|
|
84
96
|
const parentPath = opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
|
|
85
97
|
const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
|
|
86
98
|
// ignore group operators to allow easier mapping (e.g. for orderBy)
|
|
87
|
-
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
99
|
+
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
100
|
+
? '.' + this.key
|
|
101
|
+
: '';
|
|
88
102
|
const ret = parentPath + index + key;
|
|
89
103
|
if (this.isPivotJoin()) {
|
|
90
104
|
// distinguish pivot table join from target entity join
|
|
@@ -97,7 +111,11 @@ export class CriteriaNode {
|
|
|
97
111
|
return false;
|
|
98
112
|
}
|
|
99
113
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
100
|
-
const scalar = this.payload === null ||
|
|
114
|
+
const scalar = this.payload === null ||
|
|
115
|
+
Utils.isPrimaryKey(this.payload) ||
|
|
116
|
+
this.payload instanceof RegExp ||
|
|
117
|
+
this.payload instanceof Date ||
|
|
118
|
+
rawField;
|
|
101
119
|
const operator = Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
|
|
102
120
|
return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
|
|
103
121
|
}
|
|
@@ -116,7 +134,7 @@ export class CriteriaNode {
|
|
|
116
134
|
const o = {};
|
|
117
135
|
['entityName', 'key', 'index', 'payload']
|
|
118
136
|
.filter(k => this[k] !== undefined)
|
|
119
|
-
.forEach(k => o[k] = this[k]);
|
|
137
|
+
.forEach(k => (o[k] = this[k]));
|
|
120
138
|
return `${this.constructor.name} ${inspect(o)}`;
|
|
121
139
|
}
|
|
122
140
|
}
|
|
@@ -8,7 +8,11 @@ import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
|
|
|
8
8
|
export class CriteriaNodeFactory {
|
|
9
9
|
static createNode(metadata, entityName, payload, parent, key, validate = true) {
|
|
10
10
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
|
|
11
|
-
const scalar = Utils.isPrimaryKey(payload) ||
|
|
11
|
+
const scalar = Utils.isPrimaryKey(payload) ||
|
|
12
|
+
isRaw(payload) ||
|
|
13
|
+
payload instanceof RegExp ||
|
|
14
|
+
payload instanceof Date ||
|
|
15
|
+
rawField;
|
|
12
16
|
if (Array.isArray(payload) && !scalar) {
|
|
13
17
|
return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
|
|
14
18
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type Dictionary, LockMode, type QueryFlag, RawQueryFragment, type Subquery } from '@mikro-orm/core';
|
|
2
2
|
import { QueryType } from './enums.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
|
+
export interface CteOptions {
|
|
5
|
+
columns?: string[];
|
|
6
|
+
/** PostgreSQL: MATERIALIZED / NOT MATERIALIZED */
|
|
7
|
+
materialized?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface CteClause extends CteOptions {
|
|
10
|
+
name: string;
|
|
11
|
+
sql: string;
|
|
12
|
+
params: unknown[];
|
|
13
|
+
recursive?: boolean;
|
|
14
|
+
}
|
|
4
15
|
interface Options {
|
|
5
16
|
tableName?: string | RawQueryFragment;
|
|
6
17
|
indexHint?: string;
|
|
@@ -32,6 +43,7 @@ interface Options {
|
|
|
32
43
|
hintComment?: string[];
|
|
33
44
|
flags?: Set<QueryFlag>;
|
|
34
45
|
wrap?: [prefix: string, suffix: string];
|
|
46
|
+
ctes?: CteClause[];
|
|
35
47
|
}
|
|
36
48
|
interface TableOptions {
|
|
37
49
|
schema?: string;
|
|
@@ -65,6 +77,17 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
65
77
|
groupBy(groupBy: (string | RawQueryFragment)[]): this;
|
|
66
78
|
join(sql: string, params: unknown[]): this;
|
|
67
79
|
orderBy(orderBy: string): this;
|
|
80
|
+
/**
|
|
81
|
+
* The sub-query is compiled eagerly at call time — later mutations to the
|
|
82
|
+
* sub-query builder will not be reflected in this CTE.
|
|
83
|
+
*/
|
|
84
|
+
with(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
85
|
+
/**
|
|
86
|
+
* Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
|
|
87
|
+
* The sub-query is compiled eagerly — later mutations will not be reflected.
|
|
88
|
+
*/
|
|
89
|
+
withRecursive(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
|
|
90
|
+
private addCte;
|
|
68
91
|
toString(): string;
|
|
69
92
|
compile(): {
|
|
70
93
|
sql: string;
|
|
@@ -103,6 +126,8 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
103
126
|
protected compileDelete(): void;
|
|
104
127
|
protected compileTruncate(): void;
|
|
105
128
|
protected addHintComment(): void;
|
|
129
|
+
protected compileCtes(): void;
|
|
130
|
+
protected getCteKeyword(hasRecursive: boolean): string;
|
|
106
131
|
protected getTableName(): string;
|
|
107
132
|
protected quote(id: string | RawQueryFragment | NativeQueryBuilder): string;
|
|
108
133
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LockMode, raw, RawQueryFragment, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { LockMode, raw, RawQueryFragment, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { QueryType } from './enums.js';
|
|
3
3
|
/** @internal */
|
|
4
4
|
export class NativeQueryBuilder {
|
|
@@ -59,6 +59,36 @@ export class NativeQueryBuilder {
|
|
|
59
59
|
this.options.orderBy = orderBy;
|
|
60
60
|
return this;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The sub-query is compiled eagerly at call time — later mutations to the
|
|
64
|
+
* sub-query builder will not be reflected in this CTE.
|
|
65
|
+
*/
|
|
66
|
+
with(name, query, options) {
|
|
67
|
+
return this.addCte(name, query, options);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
|
|
71
|
+
* The sub-query is compiled eagerly — later mutations will not be reflected.
|
|
72
|
+
*/
|
|
73
|
+
withRecursive(name, query, options) {
|
|
74
|
+
return this.addCte(name, query, options, true);
|
|
75
|
+
}
|
|
76
|
+
addCte(name, query, options, recursive) {
|
|
77
|
+
this.options.ctes ??= [];
|
|
78
|
+
if (this.options.ctes.some(cte => cte.name === name)) {
|
|
79
|
+
throw new Error(`CTE with name '${name}' already exists`);
|
|
80
|
+
}
|
|
81
|
+
const { sql, params } = query instanceof NativeQueryBuilder ? query.compile() : { sql: query.sql, params: [...query.params] };
|
|
82
|
+
this.options.ctes.push({
|
|
83
|
+
name,
|
|
84
|
+
sql,
|
|
85
|
+
params,
|
|
86
|
+
recursive,
|
|
87
|
+
columns: options?.columns,
|
|
88
|
+
materialized: options?.materialized,
|
|
89
|
+
});
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
62
92
|
toString() {
|
|
63
93
|
const { sql, params } = this.compile();
|
|
64
94
|
return this.platform.formatQuery(sql, params);
|
|
@@ -72,6 +102,7 @@ export class NativeQueryBuilder {
|
|
|
72
102
|
if (this.options.comment) {
|
|
73
103
|
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
|
|
74
104
|
}
|
|
105
|
+
this.compileCtes();
|
|
75
106
|
switch (this.type) {
|
|
76
107
|
case QueryType.SELECT:
|
|
77
108
|
case QueryType.COUNT:
|
|
@@ -387,6 +418,35 @@ export class NativeQueryBuilder {
|
|
|
387
418
|
this.parts.push(`/*+ ${this.options.hintComment.join(' ')} */`);
|
|
388
419
|
}
|
|
389
420
|
}
|
|
421
|
+
compileCtes() {
|
|
422
|
+
const ctes = this.options.ctes;
|
|
423
|
+
if (!ctes || ctes.length === 0) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const hasRecursive = ctes.some(cte => cte.recursive);
|
|
427
|
+
const keyword = this.getCteKeyword(hasRecursive);
|
|
428
|
+
const cteParts = [];
|
|
429
|
+
for (const cte of ctes) {
|
|
430
|
+
let part = this.quote(cte.name);
|
|
431
|
+
if (cte.columns?.length) {
|
|
432
|
+
part += ` (${cte.columns.map(c => this.quote(c)).join(', ')})`;
|
|
433
|
+
}
|
|
434
|
+
part += ' as';
|
|
435
|
+
if (cte.materialized === true) {
|
|
436
|
+
part += ' materialized';
|
|
437
|
+
}
|
|
438
|
+
else if (cte.materialized === false) {
|
|
439
|
+
part += ' not materialized';
|
|
440
|
+
}
|
|
441
|
+
part += ` (${cte.sql})`;
|
|
442
|
+
this.params.push(...cte.params);
|
|
443
|
+
cteParts.push(part);
|
|
444
|
+
}
|
|
445
|
+
this.parts.push(`${keyword} ${cteParts.join(', ')}`);
|
|
446
|
+
}
|
|
447
|
+
getCteKeyword(hasRecursive) {
|
|
448
|
+
return hasRecursive ? 'with recursive' : 'with';
|
|
449
|
+
}
|
|
390
450
|
getTableName() {
|
|
391
451
|
if (!this.options.tableName) {
|
|
392
452
|
throw new Error('No table name provided');
|
|
@@ -7,7 +7,8 @@ const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size'];
|
|
|
7
7
|
*/
|
|
8
8
|
export class ObjectCriteriaNode 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
|
const ownerAlias = options?.alias || qb.alias;
|
|
13
14
|
const keys = Utils.getObjectQueryKeys(this.payload);
|
|
@@ -20,13 +21,15 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
20
21
|
if (![ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(this.prop.kind)) {
|
|
21
22
|
// ignore collection operators when used on a non-relational property - this can happen when they get into
|
|
22
23
|
// populateWhere via `infer` on m:n properties with select-in strategy
|
|
23
|
-
if (this.parent?.parent) {
|
|
24
|
+
if (this.parent?.parent) {
|
|
25
|
+
// we validate only usage on top level
|
|
24
26
|
return {};
|
|
25
27
|
}
|
|
26
28
|
throw new Error(`Collection operators can be used only inside a collection property context, but it was used for ${this.getPath()}.`);
|
|
27
29
|
}
|
|
28
30
|
const $and = [];
|
|
29
|
-
const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
|
|
31
|
+
const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
|
|
32
|
+
(this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
|
|
30
33
|
const parentMeta = this.metadata.find(this.parent.entityName);
|
|
31
34
|
const primaryKeys = parentMeta.primaryKeys.map(pk => {
|
|
32
35
|
return [QueryType.SELECT, QueryType.COUNT].includes(qb.type) ? `${knownKey ? alias : ownerAlias}.${pk}` : pk;
|
|
@@ -48,7 +51,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
48
51
|
const pks = this.prop.referencedColumnNames;
|
|
49
52
|
const countExpr = raw(`count(${pks.map(() => '??').join(', ')})`, pks.map(pk => `${joinAlias}.${pk}`));
|
|
50
53
|
sub.groupBy(parentMeta.primaryKeys);
|
|
51
|
-
sub.having({
|
|
54
|
+
sub.having({
|
|
55
|
+
$and: Object.keys(sizeCondition).map(op => ({ [countExpr]: { [op]: sizeCondition[op] } })),
|
|
56
|
+
});
|
|
52
57
|
}
|
|
53
58
|
else if (key === '$every') {
|
|
54
59
|
sub.where({ $not: { [this.key]: payload } });
|
|
@@ -111,7 +116,11 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
111
116
|
// use '??' placeholder to properly quote the identifier
|
|
112
117
|
o[raw('??', [field])] = payload;
|
|
113
118
|
}
|
|
114
|
-
else if (primaryKey ||
|
|
119
|
+
else if (primaryKey ||
|
|
120
|
+
virtual ||
|
|
121
|
+
operator ||
|
|
122
|
+
field.includes('.') ||
|
|
123
|
+
![QueryType.SELECT, QueryType.COUNT].includes(qb.type)) {
|
|
115
124
|
this.inlineCondition(field.replaceAll(ALIAS_REPLACEMENT, alias), o, payload);
|
|
116
125
|
}
|
|
117
126
|
else {
|
|
@@ -121,9 +130,10 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
121
130
|
}, {});
|
|
122
131
|
}
|
|
123
132
|
isStrict() {
|
|
124
|
-
return this.strict ||
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
return (this.strict ||
|
|
134
|
+
Utils.getObjectQueryKeys(this.payload).some(key => {
|
|
135
|
+
return this.payload[key].isStrict();
|
|
136
|
+
}));
|
|
127
137
|
}
|
|
128
138
|
unwrap() {
|
|
129
139
|
return Utils.getObjectQueryKeys(this.payload).reduce((o, field) => {
|
|
@@ -148,8 +158,18 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
148
158
|
}
|
|
149
159
|
shouldInline(payload) {
|
|
150
160
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
151
|
-
const scalar = Utils.isPrimaryKey(payload) ||
|
|
152
|
-
|
|
161
|
+
const scalar = Utils.isPrimaryKey(payload) ||
|
|
162
|
+
payload instanceof RegExp ||
|
|
163
|
+
payload instanceof Date ||
|
|
164
|
+
rawField;
|
|
165
|
+
const operator = Utils.isObject(payload) &&
|
|
166
|
+
Utils.getObjectQueryKeys(payload).every(k => {
|
|
167
|
+
if (k === '$not' && Utils.isPlainObject(payload[k])) {
|
|
168
|
+
// $not wrapping non-operator conditions (entity props) should be inlined
|
|
169
|
+
return Utils.getObjectQueryKeys(payload[k]).every(ik => Utils.isOperator(ik, false));
|
|
170
|
+
}
|
|
171
|
+
return Utils.isOperator(k, false);
|
|
172
|
+
});
|
|
153
173
|
return !!this.prop && this.prop.kind !== ReferenceKind.SCALAR && !scalar && !operator;
|
|
154
174
|
}
|
|
155
175
|
getChildKey(k, prop, childAlias, alias) {
|
|
@@ -161,7 +181,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
161
181
|
inlineArrayChildPayload(obj, payload, k, prop, childAlias, alias) {
|
|
162
182
|
const key = this.getChildKey(k, prop, childAlias);
|
|
163
183
|
const value = payload.map((child) => Utils.getObjectQueryKeys(child).reduce((inner, childKey) => {
|
|
164
|
-
const key =
|
|
184
|
+
const key = RawQueryFragment.isKnownFragmentSymbol(childKey) || this.isPrefixed(childKey) || Utils.isOperator(childKey)
|
|
185
|
+
? childKey
|
|
186
|
+
: this.aliased(childKey, childAlias);
|
|
165
187
|
inner[key] = child[childKey];
|
|
166
188
|
return inner;
|
|
167
189
|
}, {}));
|
|
@@ -173,6 +195,12 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
173
195
|
if (RawQueryFragment.isKnownFragmentSymbol(k)) {
|
|
174
196
|
o[k] = payload[k];
|
|
175
197
|
}
|
|
198
|
+
else if (k === '$not' &&
|
|
199
|
+
Utils.isPlainObject(payload[k]) &&
|
|
200
|
+
Utils.getObjectQueryKeys(payload[k]).some(ik => !Utils.isOperator(ik, false))) {
|
|
201
|
+
// $not wraps entity conditions (from auto-join), inline at current level
|
|
202
|
+
this.inlineCondition(k, o, payload[k]);
|
|
203
|
+
}
|
|
176
204
|
else if (Utils.isOperator(k, false)) {
|
|
177
205
|
const tmp = payload[k];
|
|
178
206
|
delete payload[k];
|
|
@@ -218,24 +246,42 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
218
246
|
}
|
|
219
247
|
const meta = this.metadata.find(this.entityName);
|
|
220
248
|
const embeddable = this.prop.kind === ReferenceKind.EMBEDDED;
|
|
221
|
-
const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
|
|
250
|
+
(this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
|
|
251
|
+
const operatorKeys = knownKey &&
|
|
252
|
+
keys.every(key => {
|
|
253
|
+
if (key === '$not') {
|
|
254
|
+
// $not wraps conditions like $and/$or, check if it wraps entity property conditions (needs auto-join)
|
|
255
|
+
// vs simple operator conditions on the FK (doesn't need auto-join)
|
|
256
|
+
const childPayload = this.payload[key].payload;
|
|
257
|
+
if (Utils.isPlainObject(childPayload)) {
|
|
258
|
+
return Utils.getObjectQueryKeys(childPayload).every(k => Utils.isOperator(k, false));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return Utils.isOperator(key, false);
|
|
262
|
+
});
|
|
263
|
+
const primaryKeys = knownKey &&
|
|
264
|
+
keys.every(key => {
|
|
265
|
+
if (typeof key !== 'string' || !meta.primaryKeys.includes(key)) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (!Utils.isPlainObject(this.payload[key].payload) ||
|
|
269
|
+
![ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[key].kind)) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
return Utils.getObjectQueryKeys(this.payload[key].payload).every(k => typeof k === 'string' && meta.properties[key].targetMeta.primaryKeys.includes(k));
|
|
273
|
+
});
|
|
232
274
|
return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
|
|
233
275
|
}
|
|
234
276
|
autoJoin(qb, alias, options) {
|
|
235
277
|
const nestedAlias = qb.getNextAlias(this.prop?.pivotEntity ?? this.entityName);
|
|
236
278
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
237
|
-
const scalar = Utils.isPrimaryKey(this.payload) ||
|
|
238
|
-
|
|
279
|
+
const scalar = Utils.isPrimaryKey(this.payload) ||
|
|
280
|
+
this.payload instanceof RegExp ||
|
|
281
|
+
this.payload instanceof Date ||
|
|
282
|
+
rawField;
|
|
283
|
+
const operator = Utils.isPlainObject(this.payload) &&
|
|
284
|
+
Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
|
|
239
285
|
const field = `${alias}.${this.prop.name}`;
|
|
240
286
|
const method = qb.hasFlag(QueryFlag.INFER_POPULATE) ? 'joinAndSelect' : 'join';
|
|
241
287
|
const path = this.getPath();
|
|
@@ -245,9 +291,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
245
291
|
else {
|
|
246
292
|
const prev = qb._fields?.slice();
|
|
247
293
|
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
248
|
-
const joinType = toOneProperty && !this.prop.nullable
|
|
249
|
-
? JoinType.innerJoin
|
|
250
|
-
: JoinType.leftJoin;
|
|
294
|
+
const joinType = toOneProperty && !this.prop.nullable ? JoinType.innerJoin : JoinType.leftJoin;
|
|
251
295
|
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
252
296
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
253
297
|
qb._fields = prev;
|