@mikro-orm/sql 7.0.0-dev.99 → 7.0.0-rc.0
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 +2 -4
- package/AbstractSqlConnection.js +3 -7
- package/AbstractSqlDriver.d.ts +82 -23
- package/AbstractSqlDriver.js +584 -184
- package/AbstractSqlPlatform.d.ts +3 -4
- package/AbstractSqlPlatform.js +0 -4
- package/PivotCollectionPersister.d.ts +5 -0
- package/PivotCollectionPersister.js +30 -12
- package/SqlEntityManager.d.ts +2 -2
- package/dialects/mysql/{MySqlPlatform.d.ts → BaseMySqlPlatform.d.ts} +3 -2
- package/dialects/mysql/{MySqlPlatform.js → BaseMySqlPlatform.js} +5 -1
- package/dialects/mysql/MySqlSchemaHelper.d.ts +12 -1
- package/dialects/mysql/MySqlSchemaHelper.js +97 -6
- package/dialects/mysql/index.d.ts +1 -2
- package/dialects/mysql/index.js +1 -2
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +106 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -0
- package/dialects/postgresql/FullTextType.d.ts +14 -0
- package/dialects/postgresql/FullTextType.js +59 -0
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +8 -0
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +47 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +90 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +732 -0
- package/dialects/postgresql/index.d.ts +3 -0
- package/dialects/postgresql/index.js +3 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -0
- package/dialects/sqlite/BaseSqliteConnection.js +13 -0
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +6 -0
- package/dialects/sqlite/BaseSqlitePlatform.js +12 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +25 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +145 -19
- package/dialects/sqlite/index.d.ts +0 -1
- package/dialects/sqlite/index.js +0 -1
- package/package.json +5 -6
- package/plugin/transformer.d.ts +1 -1
- package/plugin/transformer.js +1 -1
- package/query/CriteriaNode.d.ts +9 -5
- package/query/CriteriaNode.js +16 -15
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +33 -31
- package/query/NativeQueryBuilder.d.ts +3 -2
- package/query/NativeQueryBuilder.js +1 -2
- package/query/ObjectCriteriaNode.js +50 -35
- package/query/QueryBuilder.d.ts +548 -79
- package/query/QueryBuilder.js +537 -159
- package/query/QueryBuilderHelper.d.ts +22 -14
- package/query/QueryBuilderHelper.js +158 -69
- package/query/ScalarCriteriaNode.js +2 -2
- package/query/raw.d.ts +11 -3
- package/query/raw.js +1 -2
- package/schema/DatabaseSchema.d.ts +15 -2
- package/schema/DatabaseSchema.js +143 -15
- package/schema/DatabaseTable.d.ts +12 -0
- package/schema/DatabaseTable.js +91 -31
- package/schema/SchemaComparator.d.ts +8 -0
- package/schema/SchemaComparator.js +126 -3
- package/schema/SchemaHelper.d.ts +26 -3
- package/schema/SchemaHelper.js +98 -11
- package/schema/SqlSchemaGenerator.d.ts +10 -0
- package/schema/SqlSchemaGenerator.js +137 -9
- package/tsconfig.build.tsbuildinfo +1 -0
- package/typings.d.ts +74 -36
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +0 -1
- package/dialects/postgresql/PostgreSqlTableCompiler.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, LockMode, type
|
|
1
|
+
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FlatQueryOrderMap, type FormulaTable, LockMode, type QueryOrderMap, Raw, type RawQueryFragmentSymbol } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { InternalField, JoinOptions } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
5
5
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
6
6
|
/**
|
|
@@ -12,11 +12,17 @@ export declare class QueryBuilderHelper {
|
|
|
12
12
|
private readonly aliasMap;
|
|
13
13
|
private readonly subQueries;
|
|
14
14
|
private readonly driver;
|
|
15
|
+
private readonly tptAliasMap;
|
|
15
16
|
private readonly platform;
|
|
16
17
|
private readonly metadata;
|
|
17
|
-
constructor(entityName:
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
constructor(entityName: EntityName, alias: string, aliasMap: Dictionary<Alias<any>>, subQueries: Dictionary<string>, driver: AbstractSqlDriver, tptAliasMap?: Dictionary<string>);
|
|
19
|
+
/**
|
|
20
|
+
* For TPT inheritance, finds the correct alias for a property based on which entity owns it.
|
|
21
|
+
* Returns the main alias if not a TPT property or if the property belongs to the main entity.
|
|
22
|
+
*/
|
|
23
|
+
getTPTAliasForProperty(propName: string, defaultAlias: string): string;
|
|
24
|
+
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType): string;
|
|
25
|
+
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType, value?: any, alias?: string | null, schema?: string): string;
|
|
20
26
|
processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
|
|
21
27
|
joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
|
|
22
28
|
joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
|
|
@@ -26,9 +32,9 @@ export declare class QueryBuilderHelper {
|
|
|
26
32
|
sql: string;
|
|
27
33
|
params: unknown[];
|
|
28
34
|
};
|
|
29
|
-
mapJoinColumns(type: QueryType, join: JoinOptions): (string |
|
|
35
|
+
mapJoinColumns(type: QueryType, join: JoinOptions): (string | Raw)[];
|
|
30
36
|
isOneToOneInverse(field: string, meta?: EntityMetadata): boolean;
|
|
31
|
-
getTableName(entityName:
|
|
37
|
+
getTableName(entityName: EntityName): string;
|
|
32
38
|
/**
|
|
33
39
|
* Checks whether the RE can be rewritten to simple LIKE query
|
|
34
40
|
*/
|
|
@@ -45,9 +51,10 @@ export declare class QueryBuilderHelper {
|
|
|
45
51
|
private processObjectSubCondition;
|
|
46
52
|
private getValueReplacement;
|
|
47
53
|
private getOperatorReplacement;
|
|
54
|
+
validateQueryOrder<T>(orderBy: QueryOrderMap<T>): void;
|
|
48
55
|
getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string[];
|
|
49
56
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
|
|
50
|
-
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?:
|
|
57
|
+
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: InternalField<any>[]): void;
|
|
51
58
|
splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
|
|
52
59
|
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
|
|
53
60
|
updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
|
|
@@ -57,17 +64,18 @@ export declare class QueryBuilderHelper {
|
|
|
57
64
|
private fieldName;
|
|
58
65
|
getProperty(field: string, alias?: string): EntityProperty | undefined;
|
|
59
66
|
isTableNameAliasRequired(type: QueryType): boolean;
|
|
60
|
-
processOnConflictCondition(cond:
|
|
67
|
+
processOnConflictCondition(cond: FilterQuery<any>, schema?: string): FilterQuery<any>;
|
|
68
|
+
createFormulaTable(alias: string, meta: EntityMetadata, schema?: string): FormulaTable;
|
|
61
69
|
}
|
|
62
70
|
export interface Alias<T> {
|
|
63
71
|
aliasName: string;
|
|
64
|
-
entityName:
|
|
65
|
-
|
|
72
|
+
entityName: EntityName<T>;
|
|
73
|
+
meta: EntityMetadata<T>;
|
|
66
74
|
subQuery?: NativeQueryBuilder;
|
|
67
75
|
}
|
|
68
76
|
export interface OnConflictClause<T> {
|
|
69
|
-
fields: string[] |
|
|
77
|
+
fields: string[] | Raw;
|
|
70
78
|
ignore?: boolean;
|
|
71
|
-
merge?: EntityData<T> |
|
|
72
|
-
where?:
|
|
79
|
+
merge?: EntityData<T> | InternalField<T>[];
|
|
80
|
+
where?: FilterQuery<T>;
|
|
73
81
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw,
|
|
1
|
+
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, inspect, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, Raw, QueryHelper, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
4
4
|
/**
|
|
@@ -10,21 +10,51 @@ export class QueryBuilderHelper {
|
|
|
10
10
|
aliasMap;
|
|
11
11
|
subQueries;
|
|
12
12
|
driver;
|
|
13
|
+
tptAliasMap;
|
|
13
14
|
platform;
|
|
14
15
|
metadata;
|
|
15
|
-
constructor(entityName, alias, aliasMap, subQueries, driver) {
|
|
16
|
+
constructor(entityName, alias, aliasMap, subQueries, driver, tptAliasMap = {}) {
|
|
16
17
|
this.entityName = entityName;
|
|
17
18
|
this.alias = alias;
|
|
18
19
|
this.aliasMap = aliasMap;
|
|
19
20
|
this.subQueries = subQueries;
|
|
20
21
|
this.driver = driver;
|
|
22
|
+
this.tptAliasMap = tptAliasMap;
|
|
21
23
|
this.platform = this.driver.getPlatform();
|
|
22
24
|
this.metadata = this.driver.getMetadata();
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
/**
|
|
27
|
+
* For TPT inheritance, finds the correct alias for a property based on which entity owns it.
|
|
28
|
+
* Returns the main alias if not a TPT property or if the property belongs to the main entity.
|
|
29
|
+
*/
|
|
30
|
+
getTPTAliasForProperty(propName, defaultAlias) {
|
|
31
|
+
const meta = this.aliasMap[defaultAlias]?.meta ?? this.metadata.get(this.entityName);
|
|
32
|
+
if (meta?.inheritanceType !== 'tpt' || !meta.tptParent) {
|
|
33
|
+
return defaultAlias;
|
|
34
|
+
}
|
|
35
|
+
// Check if property is in the main entity's ownProps
|
|
36
|
+
if (meta.ownProps?.some(p => p.name === propName || p.fieldNames?.includes(propName))) {
|
|
37
|
+
return defaultAlias;
|
|
38
|
+
}
|
|
39
|
+
// Walk up the TPT hierarchy to find which parent owns this property
|
|
40
|
+
let parentMeta = meta.tptParent;
|
|
41
|
+
while (parentMeta) {
|
|
42
|
+
const parentAlias = this.tptAliasMap[parentMeta.className];
|
|
43
|
+
if (parentAlias && parentMeta.ownProps?.some(p => p.name === propName || p.fieldNames?.includes(propName))) {
|
|
44
|
+
return parentAlias;
|
|
45
|
+
}
|
|
46
|
+
parentMeta = parentMeta.tptParent;
|
|
47
|
+
}
|
|
48
|
+
// Property not found in hierarchy, return default alias
|
|
49
|
+
return defaultAlias;
|
|
50
|
+
}
|
|
51
|
+
mapper(field, type = QueryType.SELECT, value, alias, schema) {
|
|
25
52
|
if (isRaw(field)) {
|
|
26
53
|
return raw(field.sql, field.params);
|
|
27
54
|
}
|
|
55
|
+
if (Raw.isKnownFragmentSymbol(field)) {
|
|
56
|
+
return Raw.getKnownFragment(field);
|
|
57
|
+
}
|
|
28
58
|
/* v8 ignore next */
|
|
29
59
|
if (typeof field !== 'string') {
|
|
30
60
|
return field;
|
|
@@ -60,20 +90,17 @@ export class QueryBuilderHelper {
|
|
|
60
90
|
}
|
|
61
91
|
return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
|
|
62
92
|
}
|
|
63
|
-
const rawField = RawQueryFragment.getKnownFragment(field);
|
|
64
|
-
if (rawField) {
|
|
65
|
-
return rawField;
|
|
66
|
-
}
|
|
67
|
-
const aliasPrefix = isTableNameAliasRequired ? this.alias + '.' : '';
|
|
68
93
|
const [a, f] = this.splitField(field);
|
|
69
94
|
const prop = this.getProperty(f, a);
|
|
95
|
+
// For TPT inheritance, resolve the correct alias for this property
|
|
96
|
+
// Only apply TPT resolution when `a` is an actual table alias (in aliasMap),
|
|
97
|
+
// not when it's an embedded property name like 'profile1.identity.links'
|
|
98
|
+
const isTableAlias = !!this.aliasMap[a];
|
|
99
|
+
const baseAlias = isTableAlias ? a : this.alias;
|
|
100
|
+
const resolvedAlias = isTableAlias ? this.getTPTAliasForProperty(prop?.name ?? f, a) : this.alias;
|
|
101
|
+
const aliasPrefix = isTableNameAliasRequired ? resolvedAlias + '.' : '';
|
|
70
102
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
71
103
|
const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
|
|
72
|
-
let ret = field;
|
|
73
|
-
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
74
|
-
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
75
|
-
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
76
|
-
}
|
|
77
104
|
if (a === prop?.embedded?.[0]) {
|
|
78
105
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
79
106
|
}
|
|
@@ -85,7 +112,10 @@ export class QueryBuilderHelper {
|
|
|
85
112
|
const alias2 = this.platform.quoteIdentifier(a).toString();
|
|
86
113
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]).toString();
|
|
87
114
|
const as = alias === null ? '' : ` as ${aliased}`;
|
|
88
|
-
|
|
115
|
+
const meta = this.aliasMap[a]?.meta ?? this.metadata.get(this.entityName);
|
|
116
|
+
const table = this.createFormulaTable(alias2, meta, schema);
|
|
117
|
+
const columns = meta.createColumnMappingObject(p => this.getTPTAliasForProperty(p.name, a), alias2);
|
|
118
|
+
let value = this.driver.evaluateFormula(prop.formula, columns, table);
|
|
89
119
|
if (!this.isTableNameAliasRequired(type)) {
|
|
90
120
|
value = value.replaceAll(alias2 + '.', '');
|
|
91
121
|
}
|
|
@@ -107,17 +137,14 @@ export class QueryBuilderHelper {
|
|
|
107
137
|
}
|
|
108
138
|
return raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
|
|
109
139
|
}
|
|
110
|
-
|
|
111
|
-
if (!rawField) {
|
|
112
|
-
ret = this.prefix(field, false, false, fkIdx);
|
|
113
|
-
}
|
|
140
|
+
let ret = this.prefix(field, false, false, fkIdx);
|
|
114
141
|
if (alias) {
|
|
115
142
|
ret += ' as ' + alias;
|
|
116
143
|
}
|
|
117
144
|
if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
|
|
118
145
|
return ret;
|
|
119
146
|
}
|
|
120
|
-
return
|
|
147
|
+
return resolvedAlias + '.' + ret;
|
|
121
148
|
}
|
|
122
149
|
processData(data, convertCustomTypes, multi = false) {
|
|
123
150
|
if (Array.isArray(data)) {
|
|
@@ -133,12 +160,20 @@ export class QueryBuilderHelper {
|
|
|
133
160
|
}
|
|
134
161
|
joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
135
162
|
const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
|
|
136
|
-
const table = this.getTableName(prop.
|
|
163
|
+
const table = this.getTableName(prop.targetMeta.class);
|
|
137
164
|
const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
|
|
138
165
|
const inverseJoinColumns = prop.referencedColumnNames;
|
|
139
166
|
const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
|
|
140
167
|
schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
|
|
141
168
|
cond = Utils.merge(cond, prop.where);
|
|
169
|
+
// For inverse side of polymorphic relations, add discriminator condition
|
|
170
|
+
if (!prop.owner && prop2.polymorphic && prop2.discriminatorColumn && prop2.discriminatorMap) {
|
|
171
|
+
const ownerMeta = this.aliasMap[ownerAlias]?.meta ?? this.metadata.get(this.entityName);
|
|
172
|
+
const discriminatorValue = QueryHelper.findDiscriminatorValue(prop2.discriminatorMap, ownerMeta.class);
|
|
173
|
+
if (discriminatorValue) {
|
|
174
|
+
cond[`${alias}.${prop2.discriminatorColumn}`] = discriminatorValue;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
142
177
|
return {
|
|
143
178
|
prop, type, cond, ownerAlias, alias, table, schema,
|
|
144
179
|
joinColumns, inverseJoinColumns, primaryKeys,
|
|
@@ -147,10 +182,12 @@ export class QueryBuilderHelper {
|
|
|
147
182
|
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
148
183
|
return {
|
|
149
184
|
prop, type, cond, ownerAlias, alias,
|
|
150
|
-
table: this.getTableName(prop.
|
|
185
|
+
table: this.getTableName(prop.targetMeta.class),
|
|
151
186
|
schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
152
187
|
joinColumns: prop.referencedColumnNames,
|
|
153
|
-
|
|
188
|
+
// For polymorphic relations, fieldNames includes the discriminator column which is not
|
|
189
|
+
// part of the join condition - use joinColumns (the FK columns only) instead
|
|
190
|
+
primaryKeys: prop.polymorphic ? prop.joinColumns : prop.fieldNames,
|
|
154
191
|
};
|
|
155
192
|
}
|
|
156
193
|
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
|
|
@@ -172,7 +209,7 @@ export class QueryBuilderHelper {
|
|
|
172
209
|
if (type === JoinType.pivotJoin) {
|
|
173
210
|
return ret;
|
|
174
211
|
}
|
|
175
|
-
const prop2 = prop.owner ?
|
|
212
|
+
const prop2 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
176
213
|
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
|
|
177
214
|
ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
|
|
178
215
|
const tmp = prop2.referencedTableName.split('.');
|
|
@@ -205,8 +242,11 @@ export class QueryBuilderHelper {
|
|
|
205
242
|
join.primaryKeys.forEach((primaryKey, idx) => {
|
|
206
243
|
const right = `${join.alias}.${join.joinColumns[idx]}`;
|
|
207
244
|
if (join.prop.formula) {
|
|
208
|
-
const
|
|
209
|
-
const
|
|
245
|
+
const quotedAlias = this.platform.quoteIdentifier(join.ownerAlias).toString();
|
|
246
|
+
const ownerMeta = this.aliasMap[join.ownerAlias]?.meta ?? this.metadata.get(this.entityName);
|
|
247
|
+
const table = this.createFormulaTable(quotedAlias, ownerMeta, schema);
|
|
248
|
+
const columns = ownerMeta.createColumnMappingObject(p => this.getTPTAliasForProperty(p.name, join.ownerAlias), quotedAlias);
|
|
249
|
+
const left = this.driver.evaluateFormula(join.prop.formula, columns, table);
|
|
210
250
|
conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
|
|
211
251
|
return;
|
|
212
252
|
}
|
|
@@ -216,11 +256,20 @@ export class QueryBuilderHelper {
|
|
|
216
256
|
conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
|
|
217
257
|
});
|
|
218
258
|
}
|
|
219
|
-
if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
|
|
259
|
+
if (join.prop.targetMeta?.root.inheritanceType === 'sti' && join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
|
|
220
260
|
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
221
261
|
const alias = join.inverseAlias ?? join.alias;
|
|
222
262
|
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
223
263
|
}
|
|
264
|
+
// For polymorphic relations, add discriminator condition to filter by target entity type
|
|
265
|
+
if (join.prop.polymorphic && join.prop.discriminatorColumn && join.prop.discriminatorMap) {
|
|
266
|
+
const discriminatorValue = QueryHelper.findDiscriminatorValue(join.prop.discriminatorMap, join.prop.targetMeta.class);
|
|
267
|
+
if (discriminatorValue) {
|
|
268
|
+
const discriminatorCol = this.platform.quoteIdentifier(`${join.ownerAlias}.${join.prop.discriminatorColumn}`);
|
|
269
|
+
conditions.push(`${discriminatorCol} = ?`);
|
|
270
|
+
params.push(discriminatorValue);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
224
273
|
let sql = method + ' ';
|
|
225
274
|
if (join.nested) {
|
|
226
275
|
sql += `(${this.platform.quoteIdentifier(table)} as ${this.platform.quoteIdentifier(join.alias)}`;
|
|
@@ -243,7 +292,7 @@ export class QueryBuilderHelper {
|
|
|
243
292
|
this.alias = oldAlias;
|
|
244
293
|
if (subquery.sql) {
|
|
245
294
|
conditions.push(subquery.sql);
|
|
246
|
-
params.push(
|
|
295
|
+
subquery.params.forEach(p => params.push(p));
|
|
247
296
|
}
|
|
248
297
|
if (conditions.length > 0) {
|
|
249
298
|
sql += ` on ${conditions.join(' and ')}`;
|
|
@@ -265,11 +314,11 @@ export class QueryBuilderHelper {
|
|
|
265
314
|
isOneToOneInverse(field, meta) {
|
|
266
315
|
meta ??= this.metadata.find(this.entityName);
|
|
267
316
|
const prop = meta.properties[field.replace(/:ref$/, '')];
|
|
268
|
-
return prop
|
|
317
|
+
return prop?.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
269
318
|
}
|
|
270
319
|
getTableName(entityName) {
|
|
271
320
|
const meta = this.metadata.find(entityName);
|
|
272
|
-
return meta
|
|
321
|
+
return meta?.tableName ?? Utils.className(entityName);
|
|
273
322
|
}
|
|
274
323
|
/**
|
|
275
324
|
* Checks whether the RE can be rewritten to simple LIKE query
|
|
@@ -332,7 +381,7 @@ export class QueryBuilderHelper {
|
|
|
332
381
|
_appendQueryCondition(type, cond, operator) {
|
|
333
382
|
const parts = [];
|
|
334
383
|
const params = [];
|
|
335
|
-
for (const k of
|
|
384
|
+
for (const k of Utils.getObjectQueryKeys(cond)) {
|
|
336
385
|
if (k === '$and' || k === '$or') {
|
|
337
386
|
if (operator) {
|
|
338
387
|
this.append(() => this.appendGroupCondition(type, k, cond[k]), parts, params, operator);
|
|
@@ -344,7 +393,7 @@ export class QueryBuilderHelper {
|
|
|
344
393
|
if (k === '$not') {
|
|
345
394
|
const res = this._appendQueryCondition(type, cond[k]);
|
|
346
395
|
parts.push(`not (${res.sql})`);
|
|
347
|
-
params.push(
|
|
396
|
+
res.params.forEach(p => params.push(p));
|
|
348
397
|
continue;
|
|
349
398
|
}
|
|
350
399
|
this.append(() => this.appendQuerySubCondition(type, cond, k), parts, params);
|
|
@@ -362,7 +411,6 @@ export class QueryBuilderHelper {
|
|
|
362
411
|
appendQuerySubCondition(type, cond, key) {
|
|
363
412
|
const parts = [];
|
|
364
413
|
const params = [];
|
|
365
|
-
const fields = Utils.splitPrimaryKeys(key);
|
|
366
414
|
if (this.isSimpleRegExp(cond[key])) {
|
|
367
415
|
parts.push(`${this.platform.quoteIdentifier(this.mapper(key, type))} like ?`);
|
|
368
416
|
params.push(this.getRegExpParam(cond[key]));
|
|
@@ -372,19 +420,21 @@ export class QueryBuilderHelper {
|
|
|
372
420
|
return this.processObjectSubCondition(cond, key, type);
|
|
373
421
|
}
|
|
374
422
|
const op = cond[key] === null ? 'is' : '=';
|
|
375
|
-
|
|
376
|
-
|
|
423
|
+
if (Raw.isKnownFragmentSymbol(key)) {
|
|
424
|
+
const raw = Raw.getKnownFragment(key);
|
|
377
425
|
const sql = raw.sql.replaceAll(ALIAS_REPLACEMENT, this.alias);
|
|
378
426
|
const value = Utils.asArray(cond[key]);
|
|
379
427
|
params.push(...raw.params);
|
|
380
428
|
if (value.length > 0) {
|
|
381
|
-
const
|
|
429
|
+
const k = key;
|
|
430
|
+
const val = this.getValueReplacement([k], value[0], params, k);
|
|
382
431
|
parts.push(`${sql} ${op} ${val}`);
|
|
383
432
|
return { sql: parts.join(' and '), params };
|
|
384
433
|
}
|
|
385
434
|
parts.push(sql);
|
|
386
435
|
return { sql: parts.join(' and '), params };
|
|
387
436
|
}
|
|
437
|
+
const fields = Utils.splitPrimaryKeys(key);
|
|
388
438
|
if (this.subQueries[key]) {
|
|
389
439
|
const val = this.getValueReplacement(fields, cond[key], params, key);
|
|
390
440
|
parts.push(`(${this.subQueries[key]}) ${op} ${val}`);
|
|
@@ -404,9 +454,7 @@ export class QueryBuilderHelper {
|
|
|
404
454
|
}
|
|
405
455
|
// grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
|
|
406
456
|
if (size > 1) {
|
|
407
|
-
const rawField = RawQueryFragment.getKnownFragment(key);
|
|
408
457
|
const subCondition = Object.entries(value).map(([subKey, subValue]) => {
|
|
409
|
-
key = rawField?.clone().toString() ?? key;
|
|
410
458
|
return ({ [key]: { [subKey]: subValue } });
|
|
411
459
|
});
|
|
412
460
|
for (const sub of subCondition) {
|
|
@@ -424,7 +472,8 @@ export class QueryBuilderHelper {
|
|
|
424
472
|
throw ValidationError.invalidQueryCondition(cond);
|
|
425
473
|
}
|
|
426
474
|
const replacement = this.getOperatorReplacement(op, value);
|
|
427
|
-
const
|
|
475
|
+
const rawField = Raw.isKnownFragmentSymbol(key);
|
|
476
|
+
const fields = rawField ? [key] : Utils.splitPrimaryKeys(key);
|
|
428
477
|
if (fields.length > 1 && Array.isArray(value[op])) {
|
|
429
478
|
const singleTuple = !value[op].every((v) => Array.isArray(v));
|
|
430
479
|
if (!this.platform.allowsComparingTuples()) {
|
|
@@ -452,12 +501,15 @@ export class QueryBuilderHelper {
|
|
|
452
501
|
parts.push(`(${this.subQueries[key]}) ${replacement} ${val}`);
|
|
453
502
|
return { sql: parts.join(' and '), params };
|
|
454
503
|
}
|
|
455
|
-
const [a, f] = this.splitField(key);
|
|
456
|
-
const prop = this.getProperty(f, a);
|
|
504
|
+
const [a, f] = rawField ? [] : this.splitField(key);
|
|
505
|
+
const prop = f && this.getProperty(f, a);
|
|
506
|
+
if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
507
|
+
return { sql: '', params };
|
|
508
|
+
}
|
|
457
509
|
if (op === '$fulltext') {
|
|
458
510
|
/* v8 ignore next */
|
|
459
511
|
if (!prop) {
|
|
460
|
-
throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
|
|
512
|
+
throw new Error(`Cannot use $fulltext operator on ${String(key)}, property not found`);
|
|
461
513
|
}
|
|
462
514
|
const { sql, params: params2 } = raw(this.platform.getFullTextWhereClause(prop), {
|
|
463
515
|
column: this.mapper(key, type, undefined, null),
|
|
@@ -469,7 +521,7 @@ export class QueryBuilderHelper {
|
|
|
469
521
|
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
470
522
|
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
471
523
|
}
|
|
472
|
-
else if (value[op] instanceof
|
|
524
|
+
else if (value[op] instanceof Raw || value[op] instanceof NativeQueryBuilder) {
|
|
473
525
|
const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
|
|
474
526
|
const mappedKey = this.mapper(key, type, query, null);
|
|
475
527
|
let sql = query.sql;
|
|
@@ -501,7 +553,7 @@ export class QueryBuilderHelper {
|
|
|
501
553
|
params.push(item);
|
|
502
554
|
}
|
|
503
555
|
else {
|
|
504
|
-
value.forEach(
|
|
556
|
+
value.forEach(p => params.push(p));
|
|
505
557
|
}
|
|
506
558
|
return `(${value.map(() => '?').join(', ')})`;
|
|
507
559
|
}
|
|
@@ -528,6 +580,31 @@ export class QueryBuilderHelper {
|
|
|
528
580
|
}
|
|
529
581
|
return replacement;
|
|
530
582
|
}
|
|
583
|
+
validateQueryOrder(orderBy) {
|
|
584
|
+
const strKeys = [];
|
|
585
|
+
const rawKeys = [];
|
|
586
|
+
for (const key of Utils.getObjectQueryKeys(orderBy)) {
|
|
587
|
+
const raw = Raw.getKnownFragment(key);
|
|
588
|
+
if (raw) {
|
|
589
|
+
rawKeys.push(raw);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
strKeys.push(key);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (strKeys.length > 0 && rawKeys.length > 0) {
|
|
596
|
+
const example = [
|
|
597
|
+
...strKeys.map(key => ({ [key]: orderBy[key] })),
|
|
598
|
+
...rawKeys.map(rawKey => ({ [`raw('${rawKey.sql}')`]: orderBy[rawKey] })),
|
|
599
|
+
];
|
|
600
|
+
throw new Error([
|
|
601
|
+
`Invalid "orderBy": You are mixing field-based keys and raw SQL fragments inside a single object.`,
|
|
602
|
+
`This is not allowed because object key order cannot reliably preserve evaluation order.`,
|
|
603
|
+
`To fix this, split them into separate objects inside an array:\n`,
|
|
604
|
+
`orderBy: ${inspect(example, { depth: 5 }).replace(/"raw\('(.*)'\)"/g, `[raw('$1')]`)}`,
|
|
605
|
+
].join('\n'));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
531
608
|
getQueryOrder(type, orderBy, populate) {
|
|
532
609
|
if (Array.isArray(orderBy)) {
|
|
533
610
|
return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
|
|
@@ -536,11 +613,11 @@ export class QueryBuilderHelper {
|
|
|
536
613
|
}
|
|
537
614
|
getQueryOrderFromObject(type, orderBy, populate) {
|
|
538
615
|
const ret = [];
|
|
539
|
-
for (const key of
|
|
616
|
+
for (const key of Utils.getObjectQueryKeys(orderBy)) {
|
|
540
617
|
const direction = orderBy[key];
|
|
541
618
|
const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
|
|
542
|
-
|
|
543
|
-
|
|
619
|
+
if (Raw.isKnownFragmentSymbol(key)) {
|
|
620
|
+
const raw = Raw.getKnownFragment(key);
|
|
544
621
|
ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
|
|
545
622
|
continue;
|
|
546
623
|
}
|
|
@@ -549,7 +626,7 @@ export class QueryBuilderHelper {
|
|
|
549
626
|
let [alias, field] = this.splitField(f, true);
|
|
550
627
|
alias = populate[alias] || alias;
|
|
551
628
|
const prop = this.getProperty(field, alias);
|
|
552
|
-
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) ||
|
|
629
|
+
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || Raw.isKnownFragment(f);
|
|
553
630
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
554
631
|
/* v8 ignore next */
|
|
555
632
|
const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
|
|
@@ -592,14 +669,15 @@ export class QueryBuilderHelper {
|
|
|
592
669
|
if (type === QueryType.UPDATE) {
|
|
593
670
|
const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
|
|
594
671
|
if (returningProps.length > 0) {
|
|
595
|
-
|
|
672
|
+
const fields = returningProps.flatMap((prop) => {
|
|
596
673
|
if (prop.hasConvertToJSValueSQL) {
|
|
597
674
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
598
675
|
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
599
676
|
return [raw(sql)];
|
|
600
677
|
}
|
|
601
678
|
return prop.fieldNames;
|
|
602
|
-
})
|
|
679
|
+
});
|
|
680
|
+
qb.returning(fields);
|
|
603
681
|
}
|
|
604
682
|
}
|
|
605
683
|
}
|
|
@@ -624,7 +702,7 @@ export class QueryBuilderHelper {
|
|
|
624
702
|
getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
|
|
625
703
|
const meta = this.metadata.find(this.entityName);
|
|
626
704
|
if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
|
|
627
|
-
throw OptimisticLockError.lockFailed(this.entityName);
|
|
705
|
+
throw OptimisticLockError.lockFailed(Utils.className(this.entityName));
|
|
628
706
|
}
|
|
629
707
|
if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
|
|
630
708
|
const joins = Object.values(joinsMap);
|
|
@@ -650,9 +728,11 @@ export class QueryBuilderHelper {
|
|
|
650
728
|
prefix(field, always = false, quote = false, idx) {
|
|
651
729
|
let ret;
|
|
652
730
|
if (!this.isPrefixed(field)) {
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
731
|
+
// For TPT inheritance, resolve the correct alias for this property
|
|
732
|
+
const tptAlias = this.getTPTAliasForProperty(field, this.alias);
|
|
733
|
+
const alias = always ? (quote ? tptAlias : this.platform.quoteIdentifier(tptAlias)) + '.' : '';
|
|
734
|
+
const fieldName = this.fieldName(field, tptAlias, always, idx);
|
|
735
|
+
if (fieldName instanceof Raw) {
|
|
656
736
|
return fieldName.sql;
|
|
657
737
|
}
|
|
658
738
|
ret = alias + fieldName;
|
|
@@ -660,11 +740,16 @@ export class QueryBuilderHelper {
|
|
|
660
740
|
else {
|
|
661
741
|
const [a, ...rest] = field.split('.');
|
|
662
742
|
const f = rest.join('.');
|
|
663
|
-
|
|
664
|
-
|
|
743
|
+
// For TPT inheritance, resolve the correct alias for this property
|
|
744
|
+
// Only apply TPT resolution when `a` is an actual table alias (in aliasMap),
|
|
745
|
+
// not when it's an embedded property name like 'profile1.identity.links'
|
|
746
|
+
const isTableAlias = !!this.aliasMap[a];
|
|
747
|
+
const resolvedAlias = isTableAlias ? this.getTPTAliasForProperty(f, a) : a;
|
|
748
|
+
const fieldName = this.fieldName(f, resolvedAlias, always, idx);
|
|
749
|
+
if (fieldName instanceof Raw) {
|
|
665
750
|
return fieldName.sql;
|
|
666
751
|
}
|
|
667
|
-
ret =
|
|
752
|
+
ret = resolvedAlias + '.' + fieldName;
|
|
668
753
|
}
|
|
669
754
|
if (quote) {
|
|
670
755
|
return this.platform.quoteIdentifier(ret);
|
|
@@ -683,7 +768,7 @@ export class QueryBuilderHelper {
|
|
|
683
768
|
}
|
|
684
769
|
for (const sub of subCondition) {
|
|
685
770
|
// skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
|
|
686
|
-
const keys =
|
|
771
|
+
const keys = Utils.getObjectQueryKeys(sub);
|
|
687
772
|
const val = sub[keys[0]];
|
|
688
773
|
const simple = !Utils.isPlainObject(val) || Utils.getObjectKeysSize(val) === 1 || Object.keys(val).every(k => !Utils.isOperator(k));
|
|
689
774
|
if (keys.length === 1 && simple) {
|
|
@@ -719,27 +804,20 @@ export class QueryBuilderHelper {
|
|
|
719
804
|
}
|
|
720
805
|
getProperty(field, alias) {
|
|
721
806
|
const entityName = this.aliasMap[alias]?.entityName || this.entityName;
|
|
722
|
-
const meta = this.metadata.
|
|
807
|
+
const meta = this.metadata.get(entityName);
|
|
723
808
|
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
724
|
-
if (alias
|
|
809
|
+
if (alias) {
|
|
725
810
|
const prop = meta.properties[alias];
|
|
726
811
|
if (prop?.kind === ReferenceKind.EMBEDDED) {
|
|
727
|
-
// we want to select the full object property so hydration works as expected
|
|
728
|
-
if (prop.object) {
|
|
729
|
-
return prop;
|
|
730
|
-
}
|
|
731
812
|
const parts = field.split('.');
|
|
732
813
|
const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
|
|
733
814
|
return nest(prop);
|
|
734
815
|
}
|
|
735
816
|
}
|
|
736
|
-
if (meta) {
|
|
737
|
-
|
|
738
|
-
return meta.properties[field];
|
|
739
|
-
}
|
|
740
|
-
return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
|
|
817
|
+
if (meta.properties[field]) {
|
|
818
|
+
return meta.properties[field];
|
|
741
819
|
}
|
|
742
|
-
return
|
|
820
|
+
return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
|
|
743
821
|
}
|
|
744
822
|
isTableNameAliasRequired(type) {
|
|
745
823
|
return [QueryType.SELECT, QueryType.COUNT].includes(type);
|
|
@@ -753,4 +831,15 @@ export class QueryBuilderHelper {
|
|
|
753
831
|
}
|
|
754
832
|
return cond;
|
|
755
833
|
}
|
|
834
|
+
createFormulaTable(alias, meta, schema) {
|
|
835
|
+
const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
|
|
836
|
+
const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
|
|
837
|
+
return {
|
|
838
|
+
alias,
|
|
839
|
+
name: meta.tableName,
|
|
840
|
+
schema: effectiveSchema,
|
|
841
|
+
qualifiedName,
|
|
842
|
+
toString: () => alias,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
756
845
|
}
|
|
@@ -8,11 +8,11 @@ import { QueryBuilder } from './QueryBuilder.js';
|
|
|
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 });
|
|
11
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
|
|
12
12
|
if (this.shouldJoin(qb, nestedAlias)) {
|
|
13
13
|
const path = this.getPath();
|
|
14
14
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
15
|
-
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.
|
|
15
|
+
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotEntity ?? this.entityName);
|
|
16
16
|
const field = this.aliased(this.prop.name, options?.alias);
|
|
17
17
|
const type = this.prop.kind === ReferenceKind.MANY_TO_MANY ? JoinType.pivotJoin : JoinType.leftJoin;
|
|
18
18
|
qb.join(field, nestedAlias, undefined, type, path);
|
package/query/raw.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type AnyString, type Dictionary, type EntityKey, type RawQueryFragment } from '@mikro-orm/core';
|
|
2
|
-
import type { SelectQueryBuilder } from 'kysely';
|
|
3
|
-
|
|
2
|
+
import type { SelectQueryBuilder as KyselySelectQueryBuilder } from 'kysely';
|
|
3
|
+
/** @internal Type for QueryBuilder instances passed to raw() - uses toRaw to distinguish from Kysely QueryBuilder */
|
|
4
|
+
type QueryBuilderLike = {
|
|
5
|
+
toQuery(): {
|
|
6
|
+
sql: string;
|
|
7
|
+
params: readonly unknown[];
|
|
8
|
+
};
|
|
9
|
+
toRaw(): RawQueryFragment;
|
|
10
|
+
};
|
|
4
11
|
/**
|
|
5
12
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
6
13
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
@@ -56,4 +63,5 @@ import { QueryBuilder } from './QueryBuilder.js';
|
|
|
56
63
|
* export class Author { ... }
|
|
57
64
|
* ```
|
|
58
65
|
*/
|
|
59
|
-
export declare function raw<
|
|
66
|
+
export declare function raw<R = RawQueryFragment & symbol, T extends object = any>(sql: QueryBuilderLike | KyselySelectQueryBuilder<any, any, any> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
|
|
67
|
+
export {};
|
package/query/raw.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { raw as raw_, Utils } from '@mikro-orm/core';
|
|
2
|
-
import { QueryBuilder } from './QueryBuilder.js';
|
|
3
2
|
/**
|
|
4
3
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
5
4
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
@@ -60,7 +59,7 @@ export function raw(sql, params) {
|
|
|
60
59
|
const query = sql.compile();
|
|
61
60
|
return raw_(query.sql, query.parameters);
|
|
62
61
|
}
|
|
63
|
-
if (sql
|
|
62
|
+
if (Utils.isObject(sql) && 'toQuery' in sql) {
|
|
64
63
|
const query = sql.toQuery();
|
|
65
64
|
return raw_(query.sql, query.params);
|
|
66
65
|
}
|