-
-
Notifications
You must be signed in to change notification settings - Fork 496
/
CriteriaNode.ts
123 lines (96 loc) · 4.45 KB
/
CriteriaNode.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { inspect } from 'util';
import { EntityProperty, MetadataStorage, ReferenceType, Utils } from '@mikro-orm/core';
import { QueryBuilder } from './QueryBuilder';
import { ObjectCriteriaNode, ScalarCriteriaNode, ArrayCriteriaNode, QueryBuilderHelper } from './internal';
/**
* Helper for working with deeply nested where/orderBy/having criteria. Uses composite pattern to build tree from the payload.
* Auto-joins relations and converts payload from { books: { publisher: { name: '...' } } } to { 'publisher_alias.name': '...' }
*/
export class CriteriaNode {
payload: any;
prop?: EntityProperty;
constructor(protected readonly metadata: MetadataStorage,
readonly entityName: string,
readonly parent?: CriteriaNode,
readonly key?: string,
validate = true) {
const meta = parent && metadata.get(parent.entityName, false, false);
if (meta && key) {
Utils.splitPrimaryKeys(key).forEach(k => {
this.prop = Object.values(meta.properties).find(prop => prop.name === k || (prop.fieldNames || []).includes(k));
if (validate && !this.prop && !k.includes('.') && !Utils.isOperator(k) && !QueryBuilderHelper.isCustomExpression(k)) {
throw new Error(`Trying to query by not existing property ${entityName}.${k}`);
}
});
}
}
static create(metadata: MetadataStorage, entityName: string, payload: any, parent?: CriteriaNode, key?: string): CriteriaNode {
const customExpression = QueryBuilderHelper.isCustomExpression(key || '');
const scalar = Utils.isPrimaryKey(payload) || payload instanceof RegExp || payload instanceof Date || customExpression;
if (Array.isArray(payload) && !scalar) {
return ArrayCriteriaNode.create(metadata, entityName, payload, parent, key);
}
if (Utils.isPlainObject(payload) && !scalar) {
return ObjectCriteriaNode.create(metadata, entityName, payload, parent, key);
}
return ScalarCriteriaNode.create(metadata, entityName, payload, parent, key);
}
process(qb: QueryBuilder, alias?: string): any {
return this.payload;
}
shouldInline(payload: any): boolean {
return false;
}
willAutoJoin(qb: QueryBuilder, alias?: string) {
return false;
}
shouldRename(payload: any): boolean {
const type = this.prop ? this.prop.reference : null;
const composite = this.prop && this.prop.joinColumns ? this.prop.joinColumns.length > 1 : false;
const customExpression = QueryBuilderHelper.isCustomExpression(this.key!);
const scalar = Utils.isPrimaryKey(payload) || payload instanceof RegExp || payload instanceof Date || customExpression;
const operator = Utils.isObject(payload) && Object.keys(payload).every(k => Utils.isOperator(k, false));
if (composite) {
return true;
}
switch (type) {
case ReferenceType.MANY_TO_ONE: return false;
case ReferenceType.ONE_TO_ONE: return !this.prop!.owner && !(this.parent && this.parent.parent);
case ReferenceType.ONE_TO_MANY: return scalar || operator;
case ReferenceType.MANY_TO_MANY: return scalar || operator;
default: return false;
}
}
renameFieldToPK(qb: QueryBuilder): string {
if (this.prop!.reference === ReferenceType.MANY_TO_MANY) {
const pivotTable = this.prop!.pivotTable;
const alias = qb.getAliasForEntity(pivotTable, this);
return Utils.getPrimaryKeyHash(this.prop!.inverseJoinColumns.map(col => `${alias}.${col}`));
}
if (this.prop!.joinColumns.length > 1) {
return Utils.getPrimaryKeyHash(this.prop!.joinColumns);
}
const meta = this.metadata.get(this.prop!.type);
const alias = qb.getAliasForEntity(meta.name, this);
const pks = Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
return Utils.getPrimaryKeyHash(pks.map(col => `${alias}.${col}`));
}
getPath(): string {
let ret = this.parent && this.prop ? this.prop.name : this.entityName;
if (this.parent instanceof ArrayCriteriaNode && this.parent.parent && !this.key) {
ret = this.parent.parent.key!;
}
if (this.parent) {
const parentPath = this.parent.getPath();
if (parentPath) {
ret = this.parent.getPath() + '.' + ret;
} else if (this.parent.entityName && ret) {
ret = this.parent.entityName + '.' + ret;
}
}
return ret;
}
[inspect.custom]() {
return `${this.constructor.name} ${inspect({ entityName: this.entityName, key: this.key, payload: this.payload })}`;
}
}