@mikro-orm/knex 6.0.0-dev.12 → 6.0.0-dev.120
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 +8 -4
- package/AbstractSqlConnection.js +30 -18
- package/AbstractSqlDriver.d.ts +21 -23
- package/AbstractSqlDriver.js +378 -221
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -6
- package/SqlEntityManager.js +5 -9
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +23 -6
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +8 -8
- package/query/CriteriaNode.js +16 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +17 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +12 -8
- package/query/QueryBuilder.d.ts +44 -20
- package/query/QueryBuilder.js +371 -130
- package/query/QueryBuilderHelper.d.ts +10 -15
- package/query/QueryBuilderHelper.js +197 -158
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +9 -6
- package/query/enums.js +1 -1
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +27 -9
- package/schema/DatabaseTable.d.ts +4 -2
- package/schema/DatabaseTable.js +66 -32
- package/schema/SchemaComparator.js +15 -3
- package/schema/SchemaHelper.d.ts +6 -4
- package/schema/SchemaHelper.js +11 -4
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +61 -28
- package/typings.d.ts +13 -7
|
@@ -8,6 +8,14 @@ const enums_1 = require("./enums");
|
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
10
|
class QueryBuilderHelper {
|
|
11
|
+
entityName;
|
|
12
|
+
alias;
|
|
13
|
+
aliasMap;
|
|
14
|
+
subQueries;
|
|
15
|
+
knex;
|
|
16
|
+
driver;
|
|
17
|
+
platform;
|
|
18
|
+
metadata;
|
|
11
19
|
constructor(entityName, alias, aliasMap, subQueries, knex, driver) {
|
|
12
20
|
this.entityName = entityName;
|
|
13
21
|
this.alias = alias;
|
|
@@ -19,6 +27,10 @@ class QueryBuilderHelper {
|
|
|
19
27
|
this.metadata = this.driver.getMetadata();
|
|
20
28
|
}
|
|
21
29
|
mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
|
|
30
|
+
if (core_1.Utils.isRawSql(field)) {
|
|
31
|
+
return this.knex.raw(field.sql, field.params);
|
|
32
|
+
}
|
|
33
|
+
/* istanbul ignore next */
|
|
22
34
|
if (typeof field !== 'string') {
|
|
23
35
|
return field;
|
|
24
36
|
}
|
|
@@ -29,22 +41,42 @@ class QueryBuilderHelper {
|
|
|
29
41
|
for (const p of fields) {
|
|
30
42
|
const [a, f] = this.splitField(p);
|
|
31
43
|
const prop = this.getProperty(f, a);
|
|
32
|
-
|
|
44
|
+
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
45
|
+
if (fkIdx2 !== -1) {
|
|
46
|
+
parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
|
|
47
|
+
}
|
|
48
|
+
else if (prop) {
|
|
33
49
|
parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
|
|
34
50
|
}
|
|
35
51
|
else {
|
|
36
52
|
parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
|
|
37
53
|
}
|
|
38
54
|
}
|
|
55
|
+
// flatten the value if we see we are expanding nested composite key
|
|
56
|
+
// hackish, but cleaner solution would require quite a lot of refactoring
|
|
57
|
+
if (fields.length !== parts.length && Array.isArray(value)) {
|
|
58
|
+
value.forEach(row => {
|
|
59
|
+
if (Array.isArray(row)) {
|
|
60
|
+
const tmp = core_1.Utils.flatten(row);
|
|
61
|
+
row.length = 0;
|
|
62
|
+
row.push(...tmp);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
39
66
|
return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
|
|
40
67
|
}
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
69
|
+
if (rawField) {
|
|
70
|
+
return this.knex.raw(rawField.sql, rawField.params);
|
|
71
|
+
}
|
|
43
72
|
const [a, f] = this.splitField(field);
|
|
44
73
|
const prop = this.getProperty(f, a);
|
|
74
|
+
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
75
|
+
const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
|
|
76
|
+
let ret = field;
|
|
45
77
|
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
46
78
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
47
|
-
return this.alias + '.' + prop.fieldNames[
|
|
79
|
+
return this.alias + '.' + prop.fieldNames[fkIdx];
|
|
48
80
|
}
|
|
49
81
|
const noPrefix = prop && prop.persist === false;
|
|
50
82
|
if (prop?.fieldNameRaw) {
|
|
@@ -56,24 +88,29 @@ class QueryBuilderHelper {
|
|
|
56
88
|
const as = alias === null ? '' : ` as ${aliased}`;
|
|
57
89
|
return this.knex.raw(`${prop.formula(alias2)}${as}`);
|
|
58
90
|
}
|
|
59
|
-
if (prop?.
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
if (prop?.hasConvertToJSValueSQL) {
|
|
92
|
+
let valueSQL;
|
|
93
|
+
if (prop.fieldNames.length > 1 && fkIdx !== -1) {
|
|
94
|
+
const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
|
|
95
|
+
const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
|
|
96
|
+
valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const prefixed = this.prefix(field, isTableNameAliasRequired, true);
|
|
100
|
+
valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
101
|
+
}
|
|
62
102
|
if (alias === null) {
|
|
63
103
|
return this.knex.raw(valueSQL);
|
|
64
104
|
}
|
|
65
|
-
return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[
|
|
105
|
+
return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
|
|
66
106
|
}
|
|
67
107
|
// do not wrap custom expressions
|
|
68
|
-
if (!
|
|
69
|
-
ret = this.prefix(field);
|
|
108
|
+
if (!rawField) {
|
|
109
|
+
ret = this.prefix(field, false, false, fkIdx);
|
|
70
110
|
}
|
|
71
111
|
if (alias) {
|
|
72
112
|
ret += ' as ' + alias;
|
|
73
113
|
}
|
|
74
|
-
if (customExpression) {
|
|
75
|
-
return this.knex.raw(ret, value);
|
|
76
|
-
}
|
|
77
114
|
if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
|
|
78
115
|
return ret;
|
|
79
116
|
}
|
|
@@ -84,46 +121,47 @@ class QueryBuilderHelper {
|
|
|
84
121
|
return data.map(d => this.processData(d, convertCustomTypes, true));
|
|
85
122
|
}
|
|
86
123
|
const meta = this.metadata.find(this.entityName);
|
|
87
|
-
data = this.
|
|
124
|
+
data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
|
|
88
125
|
if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
|
|
89
126
|
/* istanbul ignore next */
|
|
90
127
|
data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
|
|
91
128
|
}
|
|
92
129
|
return data;
|
|
93
130
|
}
|
|
94
|
-
joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
|
|
131
|
+
joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
95
132
|
const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
|
|
96
133
|
const table = this.getTableName(prop.type);
|
|
97
|
-
const schema = this.driver.getSchemaName(prop.targetMeta);
|
|
98
134
|
const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
|
|
99
135
|
const inverseJoinColumns = prop.referencedColumnNames;
|
|
100
136
|
const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
|
|
137
|
+
schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
|
|
101
138
|
return {
|
|
102
139
|
prop, type, cond, ownerAlias, alias, table, schema,
|
|
103
140
|
joinColumns, inverseJoinColumns, primaryKeys,
|
|
104
141
|
};
|
|
105
142
|
}
|
|
106
|
-
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
|
|
143
|
+
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
107
144
|
return {
|
|
108
145
|
prop, type, cond, ownerAlias, alias,
|
|
109
146
|
table: this.getTableName(prop.type),
|
|
110
|
-
schema: this.driver.getSchemaName(prop.targetMeta),
|
|
147
|
+
schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
111
148
|
joinColumns: prop.referencedColumnNames,
|
|
112
149
|
primaryKeys: prop.fieldNames,
|
|
113
150
|
};
|
|
114
151
|
}
|
|
115
|
-
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
|
|
152
|
+
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
|
|
116
153
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
117
154
|
const ret = {
|
|
118
155
|
[`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
|
|
119
|
-
prop, type,
|
|
156
|
+
prop, type, ownerAlias,
|
|
120
157
|
alias: pivotAlias,
|
|
121
158
|
inverseAlias: alias,
|
|
122
159
|
joinColumns: prop.joinColumns,
|
|
123
160
|
inverseJoinColumns: prop.inverseJoinColumns,
|
|
124
161
|
primaryKeys: prop.referencedColumnNames,
|
|
162
|
+
cond: {},
|
|
125
163
|
table: pivotMeta.tableName,
|
|
126
|
-
schema: this.driver.getSchemaName(pivotMeta),
|
|
164
|
+
schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(pivotMeta, { schema }),
|
|
127
165
|
path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
|
|
128
166
|
},
|
|
129
167
|
};
|
|
@@ -131,41 +169,56 @@ class QueryBuilderHelper {
|
|
|
131
169
|
return ret;
|
|
132
170
|
}
|
|
133
171
|
const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
|
|
134
|
-
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
|
|
172
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
|
|
135
173
|
ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
|
|
174
|
+
const tmp = prop2.referencedTableName.split('.');
|
|
175
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`].schema ??= tmp.length > 1 ? tmp[0] : undefined;
|
|
136
176
|
return ret;
|
|
137
177
|
}
|
|
138
|
-
joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
|
|
139
|
-
const pivotMeta = this.metadata.find(field);
|
|
140
|
-
const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
|
|
141
|
-
return {
|
|
142
|
-
prop, type, cond, ownerAlias, alias,
|
|
143
|
-
table: pivotMeta.collection,
|
|
144
|
-
schema: pivotMeta.schema,
|
|
145
|
-
joinColumns: prop.joinColumns,
|
|
146
|
-
inverseJoinColumns: prop2.joinColumns,
|
|
147
|
-
primaryKeys: prop.referencedColumnNames,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
178
|
processJoins(qb, joins, schema) {
|
|
151
179
|
Object.values(joins).forEach(join => {
|
|
152
|
-
let table =
|
|
180
|
+
let table = join.table;
|
|
153
181
|
const method = join.type === 'innerJoin' ? 'inner join' : 'left join';
|
|
182
|
+
const conditions = [];
|
|
183
|
+
const params = [];
|
|
154
184
|
schema = join.schema && join.schema !== '*' ? join.schema : schema;
|
|
155
185
|
if (schema) {
|
|
156
186
|
table = `${schema}.${table}`;
|
|
157
187
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
188
|
+
if (!join.subquery) {
|
|
189
|
+
join.primaryKeys.forEach((primaryKey, idx) => {
|
|
190
|
+
const right = `${join.alias}.${join.joinColumns[idx]}`;
|
|
191
|
+
if (join.prop.formula) {
|
|
192
|
+
const left = join.prop.formula(join.ownerAlias);
|
|
193
|
+
conditions.push(`${left} = ${this.knex.ref(right)}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const left = `${join.ownerAlias}.${primaryKey}`;
|
|
197
|
+
conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
|
|
201
|
+
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
202
|
+
const alias = join.inverseAlias ?? join.alias;
|
|
203
|
+
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
204
|
+
}
|
|
165
205
|
Object.keys(join.cond).forEach(key => {
|
|
166
|
-
|
|
206
|
+
const needsPrefix = key.includes('.') || core_1.Utils.isOperator(key) || core_1.RawQueryFragment.isKnownFragment(key);
|
|
207
|
+
const newKey = needsPrefix ? key : `${join.alias}.${key}`;
|
|
208
|
+
conditions.push(this.processJoinClause(newKey, join.cond[key], params));
|
|
167
209
|
});
|
|
168
|
-
|
|
210
|
+
let sql = method + ' ';
|
|
211
|
+
if (join.subquery) {
|
|
212
|
+
sql += `(${join.subquery})`;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
sql += this.knex.ref(table);
|
|
216
|
+
}
|
|
217
|
+
sql += ` as ${this.knex.ref(join.alias)}`;
|
|
218
|
+
if (conditions.length > 0) {
|
|
219
|
+
sql += ` on ${conditions.join(' and ')}`;
|
|
220
|
+
}
|
|
221
|
+
return qb.joinRaw(sql, params);
|
|
169
222
|
});
|
|
170
223
|
}
|
|
171
224
|
processJoinClause(key, value, params, operator = '$eq') {
|
|
@@ -212,20 +265,14 @@ class QueryBuilderHelper {
|
|
|
212
265
|
if (operator === '$exists') {
|
|
213
266
|
value = null;
|
|
214
267
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const { sql, bindings } = k.toSQL().toNative();
|
|
223
|
-
if (params2.length > 0) {
|
|
224
|
-
params.push(...bindings);
|
|
225
|
-
params.push(...params2);
|
|
226
|
-
return sql + ' = ?';
|
|
268
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(key);
|
|
269
|
+
if (rawField) {
|
|
270
|
+
let sql = rawField.sql;
|
|
271
|
+
params.push(...rawField.params);
|
|
272
|
+
params.push(...core_1.Utils.asArray(value));
|
|
273
|
+
if (core_1.Utils.asArray(value).length > 0) {
|
|
274
|
+
sql += ' = ?';
|
|
227
275
|
}
|
|
228
|
-
params.push(...bindings);
|
|
229
276
|
return sql;
|
|
230
277
|
}
|
|
231
278
|
const sql = this.mapper(key);
|
|
@@ -241,7 +288,7 @@ class QueryBuilderHelper {
|
|
|
241
288
|
return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
|
|
242
289
|
}
|
|
243
290
|
mapJoinColumns(type, join) {
|
|
244
|
-
if (join.prop && join.prop.
|
|
291
|
+
if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
|
|
245
292
|
return join.prop.fieldNames.map((fieldName, idx) => {
|
|
246
293
|
return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
|
|
247
294
|
});
|
|
@@ -251,10 +298,10 @@ class QueryBuilderHelper {
|
|
|
251
298
|
...join.inverseJoinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
|
|
252
299
|
];
|
|
253
300
|
}
|
|
254
|
-
isOneToOneInverse(field) {
|
|
255
|
-
|
|
301
|
+
isOneToOneInverse(field, meta) {
|
|
302
|
+
meta ??= this.metadata.find(this.entityName);
|
|
256
303
|
const prop = meta.properties[field];
|
|
257
|
-
return prop && prop.
|
|
304
|
+
return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
258
305
|
}
|
|
259
306
|
getTableName(entityName) {
|
|
260
307
|
const meta = this.metadata.find(entityName);
|
|
@@ -293,13 +340,13 @@ class QueryBuilderHelper {
|
|
|
293
340
|
}
|
|
294
341
|
appendOnConflictClause(type, onConflict, qb) {
|
|
295
342
|
onConflict.forEach(item => {
|
|
296
|
-
const sub = qb.onConflict(item.fields);
|
|
343
|
+
const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
|
|
297
344
|
core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
|
|
298
345
|
core_1.Utils.runIfNotEmpty(() => {
|
|
299
346
|
let mergeParam = item.merge;
|
|
300
347
|
if (core_1.Utils.isObject(item.merge)) {
|
|
301
348
|
mergeParam = {};
|
|
302
|
-
|
|
349
|
+
core_1.Utils.keys(item.merge).forEach(key => {
|
|
303
350
|
const k = this.mapper(key, type);
|
|
304
351
|
mergeParam[k] = item.merge[key];
|
|
305
352
|
});
|
|
@@ -330,38 +377,37 @@ class QueryBuilderHelper {
|
|
|
330
377
|
}
|
|
331
378
|
appendQuerySubCondition(qb, type, method, cond, key, operator) {
|
|
332
379
|
const m = operator === '$or' ? 'orWhere' : method;
|
|
380
|
+
if (cond[key] instanceof core_1.RawQueryFragment) {
|
|
381
|
+
cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
|
|
382
|
+
}
|
|
333
383
|
if (this.isSimpleRegExp(cond[key])) {
|
|
334
384
|
return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
|
|
335
385
|
}
|
|
336
386
|
if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
|
|
337
387
|
return this.processObjectSubCondition(cond, key, qb, method, m, type);
|
|
338
388
|
}
|
|
339
|
-
if (QueryBuilderHelper.isCustomExpression(key)) {
|
|
340
|
-
return this.processCustomExpression(qb, m, key, cond, type);
|
|
341
|
-
}
|
|
342
389
|
const op = cond[key] === null ? 'is' : '=';
|
|
390
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
391
|
+
if (raw) {
|
|
392
|
+
const value = core_1.Utils.asArray(cond[key]);
|
|
393
|
+
if (value.length > 0) {
|
|
394
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
|
|
395
|
+
}
|
|
396
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params));
|
|
397
|
+
}
|
|
343
398
|
if (this.subQueries[key]) {
|
|
344
399
|
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
|
|
345
400
|
}
|
|
346
401
|
qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
|
|
347
402
|
}
|
|
348
|
-
processCustomExpression(clause, m, key, cond, type) {
|
|
349
|
-
// unwind parameters when ? found in field name
|
|
350
|
-
const count = key.concat('?').match(/\?/g).length - 1;
|
|
351
|
-
const value = core_1.Utils.asArray(cond[key]);
|
|
352
|
-
const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
|
|
353
|
-
const params2 = value.slice(count);
|
|
354
|
-
const k = this.mapper(key, type, params1);
|
|
355
|
-
if (params2.length > 0) {
|
|
356
|
-
const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
|
|
357
|
-
return void clause[m](k, val);
|
|
358
|
-
}
|
|
359
|
-
clause[m](k);
|
|
360
|
-
}
|
|
361
403
|
processObjectSubCondition(cond, key, qb, method, m, type) {
|
|
362
404
|
// grouped condition for one field
|
|
363
405
|
let value = cond[key];
|
|
364
|
-
|
|
406
|
+
const size = core_1.Utils.getObjectKeysSize(value);
|
|
407
|
+
if (core_1.Utils.isPlainObject(value) && size === 0) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (size > 1) {
|
|
365
411
|
const subCondition = Object.entries(value).map(([subKey, subValue]) => ({ [key]: { [subKey]: subValue } }));
|
|
366
412
|
return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
|
|
367
413
|
}
|
|
@@ -370,13 +416,15 @@ class QueryBuilderHelper {
|
|
|
370
416
|
}
|
|
371
417
|
// operators
|
|
372
418
|
const op = Object.keys(core_1.QueryOperator).find(op => op in value);
|
|
419
|
+
/* istanbul ignore next */
|
|
373
420
|
if (!op) {
|
|
374
421
|
throw new Error(`Invalid query condition: ${(0, util_1.inspect)(cond)}`);
|
|
375
422
|
}
|
|
376
423
|
const replacement = this.getOperatorReplacement(op, value);
|
|
377
424
|
const fields = core_1.Utils.splitPrimaryKeys(key);
|
|
378
425
|
if (fields.length > 1 && Array.isArray(value[op]) && !value[op].every((v) => Array.isArray(v))) {
|
|
379
|
-
value[op]
|
|
426
|
+
const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
|
|
427
|
+
value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, tmp);
|
|
380
428
|
}
|
|
381
429
|
if (this.subQueries[key]) {
|
|
382
430
|
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
|
|
@@ -394,7 +442,8 @@ class QueryBuilderHelper {
|
|
|
394
442
|
}));
|
|
395
443
|
}
|
|
396
444
|
else {
|
|
397
|
-
|
|
445
|
+
const mappedKey = this.mapper(key, type, value[op], null);
|
|
446
|
+
qb[m](mappedKey, replacement, value[op]);
|
|
398
447
|
}
|
|
399
448
|
}
|
|
400
449
|
getOperatorReplacement(op, value) {
|
|
@@ -422,36 +471,63 @@ class QueryBuilderHelper {
|
|
|
422
471
|
}
|
|
423
472
|
getQueryOrderFromObject(type, orderBy, populate) {
|
|
424
473
|
const ret = [];
|
|
425
|
-
Object.keys(orderBy).forEach(
|
|
426
|
-
const direction = orderBy[
|
|
474
|
+
Object.keys(orderBy).forEach(key => {
|
|
475
|
+
const direction = orderBy[key];
|
|
427
476
|
const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
|
|
428
|
-
|
|
429
|
-
|
|
477
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
478
|
+
if (raw) {
|
|
479
|
+
ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
|
|
430
480
|
return;
|
|
431
481
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const prop = this.getProperty(
|
|
437
|
-
const noPrefix = (prop && prop.persist === false && !prop.formula) ||
|
|
438
|
-
const column = this.mapper(noPrefix ?
|
|
482
|
+
core_1.Utils.splitPrimaryKeys(key).forEach(f => {
|
|
483
|
+
// eslint-disable-next-line prefer-const
|
|
484
|
+
let [alias, field] = this.splitField(f, true);
|
|
485
|
+
alias = populate[alias] || alias;
|
|
486
|
+
const prop = this.getProperty(field, alias);
|
|
487
|
+
const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
|
|
488
|
+
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
439
489
|
/* istanbul ignore next */
|
|
440
490
|
const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
|
|
441
491
|
const customOrder = prop?.customOrder;
|
|
442
|
-
|
|
492
|
+
let colPart = customOrder
|
|
443
493
|
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
444
494
|
: rawColumn;
|
|
445
|
-
|
|
495
|
+
if (core_1.Utils.isRawSql(colPart)) {
|
|
496
|
+
colPart = this.platform.formatQuery(colPart.sql, colPart.params);
|
|
497
|
+
}
|
|
498
|
+
if (Array.isArray(order)) {
|
|
499
|
+
order.forEach(part => ret.push(this.getQueryOrderFromObject(type, part, populate)));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
ret.push(`${colPart} ${order.toLowerCase()}`);
|
|
503
|
+
}
|
|
446
504
|
});
|
|
447
505
|
});
|
|
448
506
|
return ret.join(', ');
|
|
449
507
|
}
|
|
450
|
-
finalize(type, qb, meta) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
508
|
+
finalize(type, qb, meta, data, returning) {
|
|
509
|
+
if (!meta || !data || !this.platform.usesReturningStatement()) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
// always respect explicit returning hint
|
|
513
|
+
if (returning && returning.length > 0) {
|
|
514
|
+
qb.returning(returning.map(field => this.mapper(field, type)));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (type === enums_1.QueryType.INSERT) {
|
|
518
|
+
const returningProps = meta.hydrateProps
|
|
519
|
+
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
520
|
+
.filter(prop => !(prop.name in data));
|
|
521
|
+
if (returningProps.length > 0) {
|
|
522
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (type === enums_1.QueryType.UPDATE) {
|
|
527
|
+
const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
|
|
528
|
+
if (returningProps.length > 0) {
|
|
529
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
530
|
+
}
|
|
455
531
|
}
|
|
456
532
|
}
|
|
457
533
|
splitField(field, greedyAlias = false) {
|
|
@@ -489,30 +565,25 @@ class QueryBuilderHelper {
|
|
|
489
565
|
}
|
|
490
566
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
491
567
|
let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
|
|
492
|
-
if (versionProperty.
|
|
568
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
493
569
|
sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
494
570
|
}
|
|
495
571
|
qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
|
|
496
572
|
}
|
|
497
|
-
|
|
498
|
-
// if we do not have alias, we don't consider spaces as custom expressions
|
|
499
|
-
const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
|
|
500
|
-
return !!field.match(re);
|
|
501
|
-
}
|
|
502
|
-
prefix(field, always = false, quote = false) {
|
|
573
|
+
prefix(field, always = false, quote = false, idx) {
|
|
503
574
|
let ret;
|
|
504
575
|
if (!this.isPrefixed(field)) {
|
|
505
576
|
const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
|
|
506
|
-
const fieldName = this.fieldName(field, this.alias, always);
|
|
507
|
-
if (
|
|
508
|
-
return fieldName;
|
|
577
|
+
const fieldName = this.fieldName(field, this.alias, always, idx);
|
|
578
|
+
if (fieldName instanceof core_1.RawQueryFragment) {
|
|
579
|
+
return fieldName.sql;
|
|
509
580
|
}
|
|
510
581
|
ret = alias + fieldName;
|
|
511
582
|
}
|
|
512
583
|
else {
|
|
513
584
|
const [a, ...rest] = field.split('.');
|
|
514
585
|
const f = rest.join('.');
|
|
515
|
-
ret = a + '.' + this.fieldName(f, a);
|
|
586
|
+
ret = a + '.' + this.fieldName(f, a, always, idx);
|
|
516
587
|
}
|
|
517
588
|
if (quote) {
|
|
518
589
|
return this.platform.quoteIdentifier(ret);
|
|
@@ -538,25 +609,25 @@ class QueryBuilderHelper {
|
|
|
538
609
|
isPrefixed(field) {
|
|
539
610
|
return !!field.match(/[\w`"[\]]+\./);
|
|
540
611
|
}
|
|
541
|
-
fieldName(field, alias, always) {
|
|
612
|
+
fieldName(field, alias, always, idx = 0) {
|
|
542
613
|
const prop = this.getProperty(field, alias);
|
|
543
614
|
if (!prop) {
|
|
544
615
|
return field;
|
|
545
616
|
}
|
|
546
617
|
if (prop.fieldNameRaw) {
|
|
547
618
|
if (!always) {
|
|
548
|
-
return prop.fieldNameRaw
|
|
549
|
-
.replace(
|
|
550
|
-
.replace(this.platform.quoteIdentifier('') + '.', '');
|
|
619
|
+
return (0, core_1.raw)(prop.fieldNameRaw
|
|
620
|
+
.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
|
|
621
|
+
.replace(this.platform.quoteIdentifier('') + '.', ''));
|
|
551
622
|
}
|
|
552
623
|
if (alias) {
|
|
553
|
-
return prop.fieldNameRaw.replace(
|
|
624
|
+
return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
|
|
554
625
|
}
|
|
555
626
|
/* istanbul ignore next */
|
|
556
|
-
return prop.fieldNameRaw;
|
|
627
|
+
return (0, core_1.raw)(prop.fieldNameRaw);
|
|
557
628
|
}
|
|
558
629
|
/* istanbul ignore next */
|
|
559
|
-
return prop.fieldNames?.[
|
|
630
|
+
return prop.fieldNames?.[idx] ?? field;
|
|
560
631
|
}
|
|
561
632
|
getProperty(field, alias) {
|
|
562
633
|
const entityName = this.aliasMap[alias]?.entityName || this.entityName;
|
|
@@ -564,7 +635,7 @@ class QueryBuilderHelper {
|
|
|
564
635
|
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
565
636
|
if (alias && meta) {
|
|
566
637
|
const prop = meta.properties[alias];
|
|
567
|
-
if (prop?.
|
|
638
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
568
639
|
// we want to select the full object property so hydration works as expected
|
|
569
640
|
if (prop.object) {
|
|
570
641
|
return prop;
|
|
@@ -574,48 +645,16 @@ class QueryBuilderHelper {
|
|
|
574
645
|
return nest(prop);
|
|
575
646
|
}
|
|
576
647
|
}
|
|
577
|
-
|
|
648
|
+
if (meta) {
|
|
649
|
+
if (meta.properties[field]) {
|
|
650
|
+
return meta.properties[field];
|
|
651
|
+
}
|
|
652
|
+
return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
|
|
653
|
+
}
|
|
654
|
+
return undefined;
|
|
578
655
|
}
|
|
579
656
|
isTableNameAliasRequired(type) {
|
|
580
657
|
return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
|
|
581
658
|
}
|
|
582
|
-
mapData(data, properties, convertCustomTypes) {
|
|
583
|
-
if (!properties) {
|
|
584
|
-
return data;
|
|
585
|
-
}
|
|
586
|
-
data = Object.assign({}, data); // copy first
|
|
587
|
-
Object.keys(data).forEach(k => {
|
|
588
|
-
const prop = properties[k];
|
|
589
|
-
if (!prop) {
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
if (prop.embeddedProps && !prop.object) {
|
|
593
|
-
const copy = data[k];
|
|
594
|
-
delete data[k];
|
|
595
|
-
Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (prop.joinColumns && Array.isArray(data[k])) {
|
|
599
|
-
const copy = data[k];
|
|
600
|
-
delete data[k];
|
|
601
|
-
prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
|
|
605
|
-
data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query' });
|
|
606
|
-
}
|
|
607
|
-
if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
|
|
608
|
-
const quoted = this.platform.quoteValue(data[k]);
|
|
609
|
-
data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
|
|
610
|
-
}
|
|
611
|
-
if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
|
|
612
|
-
data[k] = JSON.stringify(data[k]);
|
|
613
|
-
}
|
|
614
|
-
if (prop.fieldNames) {
|
|
615
|
-
core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
return data;
|
|
619
|
-
}
|
|
620
659
|
}
|
|
621
660
|
exports.QueryBuilderHelper = QueryBuilderHelper;
|
|
@@ -3,7 +3,8 @@ import type { IQueryBuilder } from '../typings';
|
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
|
-
export declare class ScalarCriteriaNode extends CriteriaNode {
|
|
7
|
-
process
|
|
6
|
+
export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
|
+
process(qb: IQueryBuilder<T>, alias?: string): any;
|
|
8
|
+
willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string): boolean;
|
|
8
9
|
shouldJoin(): boolean;
|
|
9
10
|
}
|
|
@@ -13,23 +13,26 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
|
13
13
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
14
14
|
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
15
15
|
const field = `${alias}.${this.prop.name}`;
|
|
16
|
-
const type = this.prop.
|
|
16
|
+
const type = this.prop.kind === core_1.ReferenceKind.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
|
|
17
17
|
qb.join(field, nestedAlias, undefined, type, path);
|
|
18
18
|
// select the owner as virtual property when joining from 1:1 inverse side, but only if the parent is root entity
|
|
19
|
-
if (this.prop.
|
|
19
|
+
if (this.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !parentPath.includes('.')) {
|
|
20
20
|
qb.addSelect(field);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
return this.payload;
|
|
24
24
|
}
|
|
25
|
+
willAutoJoin(qb, alias) {
|
|
26
|
+
return this.shouldJoin();
|
|
27
|
+
}
|
|
25
28
|
shouldJoin() {
|
|
26
29
|
if (!this.parent || !this.prop) {
|
|
27
30
|
return false;
|
|
28
31
|
}
|
|
29
|
-
switch (this.prop.
|
|
30
|
-
case core_1.
|
|
31
|
-
case core_1.
|
|
32
|
-
case core_1.
|
|
32
|
+
switch (this.prop.kind) {
|
|
33
|
+
case core_1.ReferenceKind.ONE_TO_MANY: return true;
|
|
34
|
+
case core_1.ReferenceKind.MANY_TO_MANY: return true;
|
|
35
|
+
case core_1.ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
|
|
33
36
|
default: return false; // SCALAR, MANY_TO_ONE
|
|
34
37
|
}
|
|
35
38
|
}
|
package/query/enums.js
CHANGED