@contember/engine-content-api 1.2.0-alpha.13 → 1.2.0-alpha.15
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/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js +69 -23
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts +1 -4
- package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereReplacer.js +27 -9
- package/dist/src/mapper/select/optimizer/WhereReplacer.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/mapper/select/optimizer/WhereOptimizer.ts +80 -36
- package/src/mapper/select/optimizer/WhereReplacer.ts +27 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/engine-content-api",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.15",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src/index.d.ts",
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
"test": "vitest --no-threads"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@contember/database": "^1.2.0-alpha.
|
|
14
|
-
"@contember/dic": "^1.2.0-alpha.
|
|
15
|
-
"@contember/engine-common": "^1.2.0-alpha.
|
|
16
|
-
"@contember/graphql-utils": "^1.2.0-alpha.
|
|
17
|
-
"@contember/schema": "^1.2.0-alpha.
|
|
18
|
-
"@contember/schema-utils": "^1.2.0-alpha.
|
|
13
|
+
"@contember/database": "^1.2.0-alpha.15",
|
|
14
|
+
"@contember/dic": "^1.2.0-alpha.15",
|
|
15
|
+
"@contember/engine-common": "^1.2.0-alpha.15",
|
|
16
|
+
"@contember/graphql-utils": "^1.2.0-alpha.15",
|
|
17
|
+
"@contember/schema": "^1.2.0-alpha.15",
|
|
18
|
+
"@contember/schema-utils": "^1.2.0-alpha.15",
|
|
19
19
|
"@graphql-tools/schema": "^8.3.5",
|
|
20
20
|
"graphql-tag": "^2.12.5"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@contember/database-tester": "^1.2.0-alpha.
|
|
24
|
-
"@contember/engine-api-tester": "^1.2.0-alpha.
|
|
25
|
-
"@contember/schema-definition": "^1.2.0-alpha.
|
|
23
|
+
"@contember/database-tester": "^1.2.0-alpha.15",
|
|
24
|
+
"@contember/engine-api-tester": "^1.2.0-alpha.15",
|
|
25
|
+
"@contember/schema-definition": "^1.2.0-alpha.15",
|
|
26
26
|
"@graphql-codegen/cli": "^2.6.2",
|
|
27
27
|
"@graphql-codegen/typescript": "^2.5.1",
|
|
28
28
|
"@graphql-codegen/typescript-operations": "^2.4.2",
|
|
@@ -5,8 +5,8 @@ import { optimizeAnd, optimizeNot, optimizeOr } from './helpers'
|
|
|
5
5
|
import { replaceWhere } from './WhereReplacer'
|
|
6
6
|
|
|
7
7
|
type ExtendedRelationContext =
|
|
8
|
-
& Model.AnyRelationContext
|
|
9
8
|
& {
|
|
9
|
+
context: Model.AnyRelationContext
|
|
10
10
|
canEliminate: boolean
|
|
11
11
|
isLast: boolean
|
|
12
12
|
}
|
|
@@ -16,6 +16,8 @@ export interface WhereOptimizationHints {
|
|
|
16
16
|
evaluatedPredicates?: Input.OptionalWhere[]
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
type OptimizedOperand = Input.OptionalWhere | undefined | boolean
|
|
20
|
+
|
|
19
21
|
export class WhereOptimizer {
|
|
20
22
|
private static eliminableRelations = new Set<Model.AnyRelationContext['type']>(['oneHasMany', 'oneHasOneInverse', 'oneHasOneOwning'])
|
|
21
23
|
|
|
@@ -32,7 +34,7 @@ export class WhereOptimizer {
|
|
|
32
34
|
if (!WhereOptimizer.eliminableRelations.has(el.type)) {
|
|
33
35
|
processedRelationPath = []
|
|
34
36
|
} else {
|
|
35
|
-
processedRelationPath.push({
|
|
37
|
+
processedRelationPath.push({ context: el, canEliminate: true, isLast: Number(i) === relationPath.length })
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
let result = this.optimizeWhere(where, entity, processedRelationPath)
|
|
@@ -40,12 +42,16 @@ export class WhereOptimizer {
|
|
|
40
42
|
if (typeof result === 'boolean') {
|
|
41
43
|
return { [entity.primary]: { [result ? 'always' : 'never']: true } }
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
let changed = false
|
|
44
46
|
for (const evaluated of evaluatedPredicates) {
|
|
45
47
|
const evaluatedPredicate = this.optimize(evaluated, entity)
|
|
46
|
-
|
|
48
|
+
const newResult = replaceWhere(result, evaluatedPredicate, { [entity.primary]: { always: true } })
|
|
49
|
+
if (newResult !== result) {
|
|
50
|
+
result = newResult
|
|
51
|
+
changed = true
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
|
-
if (
|
|
54
|
+
if (changed) {
|
|
49
55
|
return this.optimize(result, entity)
|
|
50
56
|
}
|
|
51
57
|
|
|
@@ -53,58 +59,95 @@ export class WhereOptimizer {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
private optimizeWhere(where: Input.OptionalWhere, entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const operands: OptimizedOperand[] = []
|
|
63
|
+
|
|
64
|
+
for (const key in where) {
|
|
65
|
+
const value = where[key]
|
|
66
|
+
let operand: OptimizedOperand = undefined
|
|
67
|
+
if (value === undefined || value === null) {
|
|
68
|
+
continue
|
|
69
|
+
} else if (key === 'and') {
|
|
70
|
+
const innerOperands: (Input.OptionalWhere | boolean)[] = []
|
|
71
|
+
for (const innerValue of value as readonly Input.OptionalWhere[]) {
|
|
72
|
+
const innerOperand = this.optimizeWhere(innerValue, entity, relationPath)
|
|
73
|
+
if (innerOperand === false) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
innerOperands.push(innerOperand)
|
|
77
|
+
}
|
|
78
|
+
operand = this.optimizeAnd(innerOperands, entity, relationPath)
|
|
79
|
+
} else if (key === 'or') {
|
|
80
|
+
const innerOperands: (Input.OptionalWhere | boolean)[] = []
|
|
81
|
+
for (const innerValue of value as readonly Input.OptionalWhere[]) {
|
|
82
|
+
const innerOperand = this.optimizeWhere(innerValue, entity, relationPath)
|
|
83
|
+
if (innerOperand === true) {
|
|
84
|
+
operand = true
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
innerOperands.push(innerOperand)
|
|
88
|
+
}
|
|
89
|
+
if (operand !== true) {
|
|
90
|
+
operand = this.optimizeOr(innerOperands, entity, relationPath)
|
|
91
|
+
}
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
93
|
+
} else if (key === 'not') {
|
|
94
|
+
operand = optimizeNot(this.optimizeWhere(value as Input.OptionalWhere, entity, relationPath))
|
|
63
95
|
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
} else {
|
|
97
|
+
operand = this.resolveFieldValue(entity, key, value, relationPath)
|
|
98
|
+
}
|
|
66
99
|
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
if (operand === false) {
|
|
101
|
+
return false
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
relationPath,
|
|
76
|
-
)
|
|
103
|
+
} else if (operand !== undefined) {
|
|
104
|
+
operands.push(operand)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return this.optimizeAnd(operands, entity, relationPath)
|
|
77
108
|
}
|
|
78
109
|
|
|
79
|
-
private optimizeOr(operands: readonly
|
|
110
|
+
private optimizeOr(operands: readonly OptimizedOperand[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
|
|
80
111
|
const optimized = optimizeOr(operands)
|
|
81
112
|
if (typeof optimized === 'boolean' || !Array.isArray(optimized.or)) {
|
|
82
113
|
return optimized
|
|
83
114
|
}
|
|
84
115
|
const result = this.crossOptimize(optimized.or, entity, { never: true }, relationPath)
|
|
116
|
+
if (result === optimized.or) {
|
|
117
|
+
return optimized
|
|
118
|
+
}
|
|
85
119
|
return optimizeOr(result)
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
private optimizeAnd(operands: readonly
|
|
122
|
+
private optimizeAnd(operands: readonly OptimizedOperand[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
|
|
89
123
|
const optimized = optimizeAnd(operands)
|
|
90
124
|
if (typeof optimized === 'boolean' || !Array.isArray(optimized.and)) {
|
|
91
125
|
return optimized
|
|
92
126
|
}
|
|
93
127
|
const result = this.crossOptimize(optimized.and, entity, { always: true }, relationPath)
|
|
128
|
+
if (result === optimized.and) {
|
|
129
|
+
return optimized
|
|
130
|
+
}
|
|
94
131
|
return optimizeAnd(result)
|
|
95
132
|
}
|
|
96
133
|
|
|
97
134
|
private crossOptimize(operands: Input.OptionalWhere[], entity: Model.Entity, replacement: Input.Condition, relationPath: ExtendedRelationContext[]): (Input.OptionalWhere | boolean)[] {
|
|
98
|
-
|
|
135
|
+
let result: (Input.OptionalWhere | boolean)[] = operands
|
|
136
|
+
let copied = false
|
|
99
137
|
const count = result.length
|
|
100
138
|
for (let i = 0; i < count; i++) {
|
|
101
139
|
for (let j = 0; j < count; j++) {
|
|
102
140
|
const a = result[j]
|
|
103
141
|
const b = result[i]
|
|
104
142
|
if (i !== j && typeof a !== 'boolean' && typeof b !== 'boolean') {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
143
|
+
const elResult = replaceWhere(a, b, { [entity.primary]: replacement })
|
|
144
|
+
if (elResult !== a) {
|
|
145
|
+
if (!copied) {
|
|
146
|
+
result = [...result]
|
|
147
|
+
copied = true
|
|
148
|
+
}
|
|
149
|
+
result[j] = this.optimizeWhere(elResult, entity, relationPath)
|
|
150
|
+
}
|
|
108
151
|
}
|
|
109
152
|
}
|
|
110
153
|
}
|
|
@@ -123,21 +166,22 @@ export class WhereOptimizer {
|
|
|
123
166
|
return { [key]: optimizedCondition }
|
|
124
167
|
},
|
|
125
168
|
visitRelation: context => {
|
|
126
|
-
const { targetEntity, relation } = context
|
|
127
169
|
let where = value as Input.OptionalWhere
|
|
128
170
|
const newRelationPath: ExtendedRelationContext[] = [...relationPath]
|
|
129
|
-
|
|
171
|
+
const length = relationPath.length
|
|
172
|
+
if (length > 0 && relationPath[length - 1].context.targetRelation?.name === context.relation.name) {
|
|
130
173
|
const item = newRelationPath.pop()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
where = replaceWhere(where, { [targetEntity.primary]: { isNull:
|
|
174
|
+
const type = item!.context.type
|
|
175
|
+
if (item?.canEliminate && (type === 'oneHasMany' || type === 'oneHasOneOwning' || type === 'oneHasOneInverse')) {
|
|
176
|
+
where = replaceWhere(where, { [context.targetEntity.primary]: { isNull: true } }, { [context.targetEntity.primary]: { never: true } })
|
|
177
|
+
where = replaceWhere(where, { [context.targetEntity.primary]: { isNull: false } }, { [context.targetEntity.primary]: { always: true } })
|
|
134
178
|
}
|
|
135
179
|
} else {
|
|
136
180
|
// first level relation - we are sure root exists
|
|
137
|
-
const canEliminate =
|
|
138
|
-
newRelationPath.push({
|
|
181
|
+
const canEliminate = length === 0 || relationPath[length - 1].isLast
|
|
182
|
+
newRelationPath.push({ context, isLast: false, canEliminate })
|
|
139
183
|
}
|
|
140
|
-
const optimizedWhere = this.optimizeWhere(where, targetEntity, newRelationPath)
|
|
184
|
+
const optimizedWhere = this.optimizeWhere(where, context.targetEntity, newRelationPath)
|
|
141
185
|
|
|
142
186
|
if (typeof optimizedWhere === 'boolean') {
|
|
143
187
|
return optimizedWhere
|
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
import { Input, Writable } from '@contember/schema'
|
|
2
2
|
import deepEqual from 'fast-deep-equal'
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
count: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const replaceWhere = (subject: Input.OptionalWhere, find: Input.OptionalWhere, replace: Input.OptionalWhere, resultTracker: ReplaceWhereResult = { count: 0 }): Input.OptionalWhere => {
|
|
4
|
+
export const replaceWhere = (subject: Input.OptionalWhere, find: Input.OptionalWhere, replace: Input.OptionalWhere): Input.OptionalWhere => {
|
|
9
5
|
if (deepEqual(subject, find)) {
|
|
10
|
-
resultTracker.count++
|
|
11
6
|
return replace
|
|
12
7
|
}
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
let result: Writable<Input.OptionalWhere> = subject
|
|
9
|
+
let copied = false
|
|
10
|
+
for (const key in subject) {
|
|
11
|
+
const value = subject[key]
|
|
15
12
|
if (key === 'not') {
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const newNot = replaceWhere(value as Input.OptionalWhere, find, replace)
|
|
14
|
+
if (newNot !== value) {
|
|
15
|
+
if (!copied) {
|
|
16
|
+
result = { ...result }
|
|
17
|
+
}
|
|
18
|
+
result['not'] = newNot
|
|
19
|
+
}
|
|
18
20
|
} else if (key === 'and' || key === 'or') {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const items: Input.OptionalWhere[] = []
|
|
22
|
+
let itemChanged = false
|
|
23
|
+
for (const el of value as Input.OptionalWhere[]) {
|
|
24
|
+
const newEl = replaceWhere(el, find, replace)
|
|
25
|
+
if (newEl !== el) {
|
|
26
|
+
itemChanged = true
|
|
27
|
+
}
|
|
28
|
+
items.push(newEl)
|
|
29
|
+
}
|
|
30
|
+
if (itemChanged) {
|
|
31
|
+
if (!copied) {
|
|
32
|
+
result = { ...result }
|
|
33
|
+
}
|
|
34
|
+
result[key] = items
|
|
35
|
+
}
|
|
23
36
|
}
|
|
24
|
-
|
|
25
37
|
}
|
|
26
38
|
return result
|
|
27
39
|
}
|