@mikro-orm/knex 6.0.0-dev.9 → 6.0.0-dev.91
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 +5 -2
- package/AbstractSqlConnection.js +25 -17
- package/AbstractSqlDriver.d.ts +19 -18
- package/AbstractSqlDriver.js +171 -70
- package/AbstractSqlPlatform.d.ts +1 -0
- package/AbstractSqlPlatform.js +26 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -5
- package/SqlEntityManager.js +5 -9
- package/SqlEntityRepository.d.ts +6 -4
- package/SqlEntityRepository.js +10 -8
- package/index.mjs +20 -6
- package/package.json +7 -7
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +8 -8
- package/query/CriteriaNode.js +9 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +10 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +12 -8
- package/query/QueryBuilder.d.ts +27 -16
- package/query/QueryBuilder.js +250 -91
- package/query/QueryBuilderHelper.d.ts +6 -9
- package/query/QueryBuilderHelper.js +112 -96
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +9 -6
- package/query/enums.js +1 -1
- package/schema/DatabaseSchema.d.ts +4 -1
- package/schema/DatabaseSchema.js +22 -6
- package/schema/DatabaseTable.d.ts +2 -1
- package/schema/DatabaseTable.js +25 -24
- package/schema/SchemaComparator.js +9 -2
- package/schema/SchemaHelper.d.ts +5 -3
- package/schema/SchemaHelper.js +10 -4
- package/schema/SqlSchemaGenerator.d.ts +3 -5
- package/schema/SqlSchemaGenerator.js +41 -20
- package/typings.d.ts +10 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
|
-
import type { Dictionary, EntityData, EntityMetadata, EntityProperty, FlatQueryOrderMap, QBFilterQuery } from '@mikro-orm/core';
|
|
2
|
+
import type { Dictionary, EntityData, EntityKey, EntityMetadata, EntityProperty, FlatQueryOrderMap, QBFilterQuery } from '@mikro-orm/core';
|
|
3
3
|
import { LockMode } from '@mikro-orm/core';
|
|
4
4
|
import { QueryType } from './enums';
|
|
5
5
|
import type { Field, JoinOptions } from '../typings';
|
|
@@ -20,10 +20,9 @@ export declare class QueryBuilderHelper {
|
|
|
20
20
|
mapper(field: string | Knex.Raw, type?: QueryType): string;
|
|
21
21
|
mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null): string;
|
|
22
22
|
processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
|
|
23
|
-
joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
|
|
24
|
-
joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
|
|
25
|
-
joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string): Dictionary<JoinOptions>;
|
|
26
|
-
joinPivotTable(field: string, prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
|
|
23
|
+
joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
|
|
24
|
+
joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
|
|
25
|
+
joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string, schema?: string): Dictionary<JoinOptions>;
|
|
27
26
|
processJoins(qb: Knex.QueryBuilder, joins: Dictionary<JoinOptions>, schema?: string): void;
|
|
28
27
|
private processJoinClause;
|
|
29
28
|
private wrapQueryGroup;
|
|
@@ -43,16 +42,14 @@ export declare class QueryBuilderHelper {
|
|
|
43
42
|
}[], qb: Knex.QueryBuilder): void;
|
|
44
43
|
appendQueryCondition(type: QueryType, cond: any, qb: Knex.QueryBuilder, operator?: '$and' | '$or', method?: 'where' | 'having'): void;
|
|
45
44
|
private appendQuerySubCondition;
|
|
46
|
-
private processCustomExpression;
|
|
47
45
|
private processObjectSubCondition;
|
|
48
46
|
private getOperatorReplacement;
|
|
49
47
|
getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string;
|
|
50
48
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string;
|
|
51
|
-
finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata): void;
|
|
52
|
-
splitField(field:
|
|
49
|
+
finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
|
|
50
|
+
splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>];
|
|
53
51
|
getLockSQL(qb: Knex.QueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
|
|
54
52
|
updateVersionProperty(qb: Knex.QueryBuilder, data: Dictionary): void;
|
|
55
|
-
static isCustomExpression(field: string, hasAlias?: boolean): boolean;
|
|
56
53
|
private prefix;
|
|
57
54
|
private appendGroupCondition;
|
|
58
55
|
private isPrefixed;
|
|
@@ -19,6 +19,10 @@ class QueryBuilderHelper {
|
|
|
19
19
|
this.metadata = this.driver.getMetadata();
|
|
20
20
|
}
|
|
21
21
|
mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
|
|
22
|
+
if (core_1.Utils.isRawSql(field)) {
|
|
23
|
+
return this.knex.raw(field.sql, field.params);
|
|
24
|
+
}
|
|
25
|
+
/* istanbul ignore next */
|
|
22
26
|
if (typeof field !== 'string') {
|
|
23
27
|
return field;
|
|
24
28
|
}
|
|
@@ -36,12 +40,26 @@ class QueryBuilderHelper {
|
|
|
36
40
|
parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
// flatten the value if we see we are expanding nested composite key
|
|
44
|
+
// hackish, but cleaner solution would require quite a lot of refactoring
|
|
45
|
+
if (fields.length !== parts.length && Array.isArray(value)) {
|
|
46
|
+
value.forEach(row => {
|
|
47
|
+
if (Array.isArray(row)) {
|
|
48
|
+
const tmp = core_1.Utils.flatten(row);
|
|
49
|
+
row.length = 0;
|
|
50
|
+
row.push(...tmp);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
39
54
|
return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
57
|
+
if (rawField) {
|
|
58
|
+
return this.knex.raw(rawField.sql, rawField.params);
|
|
59
|
+
}
|
|
43
60
|
const [a, f] = this.splitField(field);
|
|
44
61
|
const prop = this.getProperty(f, a);
|
|
62
|
+
let ret = field;
|
|
45
63
|
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
46
64
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
47
65
|
return this.alias + '.' + prop.fieldNames[0];
|
|
@@ -56,7 +74,7 @@ class QueryBuilderHelper {
|
|
|
56
74
|
const as = alias === null ? '' : ` as ${aliased}`;
|
|
57
75
|
return this.knex.raw(`${prop.formula(alias2)}${as}`);
|
|
58
76
|
}
|
|
59
|
-
if (prop?.
|
|
77
|
+
if (prop?.hasConvertToJSValueSQL) {
|
|
60
78
|
const prefixed = this.prefix(field, isTableNameAliasRequired, true);
|
|
61
79
|
const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
62
80
|
if (alias === null) {
|
|
@@ -65,15 +83,12 @@ class QueryBuilderHelper {
|
|
|
65
83
|
return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
|
|
66
84
|
}
|
|
67
85
|
// do not wrap custom expressions
|
|
68
|
-
if (!
|
|
86
|
+
if (!rawField) {
|
|
69
87
|
ret = this.prefix(field);
|
|
70
88
|
}
|
|
71
89
|
if (alias) {
|
|
72
90
|
ret += ' as ' + alias;
|
|
73
91
|
}
|
|
74
|
-
if (customExpression) {
|
|
75
|
-
return this.knex.raw(ret, value);
|
|
76
|
-
}
|
|
77
92
|
if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
|
|
78
93
|
return ret;
|
|
79
94
|
}
|
|
@@ -91,28 +106,28 @@ class QueryBuilderHelper {
|
|
|
91
106
|
}
|
|
92
107
|
return data;
|
|
93
108
|
}
|
|
94
|
-
joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
|
|
109
|
+
joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
95
110
|
const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
|
|
96
111
|
const table = this.getTableName(prop.type);
|
|
97
|
-
const schema = this.driver.getSchemaName(prop.targetMeta);
|
|
98
112
|
const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
|
|
99
113
|
const inverseJoinColumns = prop.referencedColumnNames;
|
|
100
114
|
const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
|
|
101
115
|
return {
|
|
102
|
-
prop, type, cond, ownerAlias, alias, table,
|
|
116
|
+
prop, type, cond, ownerAlias, alias, table,
|
|
103
117
|
joinColumns, inverseJoinColumns, primaryKeys,
|
|
118
|
+
schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
104
119
|
};
|
|
105
120
|
}
|
|
106
|
-
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
|
|
121
|
+
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
107
122
|
return {
|
|
108
123
|
prop, type, cond, ownerAlias, alias,
|
|
109
124
|
table: this.getTableName(prop.type),
|
|
110
|
-
schema: this.driver.getSchemaName(prop.targetMeta),
|
|
125
|
+
schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
111
126
|
joinColumns: prop.referencedColumnNames,
|
|
112
127
|
primaryKeys: prop.fieldNames,
|
|
113
128
|
};
|
|
114
129
|
}
|
|
115
|
-
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
|
|
130
|
+
joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
|
|
116
131
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
117
132
|
const ret = {
|
|
118
133
|
[`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
|
|
@@ -123,7 +138,7 @@ class QueryBuilderHelper {
|
|
|
123
138
|
inverseJoinColumns: prop.inverseJoinColumns,
|
|
124
139
|
primaryKeys: prop.referencedColumnNames,
|
|
125
140
|
table: pivotMeta.tableName,
|
|
126
|
-
schema: this.driver.getSchemaName(pivotMeta),
|
|
141
|
+
schema: this.driver.getSchemaName(pivotMeta, { schema }),
|
|
127
142
|
path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
|
|
128
143
|
},
|
|
129
144
|
};
|
|
@@ -131,22 +146,10 @@ class QueryBuilderHelper {
|
|
|
131
146
|
return ret;
|
|
132
147
|
}
|
|
133
148
|
const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
|
|
134
|
-
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
|
|
149
|
+
ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, undefined, schema);
|
|
135
150
|
ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
|
|
136
151
|
return ret;
|
|
137
152
|
}
|
|
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
153
|
processJoins(qb, joins, schema) {
|
|
151
154
|
Object.values(joins).forEach(join => {
|
|
152
155
|
let table = `${join.table} as ${join.alias}`;
|
|
@@ -162,6 +165,11 @@ class QueryBuilderHelper {
|
|
|
162
165
|
const right = `${join.alias}.${join.joinColumns[idx]}`;
|
|
163
166
|
conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
|
|
164
167
|
});
|
|
168
|
+
if (join.prop.targetMeta.discriminatorValue && !join.path?.endsWith('[pivot]')) {
|
|
169
|
+
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
170
|
+
const alias = join.inverseAlias ?? join.alias;
|
|
171
|
+
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
172
|
+
}
|
|
165
173
|
Object.keys(join.cond).forEach(key => {
|
|
166
174
|
conditions.push(this.processJoinClause(key, join.cond[key], params));
|
|
167
175
|
});
|
|
@@ -212,20 +220,14 @@ class QueryBuilderHelper {
|
|
|
212
220
|
if (operator === '$exists') {
|
|
213
221
|
value = null;
|
|
214
222
|
}
|
|
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 + ' = ?';
|
|
223
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(key);
|
|
224
|
+
if (rawField) {
|
|
225
|
+
let sql = rawField.sql;
|
|
226
|
+
params.push(...rawField.params);
|
|
227
|
+
params.push(...core_1.Utils.asArray(value));
|
|
228
|
+
if (core_1.Utils.asArray(value).length > 0) {
|
|
229
|
+
sql += ' = ?';
|
|
227
230
|
}
|
|
228
|
-
params.push(...bindings);
|
|
229
231
|
return sql;
|
|
230
232
|
}
|
|
231
233
|
const sql = this.mapper(key);
|
|
@@ -241,7 +243,7 @@ class QueryBuilderHelper {
|
|
|
241
243
|
return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
|
|
242
244
|
}
|
|
243
245
|
mapJoinColumns(type, join) {
|
|
244
|
-
if (join.prop && join.prop.
|
|
246
|
+
if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
|
|
245
247
|
return join.prop.fieldNames.map((fieldName, idx) => {
|
|
246
248
|
return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
|
|
247
249
|
});
|
|
@@ -254,7 +256,7 @@ class QueryBuilderHelper {
|
|
|
254
256
|
isOneToOneInverse(field) {
|
|
255
257
|
const meta = this.metadata.find(this.entityName);
|
|
256
258
|
const prop = meta.properties[field];
|
|
257
|
-
return prop && prop.
|
|
259
|
+
return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
258
260
|
}
|
|
259
261
|
getTableName(entityName) {
|
|
260
262
|
const meta = this.metadata.find(entityName);
|
|
@@ -293,13 +295,13 @@ class QueryBuilderHelper {
|
|
|
293
295
|
}
|
|
294
296
|
appendOnConflictClause(type, onConflict, qb) {
|
|
295
297
|
onConflict.forEach(item => {
|
|
296
|
-
const sub = qb.onConflict(item.fields);
|
|
298
|
+
const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
|
|
297
299
|
core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
|
|
298
300
|
core_1.Utils.runIfNotEmpty(() => {
|
|
299
301
|
let mergeParam = item.merge;
|
|
300
302
|
if (core_1.Utils.isObject(item.merge)) {
|
|
301
303
|
mergeParam = {};
|
|
302
|
-
|
|
304
|
+
core_1.Utils.keys(item.merge).forEach(key => {
|
|
303
305
|
const k = this.mapper(key, type);
|
|
304
306
|
mergeParam[k] = item.merge[key];
|
|
305
307
|
});
|
|
@@ -330,34 +332,29 @@ class QueryBuilderHelper {
|
|
|
330
332
|
}
|
|
331
333
|
appendQuerySubCondition(qb, type, method, cond, key, operator) {
|
|
332
334
|
const m = operator === '$or' ? 'orWhere' : method;
|
|
335
|
+
if (cond[key] instanceof core_1.RawQueryFragment) {
|
|
336
|
+
cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
|
|
337
|
+
}
|
|
333
338
|
if (this.isSimpleRegExp(cond[key])) {
|
|
334
339
|
return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
|
|
335
340
|
}
|
|
336
341
|
if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
|
|
337
342
|
return this.processObjectSubCondition(cond, key, qb, method, m, type);
|
|
338
343
|
}
|
|
339
|
-
if (QueryBuilderHelper.isCustomExpression(key)) {
|
|
340
|
-
return this.processCustomExpression(qb, m, key, cond, type);
|
|
341
|
-
}
|
|
342
344
|
const op = cond[key] === null ? 'is' : '=';
|
|
345
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
346
|
+
if (raw) {
|
|
347
|
+
const value = core_1.Utils.asArray(cond[key]);
|
|
348
|
+
if (value.length > 0) {
|
|
349
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
|
|
350
|
+
}
|
|
351
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params));
|
|
352
|
+
}
|
|
343
353
|
if (this.subQueries[key]) {
|
|
344
354
|
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
|
|
345
355
|
}
|
|
346
356
|
qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
|
|
347
357
|
}
|
|
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
358
|
processObjectSubCondition(cond, key, qb, method, m, type) {
|
|
362
359
|
// grouped condition for one field
|
|
363
360
|
let value = cond[key];
|
|
@@ -394,7 +391,8 @@ class QueryBuilderHelper {
|
|
|
394
391
|
}));
|
|
395
392
|
}
|
|
396
393
|
else {
|
|
397
|
-
|
|
394
|
+
const mappedKey = this.mapper(key, type, value[op], null);
|
|
395
|
+
qb[m](mappedKey, replacement, value[op]);
|
|
398
396
|
}
|
|
399
397
|
}
|
|
400
398
|
getOperatorReplacement(op, value) {
|
|
@@ -422,36 +420,58 @@ class QueryBuilderHelper {
|
|
|
422
420
|
}
|
|
423
421
|
getQueryOrderFromObject(type, orderBy, populate) {
|
|
424
422
|
const ret = [];
|
|
425
|
-
Object.keys(orderBy).forEach(
|
|
426
|
-
const direction = orderBy[
|
|
423
|
+
Object.keys(orderBy).forEach(key => {
|
|
424
|
+
const direction = orderBy[key];
|
|
427
425
|
const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
427
|
+
if (raw) {
|
|
428
|
+
ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
|
|
430
429
|
return;
|
|
431
430
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const prop = this.getProperty(
|
|
437
|
-
const noPrefix = (prop && prop.persist === false && !prop.formula) ||
|
|
438
|
-
const column = this.mapper(noPrefix ?
|
|
431
|
+
core_1.Utils.splitPrimaryKeys(key).forEach(f => {
|
|
432
|
+
// eslint-disable-next-line prefer-const
|
|
433
|
+
let [alias, field] = this.splitField(f, true);
|
|
434
|
+
alias = populate[alias] || alias;
|
|
435
|
+
const prop = this.getProperty(field, alias);
|
|
436
|
+
const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
|
|
437
|
+
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
439
438
|
/* istanbul ignore next */
|
|
440
439
|
const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
|
|
441
440
|
const customOrder = prop?.customOrder;
|
|
442
|
-
|
|
441
|
+
let colPart = customOrder
|
|
443
442
|
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
444
443
|
: rawColumn;
|
|
444
|
+
if (core_1.Utils.isRawSql(colPart)) {
|
|
445
|
+
colPart = this.platform.formatQuery(colPart.sql, colPart.params);
|
|
446
|
+
}
|
|
445
447
|
ret.push(`${colPart} ${order.toLowerCase()}`);
|
|
446
448
|
});
|
|
447
449
|
});
|
|
448
450
|
return ret.join(', ');
|
|
449
451
|
}
|
|
450
|
-
finalize(type, qb, meta) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
finalize(type, qb, meta, data, returning) {
|
|
453
|
+
if (!meta || !data || !this.platform.usesReturningStatement()) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// always respect explicit returning hint
|
|
457
|
+
if (returning && returning.length > 0) {
|
|
458
|
+
qb.returning(returning.map(field => this.mapper(field, type)));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (type === enums_1.QueryType.INSERT) {
|
|
462
|
+
const returningProps = meta.hydrateProps
|
|
463
|
+
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
464
|
+
.filter(prop => !(prop.name in data));
|
|
465
|
+
if (returningProps.length > 0) {
|
|
466
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (type === enums_1.QueryType.UPDATE) {
|
|
471
|
+
const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
|
|
472
|
+
if (returningProps.length > 0) {
|
|
473
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
474
|
+
}
|
|
455
475
|
}
|
|
456
476
|
}
|
|
457
477
|
splitField(field, greedyAlias = false) {
|
|
@@ -489,23 +509,18 @@ class QueryBuilderHelper {
|
|
|
489
509
|
}
|
|
490
510
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
491
511
|
let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
|
|
492
|
-
if (versionProperty.
|
|
512
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
493
513
|
sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
494
514
|
}
|
|
495
515
|
qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
|
|
496
516
|
}
|
|
497
|
-
static isCustomExpression(field, hasAlias = false) {
|
|
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
517
|
prefix(field, always = false, quote = false) {
|
|
503
518
|
let ret;
|
|
504
519
|
if (!this.isPrefixed(field)) {
|
|
505
520
|
const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
|
|
506
521
|
const fieldName = this.fieldName(field, this.alias, always);
|
|
507
|
-
if (
|
|
508
|
-
return fieldName;
|
|
522
|
+
if (fieldName instanceof core_1.RawQueryFragment) {
|
|
523
|
+
return fieldName.sql;
|
|
509
524
|
}
|
|
510
525
|
ret = alias + fieldName;
|
|
511
526
|
}
|
|
@@ -545,15 +560,15 @@ class QueryBuilderHelper {
|
|
|
545
560
|
}
|
|
546
561
|
if (prop.fieldNameRaw) {
|
|
547
562
|
if (!always) {
|
|
548
|
-
return prop.fieldNameRaw
|
|
549
|
-
.replace(
|
|
550
|
-
.replace(this.platform.quoteIdentifier('') + '.', '');
|
|
563
|
+
return (0, core_1.raw)(prop.fieldNameRaw
|
|
564
|
+
.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
|
|
565
|
+
.replace(this.platform.quoteIdentifier('') + '.', ''));
|
|
551
566
|
}
|
|
552
567
|
if (alias) {
|
|
553
|
-
return prop.fieldNameRaw.replace(
|
|
568
|
+
return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
|
|
554
569
|
}
|
|
555
570
|
/* istanbul ignore next */
|
|
556
|
-
return prop.fieldNameRaw;
|
|
571
|
+
return (0, core_1.raw)(prop.fieldNameRaw);
|
|
557
572
|
}
|
|
558
573
|
/* istanbul ignore next */
|
|
559
574
|
return prop.fieldNames?.[0] ?? field;
|
|
@@ -564,7 +579,7 @@ class QueryBuilderHelper {
|
|
|
564
579
|
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
565
580
|
if (alias && meta) {
|
|
566
581
|
const prop = meta.properties[alias];
|
|
567
|
-
if (prop?.
|
|
582
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
568
583
|
// we want to select the full object property so hydration works as expected
|
|
569
584
|
if (prop.object) {
|
|
570
585
|
return prop;
|
|
@@ -596,17 +611,18 @@ class QueryBuilderHelper {
|
|
|
596
611
|
return;
|
|
597
612
|
}
|
|
598
613
|
if (prop.joinColumns && Array.isArray(data[k])) {
|
|
599
|
-
const copy = data[k];
|
|
614
|
+
const copy = core_1.Utils.flatten(data[k]);
|
|
600
615
|
delete data[k];
|
|
601
616
|
prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
|
|
602
617
|
return;
|
|
603
618
|
}
|
|
604
619
|
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' });
|
|
620
|
+
data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
|
|
606
621
|
}
|
|
607
|
-
if (prop.
|
|
622
|
+
if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
|
|
608
623
|
const quoted = this.platform.quoteValue(data[k]);
|
|
609
|
-
|
|
624
|
+
const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
|
|
625
|
+
data[k] = this.knex.raw(sql.replace(/\?/, '\\?'));
|
|
610
626
|
}
|
|
611
627
|
if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
|
|
612
628
|
data[k] = JSON.stringify(data[k]);
|
|
@@ -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
|
@@ -10,14 +10,17 @@ export declare class DatabaseSchema {
|
|
|
10
10
|
readonly name: string;
|
|
11
11
|
private tables;
|
|
12
12
|
private namespaces;
|
|
13
|
+
private nativeEnums;
|
|
13
14
|
constructor(platform: AbstractSqlPlatform, name: string);
|
|
14
15
|
addTable(name: string, schema: string | undefined | null, comment?: string): DatabaseTable;
|
|
15
16
|
getTables(): DatabaseTable[];
|
|
16
17
|
getTable(name: string): DatabaseTable | undefined;
|
|
17
18
|
hasTable(name: string): boolean;
|
|
19
|
+
setNativeEnums(nativeEnums: Dictionary<unknown[]>): void;
|
|
20
|
+
getNativeEnums(): Dictionary<unknown[]>;
|
|
18
21
|
hasNamespace(namespace: string): boolean;
|
|
19
22
|
getNamespaces(): string[];
|
|
20
|
-
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): Promise<DatabaseSchema>;
|
|
23
|
+
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[]): Promise<DatabaseSchema>;
|
|
21
24
|
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): DatabaseSchema;
|
|
22
25
|
private static getSchemaName;
|
|
23
26
|
private static shouldHaveColumn;
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -12,10 +12,12 @@ class DatabaseSchema {
|
|
|
12
12
|
this.name = name;
|
|
13
13
|
this.tables = [];
|
|
14
14
|
this.namespaces = new Set();
|
|
15
|
+
this.nativeEnums = {}; // for postgres
|
|
15
16
|
}
|
|
16
17
|
addTable(name, schema, comment) {
|
|
17
18
|
const namespaceName = schema ?? this.name;
|
|
18
19
|
const table = new DatabaseTable_1.DatabaseTable(this.platform, name, namespaceName);
|
|
20
|
+
table.nativeEnums = this.nativeEnums;
|
|
19
21
|
table.comment = comment;
|
|
20
22
|
this.tables.push(table);
|
|
21
23
|
if (namespaceName != null) {
|
|
@@ -32,24 +34,38 @@ class DatabaseSchema {
|
|
|
32
34
|
hasTable(name) {
|
|
33
35
|
return !!this.getTable(name);
|
|
34
36
|
}
|
|
37
|
+
setNativeEnums(nativeEnums) {
|
|
38
|
+
this.nativeEnums = nativeEnums;
|
|
39
|
+
this.tables.forEach(t => t.nativeEnums = nativeEnums);
|
|
40
|
+
}
|
|
41
|
+
getNativeEnums() {
|
|
42
|
+
return this.nativeEnums;
|
|
43
|
+
}
|
|
35
44
|
hasNamespace(namespace) {
|
|
36
45
|
return this.namespaces.has(namespace);
|
|
37
46
|
}
|
|
38
47
|
getNamespaces() {
|
|
39
48
|
return [...this.namespaces];
|
|
40
49
|
}
|
|
41
|
-
static async create(connection, platform, config, schemaName) {
|
|
42
|
-
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
50
|
+
static async create(connection, platform, config, schemaName, schemas) {
|
|
51
|
+
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
|
|
43
52
|
const allTables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
|
|
44
53
|
const parts = config.get('migrations').tableName.split('.');
|
|
45
54
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
46
55
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
47
56
|
const tables = allTables.filter(t => t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName));
|
|
48
|
-
await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables);
|
|
57
|
+
await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
49
58
|
return schema;
|
|
50
59
|
}
|
|
51
60
|
static fromMetadata(metadata, platform, config, schemaName) {
|
|
52
61
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
62
|
+
const nativeEnums = {};
|
|
63
|
+
for (const meta of metadata) {
|
|
64
|
+
meta.props
|
|
65
|
+
.filter(prop => prop.nativeEnumName)
|
|
66
|
+
.forEach(prop => nativeEnums[prop.nativeEnumName] = prop.items?.map(val => '' + val) ?? []);
|
|
67
|
+
}
|
|
68
|
+
schema.setNativeEnums(nativeEnums);
|
|
53
69
|
for (const meta of metadata) {
|
|
54
70
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
55
71
|
table.comment = meta.comment;
|
|
@@ -78,15 +94,15 @@ class DatabaseSchema {
|
|
|
78
94
|
if (prop.persist === false || !prop.fieldNames) {
|
|
79
95
|
return false;
|
|
80
96
|
}
|
|
81
|
-
if (meta.pivotTable || (core_1.
|
|
97
|
+
if (meta.pivotTable || (core_1.ReferenceKind.EMBEDDED && prop.object)) {
|
|
82
98
|
return true;
|
|
83
99
|
}
|
|
84
100
|
const getRootProperty = (prop) => prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop;
|
|
85
101
|
const rootProp = getRootProperty(prop);
|
|
86
|
-
if (rootProp.
|
|
102
|
+
if (rootProp.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
87
103
|
return prop === rootProp || !rootProp.object;
|
|
88
104
|
}
|
|
89
|
-
return [core_1.
|
|
105
|
+
return [core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && prop.owner);
|
|
90
106
|
}
|
|
91
107
|
toJSON() {
|
|
92
108
|
const { platform, namespaces, ...rest } = this;
|
|
@@ -13,6 +13,7 @@ export declare class DatabaseTable {
|
|
|
13
13
|
private indexes;
|
|
14
14
|
private checks;
|
|
15
15
|
private foreignKeys;
|
|
16
|
+
nativeEnums: Dictionary<unknown[]>;
|
|
16
17
|
comment?: string;
|
|
17
18
|
constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
|
|
18
19
|
getColumns(): Column[];
|
|
@@ -37,7 +38,7 @@ export declare class DatabaseTable {
|
|
|
37
38
|
getPrimaryKey(): IndexDef | undefined;
|
|
38
39
|
hasPrimaryKey(): boolean;
|
|
39
40
|
private getPropertyDeclaration;
|
|
40
|
-
private
|
|
41
|
+
private getReferenceKind;
|
|
41
42
|
private getPropertyName;
|
|
42
43
|
private getPropertyType;
|
|
43
44
|
private getPropertyDefaultValue;
|