@yandjin-mikro-orm/knex 6.1.4-rc-sti-changes-1
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 +58 -0
- package/AbstractSqlConnection.js +206 -0
- package/AbstractSqlDriver.d.ts +73 -0
- package/AbstractSqlDriver.js +1378 -0
- package/AbstractSqlPlatform.d.ts +25 -0
- package/AbstractSqlPlatform.js +86 -0
- package/LICENSE +21 -0
- package/MonkeyPatchable.d.ts +11 -0
- package/MonkeyPatchable.js +39 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +131 -0
- package/README.md +383 -0
- package/SqlEntityManager.d.ts +25 -0
- package/SqlEntityManager.js +40 -0
- package/SqlEntityRepository.d.ts +24 -0
- package/SqlEntityRepository.js +36 -0
- package/index.d.ts +18 -0
- package/index.js +40 -0
- package/index.mjs +215 -0
- package/package.json +71 -0
- package/query/ArrayCriteriaNode.d.ts +10 -0
- package/query/ArrayCriteriaNode.js +25 -0
- package/query/CriteriaNode.d.ts +31 -0
- package/query/CriteriaNode.js +147 -0
- package/query/CriteriaNodeFactory.d.ts +12 -0
- package/query/CriteriaNodeFactory.js +90 -0
- package/query/ObjectCriteriaNode.d.ts +15 -0
- package/query/ObjectCriteriaNode.js +233 -0
- package/query/QueryBuilder.d.ts +291 -0
- package/query/QueryBuilder.js +1445 -0
- package/query/QueryBuilderHelper.d.ts +64 -0
- package/query/QueryBuilderHelper.js +747 -0
- package/query/ScalarCriteriaNode.d.ts +10 -0
- package/query/ScalarCriteriaNode.js +56 -0
- package/query/enums.d.ts +15 -0
- package/query/enums.js +20 -0
- package/query/index.d.ts +8 -0
- package/query/index.js +24 -0
- package/schema/DatabaseSchema.d.ts +29 -0
- package/schema/DatabaseSchema.js +140 -0
- package/schema/DatabaseTable.d.ts +61 -0
- package/schema/DatabaseTable.js +727 -0
- package/schema/SchemaComparator.d.ts +59 -0
- package/schema/SchemaComparator.js +603 -0
- package/schema/SchemaHelper.d.ts +56 -0
- package/schema/SchemaHelper.js +274 -0
- package/schema/SqlSchemaGenerator.d.ts +63 -0
- package/schema/SqlSchemaGenerator.js +598 -0
- package/schema/index.d.ts +5 -0
- package/schema/index.js +21 -0
- package/typings.d.ts +174 -0
- package/typings.js +2 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryBuilderHelper = void 0;
|
|
4
|
+
const util_1 = require("util");
|
|
5
|
+
const core_1 = require("@yandjin-mikro-orm/core");
|
|
6
|
+
const enums_1 = require("./enums");
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
class QueryBuilderHelper {
|
|
11
|
+
entityName;
|
|
12
|
+
alias;
|
|
13
|
+
aliasMap;
|
|
14
|
+
subQueries;
|
|
15
|
+
knex;
|
|
16
|
+
driver;
|
|
17
|
+
platform;
|
|
18
|
+
metadata;
|
|
19
|
+
constructor(entityName, alias, aliasMap, subQueries, knex, driver) {
|
|
20
|
+
this.entityName = entityName;
|
|
21
|
+
this.alias = alias;
|
|
22
|
+
this.aliasMap = aliasMap;
|
|
23
|
+
this.subQueries = subQueries;
|
|
24
|
+
this.knex = knex;
|
|
25
|
+
this.driver = driver;
|
|
26
|
+
this.platform = this.driver.getPlatform();
|
|
27
|
+
this.metadata = this.driver.getMetadata();
|
|
28
|
+
}
|
|
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 */
|
|
34
|
+
if (typeof field !== "string") {
|
|
35
|
+
return field;
|
|
36
|
+
}
|
|
37
|
+
const isTableNameAliasRequired = this.isTableNameAliasRequired(type);
|
|
38
|
+
const fields = core_1.Utils.splitPrimaryKeys(field);
|
|
39
|
+
if (fields.length > 1) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (const p of fields) {
|
|
42
|
+
const [a, f] = this.splitField(p);
|
|
43
|
+
const prop = this.getProperty(f, a);
|
|
44
|
+
const fkIdx2 = prop?.fieldNames.findIndex((name) => name === f) ?? -1;
|
|
45
|
+
if (fkIdx2 !== -1) {
|
|
46
|
+
parts.push(this.mapper(a !== this.alias
|
|
47
|
+
? `${a}.${prop.fieldNames[fkIdx2]}`
|
|
48
|
+
: prop.fieldNames[fkIdx2], type, value, alias));
|
|
49
|
+
}
|
|
50
|
+
else if (prop) {
|
|
51
|
+
parts.push(...prop.fieldNames.map((f) => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// flatten the value if we see we are expanding nested composite key
|
|
58
|
+
// hackish, but cleaner solution would require quite a lot of refactoring
|
|
59
|
+
if (fields.length !== parts.length && Array.isArray(value)) {
|
|
60
|
+
value.forEach((row) => {
|
|
61
|
+
if (Array.isArray(row)) {
|
|
62
|
+
const tmp = core_1.Utils.flatten(row);
|
|
63
|
+
row.length = 0;
|
|
64
|
+
row.push(...tmp);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return this.knex.raw("(" + parts.map((part) => this.knex.ref(part)).join(", ") + ")");
|
|
69
|
+
}
|
|
70
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
71
|
+
if (rawField) {
|
|
72
|
+
return this.knex.raw(rawField.sql, rawField.params);
|
|
73
|
+
}
|
|
74
|
+
const [a, f] = this.splitField(field);
|
|
75
|
+
const prop = this.getProperty(f, a);
|
|
76
|
+
const fkIdx2 = prop?.fieldNames.findIndex((name) => name === f) ?? -1;
|
|
77
|
+
const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
|
|
78
|
+
let ret = field;
|
|
79
|
+
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
80
|
+
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
81
|
+
return this.alias + "." + prop.fieldNames[fkIdx];
|
|
82
|
+
}
|
|
83
|
+
const noPrefix = prop && prop.persist === false;
|
|
84
|
+
if (prop?.fieldNameRaw) {
|
|
85
|
+
return this.knex.raw(this.prefix(field, isTableNameAliasRequired));
|
|
86
|
+
}
|
|
87
|
+
if (prop?.formula) {
|
|
88
|
+
const alias2 = this.knex.ref(a).toString();
|
|
89
|
+
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
90
|
+
const as = alias === null ? "" : ` as ${aliased}`;
|
|
91
|
+
return this.knex.raw(`${prop.formula(alias2)}${as}`);
|
|
92
|
+
}
|
|
93
|
+
if (prop?.hasConvertToJSValueSQL) {
|
|
94
|
+
let valueSQL;
|
|
95
|
+
if (prop.fieldNames.length > 1 && fkIdx !== -1) {
|
|
96
|
+
const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
|
|
97
|
+
const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
|
|
98
|
+
valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const prefixed = this.prefix(field, isTableNameAliasRequired, true);
|
|
102
|
+
valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
103
|
+
}
|
|
104
|
+
if (alias === null) {
|
|
105
|
+
return this.knex.raw(valueSQL);
|
|
106
|
+
}
|
|
107
|
+
return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
|
|
108
|
+
}
|
|
109
|
+
// do not wrap custom expressions
|
|
110
|
+
if (!rawField) {
|
|
111
|
+
ret = this.prefix(field, false, false, fkIdx);
|
|
112
|
+
}
|
|
113
|
+
if (alias) {
|
|
114
|
+
ret += " as " + alias;
|
|
115
|
+
}
|
|
116
|
+
if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
|
|
117
|
+
return ret;
|
|
118
|
+
}
|
|
119
|
+
return this.alias + "." + ret;
|
|
120
|
+
}
|
|
121
|
+
processData(data, convertCustomTypes, multi = false) {
|
|
122
|
+
if (Array.isArray(data)) {
|
|
123
|
+
return data.map((d) => this.processData(d, convertCustomTypes, true));
|
|
124
|
+
}
|
|
125
|
+
const meta = this.metadata.find(this.entityName);
|
|
126
|
+
data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
|
|
127
|
+
if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
|
|
128
|
+
/* istanbul ignore next */
|
|
129
|
+
data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword()
|
|
130
|
+
? this.knex.raw("default")
|
|
131
|
+
: undefined;
|
|
132
|
+
}
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
136
|
+
const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
|
|
137
|
+
const table = this.getTableName(prop.type);
|
|
138
|
+
const joinColumns = prop.owner
|
|
139
|
+
? prop.referencedColumnNames
|
|
140
|
+
: prop2.joinColumns;
|
|
141
|
+
const inverseJoinColumns = prop.referencedColumnNames;
|
|
142
|
+
const primaryKeys = prop.owner
|
|
143
|
+
? prop.joinColumns
|
|
144
|
+
: prop2.referencedColumnNames;
|
|
145
|
+
schema ??=
|
|
146
|
+
prop.targetMeta?.schema === "*"
|
|
147
|
+
? "*"
|
|
148
|
+
: this.driver.getSchemaName(prop.targetMeta);
|
|
149
|
+
return {
|
|
150
|
+
prop,
|
|
151
|
+
type,
|
|
152
|
+
cond,
|
|
153
|
+
ownerAlias,
|
|
154
|
+
alias,
|
|
155
|
+
table,
|
|
156
|
+
schema,
|
|
157
|
+
joinColumns,
|
|
158
|
+
inverseJoinColumns,
|
|
159
|
+
primaryKeys,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
163
|
+
return {
|
|
164
|
+
prop,
|
|
165
|
+
type,
|
|
166
|
+
cond,
|
|
167
|
+
ownerAlias,
|
|
168
|
+
alias,
|
|
169
|
+
table: this.getTableName(prop.type),
|
|
170
|
+
schema: prop.targetMeta?.schema === "*"
|
|
171
|
+
? "*"
|
|
172
|
+
: this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
173
|
+
joinColumns: prop.referencedColumnNames,
|
|
174
|
+
primaryKeys: prop.fieldNames,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
|
|
178
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
179
|
+
const ret = {
|
|
180
|
+
[`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
|
|
181
|
+
prop,
|
|
182
|
+
type,
|
|
183
|
+
ownerAlias,
|
|
184
|
+
alias: pivotAlias,
|
|
185
|
+
inverseAlias: alias,
|
|
186
|
+
joinColumns: prop.joinColumns,
|
|
187
|
+
inverseJoinColumns: prop.inverseJoinColumns,
|
|
188
|
+
primaryKeys: prop.referencedColumnNames,
|
|
189
|
+
cond: {},
|
|
190
|
+
table: pivotMeta.tableName,
|
|
191
|
+
schema: prop.targetMeta?.schema === "*"
|
|
192
|
+
? "*"
|
|
193
|
+
: this.driver.getSchemaName(pivotMeta, { schema }),
|
|
194
|
+
path: path.endsWith("[pivot]") ? path : `${path}[pivot]`,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
if (type === enums_1.JoinType.pivotJoin) {
|
|
198
|
+
return ret;
|
|
199
|
+
}
|
|
200
|
+
const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
|
|
201
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
|
|
202
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
|
|
203
|
+
const tmp = prop2.referencedTableName.split(".");
|
|
204
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`].schema ??=
|
|
205
|
+
tmp.length > 1 ? tmp[0] : undefined;
|
|
206
|
+
return ret;
|
|
207
|
+
}
|
|
208
|
+
processJoins(qb, joins, schema) {
|
|
209
|
+
Object.values(joins).forEach((join) => {
|
|
210
|
+
let table = join.table;
|
|
211
|
+
const method = join.type === enums_1.JoinType.pivotJoin ? "left join" : join.type;
|
|
212
|
+
const conditions = [];
|
|
213
|
+
const params = [];
|
|
214
|
+
schema = join.schema && join.schema !== "*" ? join.schema : schema;
|
|
215
|
+
if (schema) {
|
|
216
|
+
table = `${schema}.${table}`;
|
|
217
|
+
}
|
|
218
|
+
if (!join.subquery) {
|
|
219
|
+
join.primaryKeys.forEach((primaryKey, idx) => {
|
|
220
|
+
const right = `${join.alias}.${join.joinColumns[idx]}`;
|
|
221
|
+
if (join.prop.formula) {
|
|
222
|
+
const left = join.prop.formula(join.ownerAlias);
|
|
223
|
+
conditions.push(`${left} = ${this.knex.ref(right)}`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const left = `${join.ownerAlias}.${primaryKey}`;
|
|
227
|
+
conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (join.prop.targetMeta?.discriminatorValue &&
|
|
231
|
+
!join.path?.endsWith("[pivot]")) {
|
|
232
|
+
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
233
|
+
const alias = join.inverseAlias ?? join.alias;
|
|
234
|
+
join.cond[`${alias}.${typeProperty}`] =
|
|
235
|
+
join.prop.targetMeta.discriminatorValue;
|
|
236
|
+
}
|
|
237
|
+
for (const key of Object.keys(join.cond)) {
|
|
238
|
+
const hasPrefix = key.includes(".") ||
|
|
239
|
+
core_1.Utils.isOperator(key) ||
|
|
240
|
+
core_1.RawQueryFragment.isKnownFragment(key);
|
|
241
|
+
const newKey = hasPrefix ? key : `${join.alias}.${key}`;
|
|
242
|
+
const clause = this.processJoinClause(newKey, join.cond[key], join.alias, params);
|
|
243
|
+
/* istanbul ignore else */
|
|
244
|
+
if (clause !== "()") {
|
|
245
|
+
conditions.push(clause);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let sql = method + " ";
|
|
249
|
+
if (join.subquery) {
|
|
250
|
+
sql += `(${join.subquery})`;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
sql += this.knex.ref(table);
|
|
254
|
+
}
|
|
255
|
+
sql += ` as ${this.knex.ref(join.alias)}`;
|
|
256
|
+
if (conditions.length > 0) {
|
|
257
|
+
sql += ` on ${conditions.join(" and ")}`;
|
|
258
|
+
}
|
|
259
|
+
return qb.joinRaw(sql, params);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
processJoinClause(key, value, alias, params, operator = "$eq") {
|
|
263
|
+
if (core_1.Utils.isGroupOperator(key) && Array.isArray(value)) {
|
|
264
|
+
const parts = value.map((sub) => {
|
|
265
|
+
return this.wrapQueryGroup(Object.keys(sub).map((k) => this.processJoinClause(k, sub[k], alias, params)));
|
|
266
|
+
});
|
|
267
|
+
return this.wrapQueryGroup(parts, key);
|
|
268
|
+
}
|
|
269
|
+
if (this.isSimpleRegExp(value)) {
|
|
270
|
+
params.push(this.getRegExpParam(value));
|
|
271
|
+
return `${this.knex.ref(this.mapper(key))} like ?`;
|
|
272
|
+
}
|
|
273
|
+
if (value instanceof RegExp) {
|
|
274
|
+
value = this.platform.getRegExpValue(value);
|
|
275
|
+
}
|
|
276
|
+
if (core_1.Utils.isOperator(key, false) && core_1.Utils.isPlainObject(value)) {
|
|
277
|
+
const parts = Object.keys(value).map((k) => this.processJoinClause(k, value[k], alias, params, key));
|
|
278
|
+
return key === "$not"
|
|
279
|
+
? `not ${this.wrapQueryGroup(parts)}`
|
|
280
|
+
: this.wrapQueryGroup(parts);
|
|
281
|
+
}
|
|
282
|
+
if (core_1.Utils.isPlainObject(value) &&
|
|
283
|
+
Object.keys(value).every((k) => core_1.Utils.isOperator(k, false))) {
|
|
284
|
+
const parts = Object.keys(value).map((op) => this.processJoinClause(key, value[op], alias, params, op));
|
|
285
|
+
return this.wrapQueryGroup(parts);
|
|
286
|
+
}
|
|
287
|
+
operator = operator === "$not" ? "$eq" : operator;
|
|
288
|
+
if (value === null) {
|
|
289
|
+
return `${this.knex.ref(this.mapper(key))} is ${operator === "$ne" ? "not " : ""}null`;
|
|
290
|
+
}
|
|
291
|
+
if (operator === "$fulltext") {
|
|
292
|
+
const [fromAlias, fromField] = this.splitField(key);
|
|
293
|
+
const property = this.getProperty(fromField, fromAlias);
|
|
294
|
+
const query = this.knex
|
|
295
|
+
.raw(this.platform.getFullTextWhereClause(property), {
|
|
296
|
+
column: this.mapper(key),
|
|
297
|
+
query: this.knex.raw("?"),
|
|
298
|
+
})
|
|
299
|
+
.toSQL()
|
|
300
|
+
.toNative();
|
|
301
|
+
params.push(value);
|
|
302
|
+
return query.sql;
|
|
303
|
+
}
|
|
304
|
+
const replacement = this.getOperatorReplacement(operator, {
|
|
305
|
+
[operator]: value,
|
|
306
|
+
});
|
|
307
|
+
if (["$in", "$nin"].includes(operator) && Array.isArray(value)) {
|
|
308
|
+
params.push(...value);
|
|
309
|
+
return `${this.knex.ref(this.mapper(key))} ${replacement} (${value.map(() => "?").join(", ")})`;
|
|
310
|
+
}
|
|
311
|
+
if (operator === "$exists") {
|
|
312
|
+
value = null;
|
|
313
|
+
}
|
|
314
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(key);
|
|
315
|
+
if (rawField) {
|
|
316
|
+
let sql = rawField.sql.replaceAll(core_1.ALIAS_REPLACEMENT, alias);
|
|
317
|
+
params.push(...rawField.params);
|
|
318
|
+
params.push(...core_1.Utils.asArray(value));
|
|
319
|
+
if (core_1.Utils.asArray(value).length > 0) {
|
|
320
|
+
sql += " = ?";
|
|
321
|
+
}
|
|
322
|
+
return sql;
|
|
323
|
+
}
|
|
324
|
+
const sql = this.mapper(key);
|
|
325
|
+
if (value !== null) {
|
|
326
|
+
params.push(value);
|
|
327
|
+
}
|
|
328
|
+
return `${this.knex.ref(sql)} ${replacement} ${value === null ? "null" : "?"}`;
|
|
329
|
+
}
|
|
330
|
+
wrapQueryGroup(parts, operator = "$and") {
|
|
331
|
+
if (parts.length === 1) {
|
|
332
|
+
return parts[0];
|
|
333
|
+
}
|
|
334
|
+
return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
|
|
335
|
+
}
|
|
336
|
+
mapJoinColumns(type, join) {
|
|
337
|
+
if (join.prop &&
|
|
338
|
+
[core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(join.prop.kind)) {
|
|
339
|
+
return join.prop.fieldNames.map((fieldName, idx) => {
|
|
340
|
+
const columns = join.prop.owner
|
|
341
|
+
? join.joinColumns
|
|
342
|
+
: join.inverseJoinColumns;
|
|
343
|
+
return this.mapper(`${join.alias}.${columns[idx]}`, type, undefined, fieldName);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return [
|
|
347
|
+
...join.joinColumns.map((col) => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
|
|
348
|
+
...join.inverseJoinColumns.map((col) => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
isOneToOneInverse(field, meta) {
|
|
352
|
+
meta ??= this.metadata.find(this.entityName);
|
|
353
|
+
const prop = meta.properties[field.replace(/:ref$/, "")];
|
|
354
|
+
return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
355
|
+
}
|
|
356
|
+
getTableName(entityName) {
|
|
357
|
+
const meta = this.metadata.find(entityName);
|
|
358
|
+
return meta ? meta.collection : entityName;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Checks whether the RE can be rewritten to simple LIKE query
|
|
362
|
+
*/
|
|
363
|
+
isSimpleRegExp(re) {
|
|
364
|
+
if (!(re instanceof RegExp)) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (re.flags.includes("i")) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
// when including the opening bracket/paren we consider it complex
|
|
371
|
+
return !re.source.match(/[{[(]/);
|
|
372
|
+
}
|
|
373
|
+
getRegExpParam(re) {
|
|
374
|
+
const value = re.source
|
|
375
|
+
.replace(/\.\*/g, "%") // .* -> %
|
|
376
|
+
.replace(/\./g, "_") // . -> _
|
|
377
|
+
.replace(/\\_/g, ".") // \. -> .
|
|
378
|
+
.replace(/^\^/g, "") // remove ^ from start
|
|
379
|
+
.replace(/\$$/g, ""); // remove $ from end
|
|
380
|
+
if (re.source.startsWith("^") && re.source.endsWith("$")) {
|
|
381
|
+
return value;
|
|
382
|
+
}
|
|
383
|
+
if (re.source.startsWith("^")) {
|
|
384
|
+
return value + "%";
|
|
385
|
+
}
|
|
386
|
+
if (re.source.endsWith("$")) {
|
|
387
|
+
return "%" + value;
|
|
388
|
+
}
|
|
389
|
+
return `%${value}%`;
|
|
390
|
+
}
|
|
391
|
+
appendOnConflictClause(type, onConflict, qb) {
|
|
392
|
+
onConflict.forEach((item) => {
|
|
393
|
+
const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
|
|
394
|
+
core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
|
|
395
|
+
core_1.Utils.runIfNotEmpty(() => {
|
|
396
|
+
let mergeParam = item.merge;
|
|
397
|
+
if (core_1.Utils.isObject(item.merge)) {
|
|
398
|
+
mergeParam = {};
|
|
399
|
+
core_1.Utils.keys(item.merge).forEach((key) => {
|
|
400
|
+
const k = this.mapper(key, type);
|
|
401
|
+
mergeParam[k] = item.merge[key];
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(item.merge)) {
|
|
405
|
+
mergeParam = item.merge.map((key) => this.mapper(key, type));
|
|
406
|
+
}
|
|
407
|
+
const sub2 = sub.merge(mergeParam);
|
|
408
|
+
core_1.Utils.runIfNotEmpty(() => this.appendQueryCondition(type, item.where, sub2), item.where);
|
|
409
|
+
}, "merge" in item);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
appendQueryCondition(type, cond, qb, operator, method = "where") {
|
|
413
|
+
const m = operator === "$or" ? "orWhere" : "andWhere";
|
|
414
|
+
Object.keys(cond).forEach((k) => {
|
|
415
|
+
if (k === "$and" || k === "$or") {
|
|
416
|
+
if (operator) {
|
|
417
|
+
return qb[m]((inner) => this.appendGroupCondition(type, inner, k, method, cond[k]));
|
|
418
|
+
}
|
|
419
|
+
return this.appendGroupCondition(type, qb, k, method, cond[k]);
|
|
420
|
+
}
|
|
421
|
+
if (k === "$not") {
|
|
422
|
+
const m = operator === "$or" ? "orWhereNot" : "whereNot";
|
|
423
|
+
return qb[m]((inner) => this.appendQueryCondition(type, cond[k], inner));
|
|
424
|
+
}
|
|
425
|
+
this.appendQuerySubCondition(qb, type, method, cond, k, operator);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
appendQuerySubCondition(qb, type, method, cond, key, operator) {
|
|
429
|
+
const m = operator === "$or" ? "orWhere" : method;
|
|
430
|
+
if (cond[key] instanceof core_1.RawQueryFragment) {
|
|
431
|
+
cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
|
|
432
|
+
}
|
|
433
|
+
if (this.isSimpleRegExp(cond[key])) {
|
|
434
|
+
return void qb[m](this.mapper(key, type), "like", this.getRegExpParam(cond[key]));
|
|
435
|
+
}
|
|
436
|
+
if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
|
|
437
|
+
return this.processObjectSubCondition(cond, key, qb, method, m, type);
|
|
438
|
+
}
|
|
439
|
+
const op = cond[key] === null ? "is" : "=";
|
|
440
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
441
|
+
if (raw) {
|
|
442
|
+
const value = core_1.Utils.asArray(cond[key]);
|
|
443
|
+
if (value.length > 0) {
|
|
444
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
|
|
445
|
+
}
|
|
446
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params));
|
|
447
|
+
}
|
|
448
|
+
if (this.subQueries[key]) {
|
|
449
|
+
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
|
|
450
|
+
}
|
|
451
|
+
qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
|
|
452
|
+
}
|
|
453
|
+
processObjectSubCondition(cond, key, qb, method, m, type) {
|
|
454
|
+
let value = cond[key];
|
|
455
|
+
const size = core_1.Utils.getObjectKeysSize(value);
|
|
456
|
+
if (core_1.Utils.isPlainObject(value) && size === 0) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
|
|
460
|
+
if (size > 1) {
|
|
461
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(key);
|
|
462
|
+
const subCondition = Object.entries(value).map(([subKey, subValue]) => {
|
|
463
|
+
key = rawField?.clone().toString() ?? key;
|
|
464
|
+
return { [key]: { [subKey]: subValue } };
|
|
465
|
+
});
|
|
466
|
+
return subCondition.forEach((sub) => this.appendQueryCondition(type, sub, qb, "$and", method));
|
|
467
|
+
}
|
|
468
|
+
if (value instanceof RegExp) {
|
|
469
|
+
value = this.platform.getRegExpValue(value);
|
|
470
|
+
}
|
|
471
|
+
// operators
|
|
472
|
+
const op = Object.keys(core_1.QueryOperator).find((op) => op in value);
|
|
473
|
+
/* istanbul ignore next */
|
|
474
|
+
if (!op) {
|
|
475
|
+
throw new Error(`Invalid query condition: ${(0, util_1.inspect)(cond)}`);
|
|
476
|
+
}
|
|
477
|
+
const replacement = this.getOperatorReplacement(op, value);
|
|
478
|
+
const fields = core_1.Utils.splitPrimaryKeys(key);
|
|
479
|
+
if (fields.length > 1 &&
|
|
480
|
+
Array.isArray(value[op]) &&
|
|
481
|
+
!value[op].every((v) => Array.isArray(v))) {
|
|
482
|
+
const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0])
|
|
483
|
+
? fields.map((f) => value[op][0][f])
|
|
484
|
+
: value[op];
|
|
485
|
+
value[op] = this.knex.raw(`(${fields.map(() => "?").join(", ")})`, tmp);
|
|
486
|
+
}
|
|
487
|
+
if (this.subQueries[key]) {
|
|
488
|
+
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
|
|
489
|
+
}
|
|
490
|
+
if (op === "$fulltext") {
|
|
491
|
+
const [a, f] = this.splitField(key);
|
|
492
|
+
const prop = this.getProperty(f, a);
|
|
493
|
+
/* istanbul ignore next */
|
|
494
|
+
if (!prop) {
|
|
495
|
+
throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
|
|
496
|
+
}
|
|
497
|
+
qb[m](this.knex.raw(this.platform.getFullTextWhereClause(prop), {
|
|
498
|
+
column: this.mapper(key, type, undefined, null),
|
|
499
|
+
query: value[op],
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
const mappedKey = this.mapper(key, type, value[op], null);
|
|
504
|
+
qb[m](mappedKey, replacement, value[op]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
getOperatorReplacement(op, value) {
|
|
508
|
+
let replacement = core_1.QueryOperator[op];
|
|
509
|
+
if (op === "$exists") {
|
|
510
|
+
replacement = value[op] ? "is not" : "is";
|
|
511
|
+
value[op] = null;
|
|
512
|
+
}
|
|
513
|
+
if (value[op] === null && ["$eq", "$ne"].includes(op)) {
|
|
514
|
+
replacement = op === "$eq" ? "is" : "is not";
|
|
515
|
+
}
|
|
516
|
+
if (op === "$re") {
|
|
517
|
+
replacement = this.platform.getRegExpOperator(value[op], value.$flags);
|
|
518
|
+
}
|
|
519
|
+
return replacement;
|
|
520
|
+
}
|
|
521
|
+
getQueryOrder(type, orderBy, populate) {
|
|
522
|
+
if (Array.isArray(orderBy)) {
|
|
523
|
+
return orderBy.flatMap((o) => this.getQueryOrder(type, o, populate));
|
|
524
|
+
}
|
|
525
|
+
return this.getQueryOrderFromObject(type, orderBy, populate);
|
|
526
|
+
}
|
|
527
|
+
getQueryOrderFromObject(type, orderBy, populate) {
|
|
528
|
+
const ret = [];
|
|
529
|
+
for (const key of Object.keys(orderBy)) {
|
|
530
|
+
const direction = orderBy[key];
|
|
531
|
+
const order = core_1.Utils.isNumber(direction)
|
|
532
|
+
? core_1.QueryOrderNumeric[direction]
|
|
533
|
+
: direction;
|
|
534
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
535
|
+
if (raw) {
|
|
536
|
+
ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
for (const f of core_1.Utils.splitPrimaryKeys(key)) {
|
|
540
|
+
// eslint-disable-next-line prefer-const
|
|
541
|
+
let [alias, field] = this.splitField(f, true);
|
|
542
|
+
alias = populate[alias] || alias;
|
|
543
|
+
const prop = this.getProperty(field, alias);
|
|
544
|
+
const noPrefix = (prop && prop.persist === false && !prop.formula) ||
|
|
545
|
+
core_1.RawQueryFragment.isKnownFragment(f);
|
|
546
|
+
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
547
|
+
/* istanbul ignore next */
|
|
548
|
+
const rawColumn = core_1.Utils.isString(column)
|
|
549
|
+
? column
|
|
550
|
+
.split(".")
|
|
551
|
+
.map((e) => this.knex.ref(e))
|
|
552
|
+
.join(".")
|
|
553
|
+
: column;
|
|
554
|
+
const customOrder = prop?.customOrder;
|
|
555
|
+
let colPart = customOrder
|
|
556
|
+
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
557
|
+
: rawColumn;
|
|
558
|
+
if (core_1.Utils.isRawSql(colPart)) {
|
|
559
|
+
colPart = this.platform.formatQuery(colPart.sql, colPart.params);
|
|
560
|
+
}
|
|
561
|
+
if (Array.isArray(order)) {
|
|
562
|
+
order.forEach((part) => ret.push(...this.getQueryOrderFromObject(type, part, populate)));
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
ret.push(...this.platform.getOrderByExpression(colPart, order));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return ret;
|
|
570
|
+
}
|
|
571
|
+
finalize(type, qb, meta, data, returning) {
|
|
572
|
+
if (!meta || !data || !this.platform.usesReturningStatement()) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
// always respect explicit returning hint
|
|
576
|
+
if (returning && returning.length > 0) {
|
|
577
|
+
qb.returning(returning.map((field) => this.mapper(field, type)));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (type === enums_1.QueryType.INSERT) {
|
|
581
|
+
const returningProps = meta.hydrateProps
|
|
582
|
+
.filter((prop) => prop.returning ||
|
|
583
|
+
(prop.persist !== false &&
|
|
584
|
+
((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
585
|
+
.filter((prop) => !(prop.name in data));
|
|
586
|
+
if (returningProps.length > 0) {
|
|
587
|
+
qb.returning(core_1.Utils.flatten(returningProps.map((prop) => prop.fieldNames)));
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (type === enums_1.QueryType.UPDATE) {
|
|
592
|
+
const returningProps = meta.hydrateProps.filter((prop) => core_1.Utils.isRawSql(data[prop.name]));
|
|
593
|
+
if (returningProps.length > 0) {
|
|
594
|
+
qb.returning(returningProps.flatMap((prop) => {
|
|
595
|
+
if (prop.hasConvertToJSValueSQL) {
|
|
596
|
+
const sql = prop.customType.convertToJSValueSQL(prop.fieldNames[0], this.platform) +
|
|
597
|
+
" as " +
|
|
598
|
+
this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
599
|
+
return [this.knex.raw(sql)];
|
|
600
|
+
}
|
|
601
|
+
return prop.fieldNames;
|
|
602
|
+
}));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
splitField(field, greedyAlias = false) {
|
|
607
|
+
const parts = field.split(".");
|
|
608
|
+
const ref = parts[parts.length - 1].split(":")[1];
|
|
609
|
+
if (ref) {
|
|
610
|
+
parts[parts.length - 1] = parts[parts.length - 1].substring(0, parts[parts.length - 1].indexOf(":"));
|
|
611
|
+
}
|
|
612
|
+
if (parts.length === 1) {
|
|
613
|
+
return [this.alias, parts[0], ref];
|
|
614
|
+
}
|
|
615
|
+
if (greedyAlias) {
|
|
616
|
+
const fromField = parts.pop();
|
|
617
|
+
const fromAlias = parts.join(".");
|
|
618
|
+
return [fromAlias, fromField, ref];
|
|
619
|
+
}
|
|
620
|
+
const fromAlias = parts.shift();
|
|
621
|
+
const fromField = parts.join(".");
|
|
622
|
+
return [fromAlias, fromField, ref];
|
|
623
|
+
}
|
|
624
|
+
getLockSQL(qb, lockMode, lockTables = []) {
|
|
625
|
+
const meta = this.metadata.find(this.entityName);
|
|
626
|
+
if (lockMode === core_1.LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
|
|
627
|
+
throw core_1.OptimisticLockError.lockFailed(this.entityName);
|
|
628
|
+
}
|
|
629
|
+
switch (lockMode) {
|
|
630
|
+
case core_1.LockMode.PESSIMISTIC_READ:
|
|
631
|
+
return void qb.forShare(...lockTables);
|
|
632
|
+
case core_1.LockMode.PESSIMISTIC_WRITE:
|
|
633
|
+
return void qb.forUpdate(...lockTables);
|
|
634
|
+
case core_1.LockMode.PESSIMISTIC_PARTIAL_WRITE:
|
|
635
|
+
return void qb.forUpdate(...lockTables).skipLocked();
|
|
636
|
+
case core_1.LockMode.PESSIMISTIC_WRITE_OR_FAIL:
|
|
637
|
+
return void qb.forUpdate(...lockTables).noWait();
|
|
638
|
+
case core_1.LockMode.PESSIMISTIC_PARTIAL_READ:
|
|
639
|
+
return void qb.forShare(...lockTables).skipLocked();
|
|
640
|
+
case core_1.LockMode.PESSIMISTIC_READ_OR_FAIL:
|
|
641
|
+
return void qb.forShare(...lockTables).noWait();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
updateVersionProperty(qb, data) {
|
|
645
|
+
const meta = this.metadata.find(this.entityName);
|
|
646
|
+
if (!meta?.versionProperty || meta.versionProperty in data) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const versionProperty = meta.properties[meta.versionProperty];
|
|
650
|
+
let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + " + 1";
|
|
651
|
+
if (versionProperty.runtimeType === "Date") {
|
|
652
|
+
sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
653
|
+
}
|
|
654
|
+
qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
|
|
655
|
+
}
|
|
656
|
+
prefix(field, always = false, quote = false, idx) {
|
|
657
|
+
let ret;
|
|
658
|
+
if (!this.isPrefixed(field)) {
|
|
659
|
+
const alias = always
|
|
660
|
+
? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + "."
|
|
661
|
+
: "";
|
|
662
|
+
const fieldName = this.fieldName(field, this.alias, always, idx);
|
|
663
|
+
if (fieldName instanceof core_1.RawQueryFragment) {
|
|
664
|
+
return fieldName.sql;
|
|
665
|
+
}
|
|
666
|
+
ret = alias + fieldName;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
const [a, ...rest] = field.split(".");
|
|
670
|
+
const f = rest.join(".");
|
|
671
|
+
ret = a + "." + this.fieldName(f, a, always, idx);
|
|
672
|
+
}
|
|
673
|
+
if (quote) {
|
|
674
|
+
return this.platform.quoteIdentifier(ret);
|
|
675
|
+
}
|
|
676
|
+
return ret;
|
|
677
|
+
}
|
|
678
|
+
appendGroupCondition(type, qb, operator, method, subCondition) {
|
|
679
|
+
// single sub-condition can be ignored to reduce nesting of parens
|
|
680
|
+
if (subCondition.length === 1 || operator === "$and") {
|
|
681
|
+
return subCondition.forEach((sub) => this.appendQueryCondition(type, sub, qb, undefined, method));
|
|
682
|
+
}
|
|
683
|
+
qb[method]((outer) => subCondition.forEach((sub) => {
|
|
684
|
+
// skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
|
|
685
|
+
const keys = Object.keys(sub);
|
|
686
|
+
const val = sub[keys[0]];
|
|
687
|
+
const simple = !core_1.Utils.isPlainObject(val) ||
|
|
688
|
+
core_1.Utils.getObjectKeysSize(val) === 1 ||
|
|
689
|
+
Object.keys(val).every((k) => !core_1.Utils.isOperator(k));
|
|
690
|
+
if (keys.length === 1 && simple) {
|
|
691
|
+
return this.appendQueryCondition(type, sub, outer, operator);
|
|
692
|
+
}
|
|
693
|
+
outer.orWhere((inner) => this.appendQueryCondition(type, sub, inner));
|
|
694
|
+
}));
|
|
695
|
+
}
|
|
696
|
+
isPrefixed(field) {
|
|
697
|
+
return !!field.match(/[\w`"[\]]+\./);
|
|
698
|
+
}
|
|
699
|
+
fieldName(field, alias, always, idx = 0) {
|
|
700
|
+
const prop = this.getProperty(field, alias);
|
|
701
|
+
if (!prop) {
|
|
702
|
+
return field;
|
|
703
|
+
}
|
|
704
|
+
if (prop.fieldNameRaw) {
|
|
705
|
+
if (!always) {
|
|
706
|
+
return (0, core_1.raw)(prop.fieldNameRaw
|
|
707
|
+
.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + "\\.?", "g"), "")
|
|
708
|
+
.replace(this.platform.quoteIdentifier("") + ".", ""));
|
|
709
|
+
}
|
|
710
|
+
if (alias) {
|
|
711
|
+
return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, "g"), alias));
|
|
712
|
+
}
|
|
713
|
+
/* istanbul ignore next */
|
|
714
|
+
return (0, core_1.raw)(prop.fieldNameRaw);
|
|
715
|
+
}
|
|
716
|
+
/* istanbul ignore next */
|
|
717
|
+
return prop.fieldNames?.[idx] ?? field;
|
|
718
|
+
}
|
|
719
|
+
getProperty(field, alias) {
|
|
720
|
+
const entityName = this.aliasMap[alias]?.entityName || this.entityName;
|
|
721
|
+
const meta = this.metadata.find(entityName);
|
|
722
|
+
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
723
|
+
if (alias && meta) {
|
|
724
|
+
const prop = meta.properties[alias];
|
|
725
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
726
|
+
// we want to select the full object property so hydration works as expected
|
|
727
|
+
if (prop.object) {
|
|
728
|
+
return prop;
|
|
729
|
+
}
|
|
730
|
+
const parts = field.split(".");
|
|
731
|
+
const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
|
|
732
|
+
return nest(prop);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (meta) {
|
|
736
|
+
if (meta.properties[field]) {
|
|
737
|
+
return meta.properties[field];
|
|
738
|
+
}
|
|
739
|
+
return meta.relations.find((prop) => prop.fieldNames?.some((name) => field === name));
|
|
740
|
+
}
|
|
741
|
+
return undefined;
|
|
742
|
+
}
|
|
743
|
+
isTableNameAliasRequired(type) {
|
|
744
|
+
return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
exports.QueryBuilderHelper = QueryBuilderHelper;
|