@contember/engine-content-api 1.2.0-alpha.4 → 1.2.0-alpha.7
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/acl/EvaluatedPredicateReplacer.d.ts +10 -0
- package/dist/src/acl/EvaluatedPredicateReplacer.d.ts.map +1 -0
- package/dist/src/acl/EvaluatedPredicateReplacer.js +40 -0
- package/dist/src/acl/EvaluatedPredicateReplacer.js.map +1 -0
- package/dist/src/acl/PredicateFactory.d.ts +4 -2
- package/dist/src/acl/PredicateFactory.d.ts.map +1 -1
- package/dist/src/acl/PredicateFactory.js +24 -6
- package/dist/src/acl/PredicateFactory.js.map +1 -1
- package/dist/src/acl/PredicatesInjector.d.ts +1 -1
- package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
- package/dist/src/acl/PredicatesInjector.js +14 -11
- package/dist/src/acl/PredicatesInjector.js.map +1 -1
- package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js +1 -1
- package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.d.ts.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js +25 -13
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.d.ts.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js +26 -13
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js.map +1 -1
- package/dist/src/input-validation/ValidationDataSelector.d.ts.map +1 -1
- package/dist/src/input-validation/ValidationDataSelector.js +1 -1
- package/dist/src/input-validation/ValidationDataSelector.js.map +1 -1
- package/dist/src/mapper/Mapper.d.ts +4 -4
- package/dist/src/mapper/Mapper.d.ts.map +1 -1
- package/dist/src/mapper/Mapper.js +7 -7
- package/dist/src/mapper/Mapper.js.map +1 -1
- package/dist/src/mapper/MapperContainer.d.ts.map +1 -1
- package/dist/src/mapper/MapperContainer.js +5 -2
- package/dist/src/mapper/MapperContainer.js.map +1 -1
- package/dist/src/mapper/select/RelationFetcher.d.ts +40 -5
- package/dist/src/mapper/select/RelationFetcher.d.ts.map +1 -1
- package/dist/src/mapper/select/RelationFetcher.js +19 -6
- package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.d.ts +3 -1
- package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.js +4 -2
- package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
- package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts +2 -2
- package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts.map +1 -1
- package/dist/src/mapper/select/handlers/FieldsVisitor.js +31 -18
- package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
- package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts +5 -0
- package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts.map +1 -0
- package/dist/src/mapper/select/optimizer/ConditionOptimizer.js +33 -0
- package/dist/src/mapper/select/optimizer/ConditionOptimizer.js.map +1 -0
- package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts +11 -0
- package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -0
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js +57 -0
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -0
- package/dist/src/mapper/select/optimizer/helpers.d.ts +10 -0
- package/dist/src/mapper/select/optimizer/helpers.d.ts.map +1 -0
- package/dist/src/mapper/select/optimizer/helpers.js +65 -0
- package/dist/src/mapper/select/optimizer/helpers.js.map +1 -0
- package/dist/src/resolvers/MutationResolver.js +1 -1
- package/dist/src/resolvers/MutationResolver.js.map +1 -1
- package/dist/src/resolvers/ReadHelpers.js +3 -3
- package/dist/src/resolvers/ReadHelpers.js.map +1 -1
- package/dist/src/resolvers/ReadResolver.js +2 -2
- package/dist/src/resolvers/ReadResolver.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js +3 -2
- package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts +2 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js +137 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.js +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js +73 -0
- package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js +73 -0
- package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js +2 -2
- package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js.map +1 -1
- package/dist/tests/cases/unit/conditionOptimizer.test.d.ts +2 -0
- package/dist/tests/cases/unit/conditionOptimizer.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/conditionOptimizer.test.js +113 -0
- package/dist/tests/cases/unit/conditionOptimizer.test.js.map +1 -0
- package/dist/tests/cases/unit/predicatesFactory.test.d.ts +2 -0
- package/dist/tests/cases/unit/predicatesFactory.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/predicatesFactory.test.js +103 -0
- package/dist/tests/cases/unit/predicatesFactory.test.js.map +1 -0
- package/dist/tests/cases/unit/predicatesInjector.test.js +8 -12
- package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
- package/dist/tests/cases/unit/whereOptimizer.test.d.ts +2 -0
- package/dist/tests/cases/unit/whereOptimizer.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/whereOptimizer.test.js +81 -0
- package/dist/tests/cases/unit/whereOptimizer.test.js.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/acl/EvaluatedPredicateReplacer.ts +38 -0
- package/src/acl/PredicateFactory.ts +30 -7
- package/src/acl/PredicatesInjector.ts +18 -10
- package/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.ts +3 -3
- package/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.ts +26 -28
- package/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.ts +27 -31
- package/src/input-validation/ValidationDataSelector.ts +2 -2
- package/src/mapper/Mapper.ts +10 -6
- package/src/mapper/MapperContainer.ts +7 -3
- package/src/mapper/select/RelationFetcher.ts +95 -34
- package/src/mapper/select/WhereBuilder.ts +5 -2
- package/src/mapper/select/handlers/FieldsVisitor.ts +31 -24
- package/src/mapper/select/optimizer/ConditionOptimizer.ts +35 -0
- package/src/mapper/select/optimizer/WhereOptimizer.ts +67 -0
- package/src/mapper/select/optimizer/helpers.ts +54 -0
- package/src/resolvers/MutationResolver.ts +1 -1
- package/src/resolvers/ReadHelpers.ts +3 -3
- package/src/resolvers/ReadResolver.ts +2 -2
- package/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.ts +3 -2
- package/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.ts +130 -0
- package/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.ts +1 -1
- package/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.ts +1 -1
- package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.ts +74 -0
- package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.ts +77 -0
- package/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.ts +2 -2
- package/tests/cases/unit/conditionOptimizer.test.ts +129 -0
- package/tests/cases/unit/predicatesFactory.test.ts +97 -0
- package/tests/cases/unit/predicatesInjector.test.ts +8 -12
- package/tests/cases/unit/whereOptimizer.test.ts +77 -0
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.7",
|
|
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.7",
|
|
14
|
+
"@contember/dic": "^1.2.0-alpha.7",
|
|
15
|
+
"@contember/engine-common": "^1.2.0-alpha.7",
|
|
16
|
+
"@contember/graphql-utils": "^1.2.0-alpha.7",
|
|
17
|
+
"@contember/schema": "^1.2.0-alpha.7",
|
|
18
|
+
"@contember/schema-utils": "^1.2.0-alpha.7",
|
|
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.7",
|
|
24
|
+
"@contember/engine-api-tester": "^1.2.0-alpha.7",
|
|
25
|
+
"@contember/schema-definition": "^1.2.0-alpha.7",
|
|
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",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Input, Model, Writable } from '@contember/schema'
|
|
2
|
+
import deepEqual from 'fast-deep-equal'
|
|
3
|
+
|
|
4
|
+
export class EvaluatedPredicateReplacer {
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly evaluatedPredicate: Input.Where,
|
|
7
|
+
private readonly sourceEntity: Model.Entity,
|
|
8
|
+
private readonly relation: Model.AnyRelation,
|
|
9
|
+
) {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public replace(where: Input.Where): Input.Where {
|
|
13
|
+
return this.replaceInternal(where, false)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private replaceInternal(where: Input.Where, onRelation: boolean): Input.Where {
|
|
17
|
+
if (onRelation && deepEqual(where, this.evaluatedPredicate)) {
|
|
18
|
+
return { [this.sourceEntity.primary]: { always: true } }
|
|
19
|
+
}
|
|
20
|
+
const result: Writable<Input.Where> = {}
|
|
21
|
+
for (const [key, value] of Object.entries(where)) {
|
|
22
|
+
if (key === 'not') {
|
|
23
|
+
result['not'] = this.replaceInternal(value as Input.Where, onRelation)
|
|
24
|
+
|
|
25
|
+
} else if (key === 'and' || key === 'or') {
|
|
26
|
+
result[key] = (value as Input.Where[]).map(it => this.replaceInternal(it, onRelation))
|
|
27
|
+
|
|
28
|
+
} else if (key === this.relation.name && !onRelation) {
|
|
29
|
+
result[key] = this.replaceInternal(value as Input.Where, true)
|
|
30
|
+
|
|
31
|
+
} else {
|
|
32
|
+
result[key] = value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Acl, Input, Model } from '@contember/schema'
|
|
2
2
|
import { VariableInjector } from './VariableInjector'
|
|
3
|
+
import { isOwningRelation } from '@contember/schema-utils'
|
|
4
|
+
import { ImplementationException } from '../exception'
|
|
5
|
+
import { EvaluatedPredicateReplacer } from './EvaluatedPredicateReplacer'
|
|
3
6
|
|
|
4
7
|
const getRowLevelPredicatePseudoField = (entity: Model.Entity) => entity.primary
|
|
5
8
|
|
|
6
9
|
export class PredicateFactory {
|
|
7
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly permissions: Acl.Permissions,
|
|
12
|
+
private readonly model: Model.Schema,
|
|
13
|
+
private readonly variableInjector: VariableInjector,
|
|
14
|
+
) {}
|
|
8
15
|
|
|
9
16
|
public shouldApplyCellLevelPredicate(
|
|
10
17
|
entity: Model.Entity,
|
|
@@ -21,8 +28,9 @@ export class PredicateFactory {
|
|
|
21
28
|
entity: Model.Entity,
|
|
22
29
|
operation: Acl.Operation.update | Acl.Operation.read | Acl.Operation.create,
|
|
23
30
|
fieldNames?: string[],
|
|
31
|
+
overRelation?: Model.AnyRelation,
|
|
24
32
|
): Input.Where
|
|
25
|
-
public create(entity: Model.Entity, operation: Acl.Operation, fieldNames?: string[]): Input.Where {
|
|
33
|
+
public create(entity: Model.Entity, operation: Acl.Operation, fieldNames?: string[], overRelation?: Model.AnyRelation): Input.Where {
|
|
26
34
|
const entityPermissions: Acl.EntityPermissions = this.permissions[entity.name]
|
|
27
35
|
const neverCondition: Input.Where = { [entity.primary]: { never: true } }
|
|
28
36
|
|
|
@@ -68,11 +76,8 @@ export class PredicateFactory {
|
|
|
68
76
|
if (predicatesWhere.length === 0) {
|
|
69
77
|
return {}
|
|
70
78
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { and: predicatesWhere }
|
|
79
|
+
const where: Input.Where = predicatesWhere.length === 1 ? predicatesWhere[0] : { and: predicatesWhere }
|
|
80
|
+
return this.optimizePredicates(where, overRelation)
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
private getRequiredPredicates(
|
|
@@ -94,4 +99,22 @@ export class PredicateFactory {
|
|
|
94
99
|
}
|
|
95
100
|
return predicates
|
|
96
101
|
}
|
|
102
|
+
|
|
103
|
+
private optimizePredicates(where: Input.Where, overRelation?: Model.AnyRelation) {
|
|
104
|
+
if (!overRelation) {
|
|
105
|
+
return where
|
|
106
|
+
}
|
|
107
|
+
const otherSide = isOwningRelation(overRelation) ? overRelation.inversedBy : overRelation.ownedBy
|
|
108
|
+
if (!otherSide) {
|
|
109
|
+
throw new ImplementationException()
|
|
110
|
+
}
|
|
111
|
+
const sourceEntity = this.model.entities[overRelation.target]
|
|
112
|
+
const sourcePredicate = this.create(sourceEntity, Acl.Operation.read, [otherSide])
|
|
113
|
+
if (Object.keys(sourcePredicate).length === 0) {
|
|
114
|
+
return where
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const replacer = new EvaluatedPredicateReplacer(sourcePredicate, sourceEntity, overRelation)
|
|
118
|
+
return replacer.replace(where)
|
|
119
|
+
}
|
|
97
120
|
}
|
|
@@ -5,17 +5,18 @@ import { PredicateFactory } from './PredicateFactory'
|
|
|
5
5
|
export class PredicatesInjector {
|
|
6
6
|
constructor(private readonly schema: Model.Schema, private readonly predicateFactory: PredicateFactory) {}
|
|
7
7
|
|
|
8
|
-
public inject(entity: Model.Entity, where: Input.OptionalWhere): Input.OptionalWhere {
|
|
9
|
-
const restrictedWhere = this.injectToWhere(where, entity)
|
|
10
|
-
return this.createWhere(entity, undefined, restrictedWhere)
|
|
8
|
+
public inject(entity: Model.Entity, where: Input.OptionalWhere, overRelation?: Model.AnyRelation): Input.OptionalWhere {
|
|
9
|
+
const restrictedWhere = this.injectToWhere(where, entity, true)
|
|
10
|
+
return this.createWhere(entity, undefined, restrictedWhere, overRelation)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
private createWhere(
|
|
14
14
|
entity: Model.Entity,
|
|
15
15
|
fieldNames: string[] | undefined,
|
|
16
16
|
where: Input.OptionalWhere,
|
|
17
|
+
overRelation?: Model.AnyRelation,
|
|
17
18
|
): Input.OptionalWhere {
|
|
18
|
-
const predicatesWhere: Input.Where = this.predicateFactory.create(entity, Acl.Operation.read, fieldNames)
|
|
19
|
+
const predicatesWhere: Input.Where = this.predicateFactory.create(entity, Acl.Operation.read, fieldNames, overRelation)
|
|
19
20
|
|
|
20
21
|
const and = [where, predicatesWhere].filter(it => Object.keys(it).length > 0)
|
|
21
22
|
if (and.length === 0) {
|
|
@@ -27,18 +28,20 @@ export class PredicatesInjector {
|
|
|
27
28
|
return { and: and }
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
private injectToWhere(where: Input.OptionalWhere, entity: Model.Entity): Input.OptionalWhere {
|
|
31
|
+
private injectToWhere(where: Input.OptionalWhere, entity: Model.Entity, isRoot: boolean): Input.OptionalWhere {
|
|
31
32
|
const resultWhere: Writable<Input.OptionalWhere> = {}
|
|
32
33
|
if (where.and) {
|
|
33
|
-
resultWhere.and = where.and.filter((it): it is Input.Where => !!it).map(it => this.injectToWhere(it, entity))
|
|
34
|
+
resultWhere.and = where.and.filter((it): it is Input.Where => !!it).map(it => this.injectToWhere(it, entity, isRoot))
|
|
34
35
|
}
|
|
35
36
|
if (where.or) {
|
|
36
|
-
resultWhere.or = where.or.filter((it): it is Input.Where => !!it).map(it => this.injectToWhere(it, entity))
|
|
37
|
+
resultWhere.or = where.or.filter((it): it is Input.Where => !!it).map(it => this.injectToWhere(it, entity, isRoot))
|
|
37
38
|
}
|
|
38
39
|
if (where.not) {
|
|
39
|
-
resultWhere.not = this.injectToWhere(where.not, entity)
|
|
40
|
+
resultWhere.not = this.injectToWhere(where.not, entity, isRoot)
|
|
40
41
|
}
|
|
42
|
+
|
|
41
43
|
const fields = Object.keys(where).filter(it => !['and', 'or', 'not'].includes(it))
|
|
44
|
+
|
|
42
45
|
if (fields.length === 0) {
|
|
43
46
|
return resultWhere
|
|
44
47
|
}
|
|
@@ -48,11 +51,16 @@ export class PredicatesInjector {
|
|
|
48
51
|
visitRelation: (_a, _b, targetEntity) => targetEntity,
|
|
49
52
|
})
|
|
50
53
|
if (targetEntity) {
|
|
51
|
-
resultWhere[field] = this.injectToWhere(where[field] as Input.Where, targetEntity)
|
|
54
|
+
resultWhere[field] = this.injectToWhere(where[field] as Input.Where, targetEntity, false)
|
|
52
55
|
} else {
|
|
53
56
|
resultWhere[field] = where[field]
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
const fieldsForPredicate = !isRoot
|
|
60
|
+
? fields
|
|
61
|
+
: fields.filter(it => this.predicateFactory.shouldApplyCellLevelPredicate(entity, Acl.Operation.read, it))
|
|
62
|
+
|
|
63
|
+
return this.createWhere(entity, fieldsForPredicate, resultWhere)
|
|
57
64
|
}
|
|
65
|
+
|
|
58
66
|
}
|
|
@@ -31,7 +31,7 @@ export class HasManyToHasOneReducerExecutionHandler implements SelectExecutionHa
|
|
|
31
31
|
}
|
|
32
32
|
const newObjectNode = objectNode.withArgs<Input.ListQueryInput>({ filter: whereWithParentId })
|
|
33
33
|
|
|
34
|
-
return context.mapper.select(targetEntity, newObjectNode, targetRelation.name)
|
|
34
|
+
return context.mapper.select(targetEntity, newObjectNode, targetRelation, targetRelation.name)
|
|
35
35
|
},
|
|
36
36
|
null,
|
|
37
37
|
)
|
|
@@ -40,7 +40,7 @@ export class HasManyToHasOneReducerExecutionHandler implements SelectExecutionHa
|
|
|
40
40
|
private getRelationTarget(
|
|
41
41
|
entity: Model.Entity,
|
|
42
42
|
relationName: string,
|
|
43
|
-
): [Model.Entity, Model.
|
|
43
|
+
): [Model.Entity, Model.AnyRelation & Model.JoiningColumnRelation] {
|
|
44
44
|
return acceptFieldVisitor(this.schema, entity, relationName, {
|
|
45
45
|
visitColumn: (): never => {
|
|
46
46
|
throw new ImplementationException('HasManyToHasOneReducerExecutionHandler: Not applicable for a column')
|
|
@@ -50,7 +50,7 @@ export class HasManyToHasOneReducerExecutionHandler implements SelectExecutionHa
|
|
|
50
50
|
relation,
|
|
51
51
|
targetEntity,
|
|
52
52
|
targetRelation,
|
|
53
|
-
): [Model.Entity, Model.
|
|
53
|
+
): [Model.Entity, Model.AnyRelation & Model.JoiningColumnRelation] => {
|
|
54
54
|
if (!targetRelation || !isIt<Model.JoiningColumnRelation>(targetRelation, 'joiningColumn')) {
|
|
55
55
|
throw new Error('HasManyToHasOneReducerExecutionHandler: only applicable for relations with joiningColumn')
|
|
56
56
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Input, Model } from '@contember/schema'
|
|
2
|
-
import { GroupedCounts,
|
|
2
|
+
import { GroupedCounts, Mapper, RelationFetcher } from '../../mapper'
|
|
3
3
|
import { ObjectNode } from '../../inputProcessing'
|
|
4
4
|
|
|
5
5
|
export class PaginatedHasManyCountVisitor implements
|
|
@@ -20,11 +20,13 @@ export class PaginatedHasManyCountVisitor implements
|
|
|
20
20
|
targetRelation: Model.ManyHasOneRelation,
|
|
21
21
|
): Promise<GroupedCounts> {
|
|
22
22
|
return await this.relationFetcher.countOneHasManyGroups(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
{
|
|
24
|
+
mapper: this.mapper,
|
|
25
|
+
filter: this.objectNode.args.filter,
|
|
26
|
+
targetEntity: targetEntity,
|
|
27
|
+
targetRelation: targetRelation,
|
|
28
|
+
ids: this.ids,
|
|
29
|
+
},
|
|
28
30
|
)
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -34,18 +36,16 @@ export class PaginatedHasManyCountVisitor implements
|
|
|
34
36
|
targetEntity: Model.Entity,
|
|
35
37
|
targetRelation: Model.ManyHasManyInverseRelation | null,
|
|
36
38
|
): Promise<GroupedCounts> {
|
|
37
|
-
const joiningTable = relation.joiningTable
|
|
38
|
-
const columns: JoiningColumns = {
|
|
39
|
-
sourceColumn: joiningTable.joiningColumn,
|
|
40
|
-
targetColumn: joiningTable.inverseJoiningColumn,
|
|
41
|
-
}
|
|
42
39
|
return await this.relationFetcher.countManyHasManyGroups(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
{
|
|
41
|
+
mapper: this.mapper,
|
|
42
|
+
filter: this.objectNode.args.filter,
|
|
43
|
+
targetEntity: targetEntity,
|
|
44
|
+
sourceRelation: relation,
|
|
45
|
+
targetRelation,
|
|
46
|
+
directionFrom: 'owning',
|
|
47
|
+
ids: this.ids,
|
|
48
|
+
},
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -55,18 +55,16 @@ export class PaginatedHasManyCountVisitor implements
|
|
|
55
55
|
targetEntity: Model.Entity,
|
|
56
56
|
targetRelation: Model.ManyHasManyOwningRelation,
|
|
57
57
|
): Promise<GroupedCounts> {
|
|
58
|
-
const joiningTable = targetRelation.joiningTable
|
|
59
|
-
const columns: JoiningColumns = {
|
|
60
|
-
targetColumn: joiningTable.joiningColumn,
|
|
61
|
-
sourceColumn: joiningTable.inverseJoiningColumn,
|
|
62
|
-
}
|
|
63
58
|
return await this.relationFetcher.countManyHasManyGroups(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
{
|
|
60
|
+
mapper: this.mapper,
|
|
61
|
+
filter: this.objectNode.args.filter,
|
|
62
|
+
targetEntity: targetEntity,
|
|
63
|
+
sourceRelation: relation,
|
|
64
|
+
targetRelation,
|
|
65
|
+
directionFrom: 'inverse',
|
|
66
|
+
ids: this.ids,
|
|
67
|
+
},
|
|
70
68
|
)
|
|
71
69
|
}
|
|
72
70
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Input, Model } from '@contember/schema'
|
|
2
|
-
import {
|
|
2
|
+
import { Mapper, RelationFetcher, SelectGroupedObjects } from '../../mapper'
|
|
3
3
|
import { ObjectNode } from '../../inputProcessing'
|
|
4
4
|
|
|
5
5
|
export class PaginatedHasManyNodesVisitor implements
|
|
@@ -20,12 +20,14 @@ export class PaginatedHasManyNodesVisitor implements
|
|
|
20
20
|
targetRelation: Model.ManyHasOneRelation,
|
|
21
21
|
): Promise<SelectGroupedObjects> {
|
|
22
22
|
return await this.relationFetcher.fetchOneHasManyGroups(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
{
|
|
24
|
+
mapper: this.mapper,
|
|
25
|
+
objectNode: this.objectNode,
|
|
26
|
+
targetEntity: targetEntity,
|
|
27
|
+
relation: relation,
|
|
28
|
+
targetRelation: targetRelation,
|
|
29
|
+
ids: this.ids,
|
|
30
|
+
},
|
|
29
31
|
)
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -35,19 +37,16 @@ export class PaginatedHasManyNodesVisitor implements
|
|
|
35
37
|
targetEntity: Model.Entity,
|
|
36
38
|
targetRelation: Model.ManyHasManyInverseRelation | null,
|
|
37
39
|
): Promise<SelectGroupedObjects> {
|
|
38
|
-
const joiningTable = relation.joiningTable
|
|
39
|
-
const columns: JoiningColumns = {
|
|
40
|
-
sourceColumn: joiningTable.joiningColumn,
|
|
41
|
-
targetColumn: joiningTable.inverseJoiningColumn,
|
|
42
|
-
}
|
|
43
40
|
return await this.relationFetcher.fetchManyHasManyGroups(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
{
|
|
42
|
+
mapper: this.mapper,
|
|
43
|
+
field: this.objectNode,
|
|
44
|
+
targetEntity: targetEntity,
|
|
45
|
+
sourceRelation: relation,
|
|
46
|
+
targetRelation,
|
|
47
|
+
directionFrom: 'owning',
|
|
48
|
+
ids: this.ids,
|
|
49
|
+
},
|
|
51
50
|
)
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -57,19 +56,16 @@ export class PaginatedHasManyNodesVisitor implements
|
|
|
57
56
|
targetEntity: Model.Entity,
|
|
58
57
|
targetRelation: Model.ManyHasManyOwningRelation,
|
|
59
58
|
): Promise<SelectGroupedObjects> {
|
|
60
|
-
const joiningTable = targetRelation.joiningTable
|
|
61
|
-
const columns: JoiningColumns = {
|
|
62
|
-
targetColumn: joiningTable.joiningColumn,
|
|
63
|
-
sourceColumn: joiningTable.inverseJoiningColumn,
|
|
64
|
-
}
|
|
65
59
|
return await this.relationFetcher.fetchManyHasManyGroups(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
{
|
|
61
|
+
mapper: this.mapper,
|
|
62
|
+
field: this.objectNode,
|
|
63
|
+
targetEntity: targetEntity,
|
|
64
|
+
sourceRelation: relation,
|
|
65
|
+
targetRelation,
|
|
66
|
+
directionFrom: 'inverse',
|
|
67
|
+
ids: this.ids,
|
|
68
|
+
},
|
|
73
69
|
)
|
|
74
70
|
}
|
|
75
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { QueryAstFactory } from './QueryAstFactory'
|
|
2
2
|
import { Mapper } from '../mapper'
|
|
3
|
-
import { Dependencies
|
|
3
|
+
import { Dependencies } from './dependencies'
|
|
4
4
|
import { Input, Model, Value } from '@contember/schema'
|
|
5
5
|
|
|
6
6
|
export class ValidationDataSelector {
|
|
@@ -20,7 +20,7 @@ export class ValidationDataSelector {
|
|
|
20
20
|
return {}
|
|
21
21
|
}
|
|
22
22
|
const queryAst = this.queryAstFactory.create(entity.name, dependencies).withArg('by', where)
|
|
23
|
-
const node = await mapper.selectUnique(entity, queryAst)
|
|
23
|
+
const node = await mapper.selectUnique(entity, queryAst, null)
|
|
24
24
|
if (!node) {
|
|
25
25
|
return null
|
|
26
26
|
}
|
package/src/mapper/Mapper.ts
CHANGED
|
@@ -56,15 +56,17 @@ export class Mapper {
|
|
|
56
56
|
return result[0] !== undefined ? result[0][columnName] : undefined
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
public async select(entity: Model.Entity, input: ObjectNode<Input.ListQueryInput
|
|
59
|
+
public async select(entity: Model.Entity, input: ObjectNode<Input.ListQueryInput>, overRelation: Model.AnyRelation | null): Promise<SelectResultObject[]>
|
|
60
60
|
public async select(
|
|
61
61
|
entity: Model.Entity,
|
|
62
62
|
input: ObjectNode<Input.ListQueryInput>,
|
|
63
|
+
overRelation: Model.AnyRelation | null,
|
|
63
64
|
indexBy: string,
|
|
64
65
|
): Promise<SelectIndexedResultObjects>
|
|
65
66
|
public async select(
|
|
66
67
|
entity: Model.Entity,
|
|
67
68
|
input: ObjectNode<Input.ListQueryInput>,
|
|
69
|
+
overRelation: Model.AnyRelation | null,
|
|
68
70
|
indexBy?: string,
|
|
69
71
|
): Promise<SelectResultObject[] | SelectIndexedResultObjects> {
|
|
70
72
|
const hydrator = new SelectHydrator()
|
|
@@ -75,7 +77,7 @@ export class Mapper {
|
|
|
75
77
|
indexByAlias = path.for(indexBy).alias
|
|
76
78
|
qb = qb.select([path.alias, getColumnName(this.schema, entity, indexBy)], indexByAlias)
|
|
77
79
|
}
|
|
78
|
-
const rows = await this.selectRows(hydrator, qb, entity, input)
|
|
80
|
+
const rows = await this.selectRows(hydrator, qb, entity, input, overRelation)
|
|
79
81
|
|
|
80
82
|
return await (indexByAlias !== null ? hydrator.hydrateAll(rows, indexByAlias) : hydrator.hydrateAll(rows))
|
|
81
83
|
}
|
|
@@ -83,18 +85,19 @@ export class Mapper {
|
|
|
83
85
|
public async selectUnique(
|
|
84
86
|
entity: Model.Entity,
|
|
85
87
|
query: ObjectNode<Input.UniqueQueryInput>,
|
|
88
|
+
overRelation: Model.AnyRelation | null,
|
|
86
89
|
): Promise<SelectResultObject | null> {
|
|
87
90
|
const uniqueWhere = this.uniqueWhereExpander.expand(entity, query.args.by)
|
|
88
91
|
const where = query.args.filter ? { and: [uniqueWhere, query.args.filter] } : uniqueWhere
|
|
89
92
|
const queryExpanded = query.withArg<Input.ListQueryInput>('filter', where)
|
|
90
93
|
|
|
91
|
-
return (await this.select(entity, queryExpanded))[0] || null
|
|
94
|
+
return (await this.select(entity, queryExpanded, overRelation))[0] || null
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
public async selectGrouped(
|
|
95
98
|
entity: Model.Entity,
|
|
96
99
|
input: ObjectNode<Input.ListQueryInput>,
|
|
97
|
-
relation: Model.JoiningColumnRelation & Model.
|
|
100
|
+
relation: Model.JoiningColumnRelation & Model.AnyRelation,
|
|
98
101
|
) {
|
|
99
102
|
const hydrator = new SelectHydrator()
|
|
100
103
|
let qb: SelectBuilder<SelectBuilder.Result> = SelectBuilder.create()
|
|
@@ -102,7 +105,7 @@ export class Mapper {
|
|
|
102
105
|
const groupingKey = '__grouping_key'
|
|
103
106
|
qb = qb.select([path.alias, relation.joiningColumn.columnName], groupingKey)
|
|
104
107
|
|
|
105
|
-
const rows = await this.selectRows(hydrator, qb, entity, input, relation.name)
|
|
108
|
+
const rows = await this.selectRows(hydrator, qb, entity, input, relation, relation.name)
|
|
106
109
|
return await hydrator.hydrateGroups(rows, groupingKey)
|
|
107
110
|
}
|
|
108
111
|
|
|
@@ -111,6 +114,7 @@ export class Mapper {
|
|
|
111
114
|
qb: SelectBuilder<SelectBuilder.Result>,
|
|
112
115
|
entity: Model.Entity,
|
|
113
116
|
input: ObjectNode<Input.ListQueryInput>,
|
|
117
|
+
overRelation: Model.AnyRelation | null,
|
|
114
118
|
groupBy?: string,
|
|
115
119
|
) {
|
|
116
120
|
const inputWithOrder = OrderByHelper.appendDefaultOrderBy(entity, input, [])
|
|
@@ -118,7 +122,7 @@ export class Mapper {
|
|
|
118
122
|
const augmentedBuilder = qb.from(entity.tableName, path.alias).meta('path', [...input.path, input.alias])
|
|
119
123
|
|
|
120
124
|
const selector = this.selectBuilderFactory.create(augmentedBuilder, hydrator)
|
|
121
|
-
const filterWithPredicates = this.predicatesInjector.inject(entity, inputWithOrder.args.filter || {})
|
|
125
|
+
const filterWithPredicates = this.predicatesInjector.inject(entity, inputWithOrder.args.filter || {}, overRelation ?? undefined)
|
|
122
126
|
const inputWithPredicates = inputWithOrder.withArg('filter', filterWithPredicates)
|
|
123
127
|
selector.select(this, entity, inputWithPredicates, path, groupBy)
|
|
124
128
|
return await selector.execute(this.db)
|
|
@@ -32,6 +32,8 @@ import { Client, SelectBuilder as DbSelectBuilder } from '@contember/database'
|
|
|
32
32
|
import { Providers } from '@contember/schema-utils'
|
|
33
33
|
import { PaginatedHasManyExecutionHandler } from '../extensions/paginatedHasMany/PaginatedHasManyExecutionHandler'
|
|
34
34
|
import { PaginatedHasManyFieldProvider } from '../extensions/paginatedHasMany/PaginatedHasManyFieldProvider'
|
|
35
|
+
import { WhereOptimizer } from './select/optimizer/WhereOptimizer'
|
|
36
|
+
import { ConditionOptimizer } from './select/optimizer/ConditionOptimizer'
|
|
35
37
|
|
|
36
38
|
type MapperContainerArgs = {
|
|
37
39
|
schema: Schema
|
|
@@ -51,7 +53,7 @@ export const createMapperContainer = ({ permissions, schema, identityVariables,
|
|
|
51
53
|
.addService('variableInjector', () =>
|
|
52
54
|
new VariableInjector(schema.model, identityVariables))
|
|
53
55
|
.addService('predicateFactory', ({ variableInjector }) =>
|
|
54
|
-
new PredicateFactory(permissions, variableInjector))
|
|
56
|
+
new PredicateFactory(permissions, schema.model, variableInjector))
|
|
55
57
|
.addService('predicatesInjector', ({ predicateFactory }) =>
|
|
56
58
|
new PredicatesInjector(schema.model, predicateFactory))
|
|
57
59
|
.addService('joinBuilder', () =>
|
|
@@ -60,8 +62,10 @@ export const createMapperContainer = ({ permissions, schema, identityVariables,
|
|
|
60
62
|
new ConditionBuilder())
|
|
61
63
|
.addService('pathFactory', () =>
|
|
62
64
|
new PathFactory())
|
|
63
|
-
.addService('
|
|
64
|
-
new
|
|
65
|
+
.addService('whereOptimized', () =>
|
|
66
|
+
new WhereOptimizer(schema.model, new ConditionOptimizer()))
|
|
67
|
+
.addService('whereBuilder', ({ joinBuilder, conditionBuilder, pathFactory, whereOptimized }) =>
|
|
68
|
+
new WhereBuilder(schema.model, joinBuilder, conditionBuilder, pathFactory, whereOptimized))
|
|
65
69
|
.addService('orderByBuilder', ({ joinBuilder }) =>
|
|
66
70
|
new OrderByBuilder(schema.model, joinBuilder))
|
|
67
71
|
.addService('relationFetcher', ({ whereBuilder, orderByBuilder, predicatesInjector, pathFactory }) =>
|