@contember/engine-content-api 1.2.0-alpha.5 → 1.2.0-alpha.6

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.
Files changed (121) hide show
  1. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts +10 -0
  2. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts.map +1 -0
  3. package/dist/src/acl/EvaluatedPredicateReplacer.js +40 -0
  4. package/dist/src/acl/EvaluatedPredicateReplacer.js.map +1 -0
  5. package/dist/src/acl/PredicateFactory.d.ts +4 -2
  6. package/dist/src/acl/PredicateFactory.d.ts.map +1 -1
  7. package/dist/src/acl/PredicateFactory.js +24 -6
  8. package/dist/src/acl/PredicateFactory.js.map +1 -1
  9. package/dist/src/acl/PredicatesInjector.d.ts +1 -1
  10. package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
  11. package/dist/src/acl/PredicatesInjector.js +14 -11
  12. package/dist/src/acl/PredicatesInjector.js.map +1 -1
  13. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js +1 -1
  14. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js.map +1 -1
  15. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.d.ts.map +1 -1
  16. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js +25 -13
  17. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js.map +1 -1
  18. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.d.ts.map +1 -1
  19. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js +26 -13
  20. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js.map +1 -1
  21. package/dist/src/input-validation/ValidationDataSelector.d.ts.map +1 -1
  22. package/dist/src/input-validation/ValidationDataSelector.js +1 -1
  23. package/dist/src/input-validation/ValidationDataSelector.js.map +1 -1
  24. package/dist/src/mapper/Mapper.d.ts +4 -4
  25. package/dist/src/mapper/Mapper.d.ts.map +1 -1
  26. package/dist/src/mapper/Mapper.js +7 -7
  27. package/dist/src/mapper/Mapper.js.map +1 -1
  28. package/dist/src/mapper/MapperContainer.d.ts.map +1 -1
  29. package/dist/src/mapper/MapperContainer.js +5 -2
  30. package/dist/src/mapper/MapperContainer.js.map +1 -1
  31. package/dist/src/mapper/select/RelationFetcher.d.ts +40 -5
  32. package/dist/src/mapper/select/RelationFetcher.d.ts.map +1 -1
  33. package/dist/src/mapper/select/RelationFetcher.js +19 -6
  34. package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
  35. package/dist/src/mapper/select/WhereBuilder.d.ts +3 -1
  36. package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
  37. package/dist/src/mapper/select/WhereBuilder.js +4 -2
  38. package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
  39. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts +2 -2
  40. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts.map +1 -1
  41. package/dist/src/mapper/select/handlers/FieldsVisitor.js +31 -18
  42. package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
  43. package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts +5 -0
  44. package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts.map +1 -0
  45. package/dist/src/mapper/select/optimizer/ConditionOptimizer.js +33 -0
  46. package/dist/src/mapper/select/optimizer/ConditionOptimizer.js.map +1 -0
  47. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts +11 -0
  48. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -0
  49. package/dist/src/mapper/select/optimizer/WhereOptimizer.js +57 -0
  50. package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -0
  51. package/dist/src/mapper/select/optimizer/helpers.d.ts +10 -0
  52. package/dist/src/mapper/select/optimizer/helpers.d.ts.map +1 -0
  53. package/dist/src/mapper/select/optimizer/helpers.js +65 -0
  54. package/dist/src/mapper/select/optimizer/helpers.js.map +1 -0
  55. package/dist/src/resolvers/MutationResolver.js +1 -1
  56. package/dist/src/resolvers/MutationResolver.js.map +1 -1
  57. package/dist/src/resolvers/ReadHelpers.js +3 -3
  58. package/dist/src/resolvers/ReadHelpers.js.map +1 -1
  59. package/dist/src/resolvers/ReadResolver.js +2 -2
  60. package/dist/src/resolvers/ReadResolver.js.map +1 -1
  61. package/dist/src/tsconfig.tsbuildinfo +1 -1
  62. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js +3 -2
  63. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js.map +1 -1
  64. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts +2 -0
  65. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts.map +1 -0
  66. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js +137 -0
  67. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js.map +1 -0
  68. package/dist/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.js +1 -1
  69. package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js +1 -1
  70. package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js.map +1 -1
  71. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js +73 -0
  72. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js.map +1 -1
  73. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js +73 -0
  74. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js.map +1 -1
  75. package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js +2 -2
  76. package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js.map +1 -1
  77. package/dist/tests/cases/unit/conditionOptimizer.test.d.ts +2 -0
  78. package/dist/tests/cases/unit/conditionOptimizer.test.d.ts.map +1 -0
  79. package/dist/tests/cases/unit/conditionOptimizer.test.js +113 -0
  80. package/dist/tests/cases/unit/conditionOptimizer.test.js.map +1 -0
  81. package/dist/tests/cases/unit/predicatesFactory.test.d.ts +2 -0
  82. package/dist/tests/cases/unit/predicatesFactory.test.d.ts.map +1 -0
  83. package/dist/tests/cases/unit/predicatesFactory.test.js +103 -0
  84. package/dist/tests/cases/unit/predicatesFactory.test.js.map +1 -0
  85. package/dist/tests/cases/unit/predicatesInjector.test.js +8 -12
  86. package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
  87. package/dist/tests/cases/unit/whereOptimizer.test.d.ts +2 -0
  88. package/dist/tests/cases/unit/whereOptimizer.test.d.ts.map +1 -0
  89. package/dist/tests/cases/unit/whereOptimizer.test.js +81 -0
  90. package/dist/tests/cases/unit/whereOptimizer.test.js.map +1 -0
  91. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +10 -10
  93. package/src/acl/EvaluatedPredicateReplacer.ts +38 -0
  94. package/src/acl/PredicateFactory.ts +30 -7
  95. package/src/acl/PredicatesInjector.ts +18 -10
  96. package/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.ts +3 -3
  97. package/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.ts +26 -28
  98. package/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.ts +27 -31
  99. package/src/input-validation/ValidationDataSelector.ts +2 -2
  100. package/src/mapper/Mapper.ts +10 -6
  101. package/src/mapper/MapperContainer.ts +7 -3
  102. package/src/mapper/select/RelationFetcher.ts +95 -34
  103. package/src/mapper/select/WhereBuilder.ts +5 -2
  104. package/src/mapper/select/handlers/FieldsVisitor.ts +31 -24
  105. package/src/mapper/select/optimizer/ConditionOptimizer.ts +35 -0
  106. package/src/mapper/select/optimizer/WhereOptimizer.ts +67 -0
  107. package/src/mapper/select/optimizer/helpers.ts +54 -0
  108. package/src/resolvers/MutationResolver.ts +1 -1
  109. package/src/resolvers/ReadHelpers.ts +3 -3
  110. package/src/resolvers/ReadResolver.ts +2 -2
  111. package/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.ts +3 -2
  112. package/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.ts +130 -0
  113. package/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.ts +1 -1
  114. package/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.ts +1 -1
  115. package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.ts +74 -0
  116. package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.ts +77 -0
  117. package/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.ts +2 -2
  118. package/tests/cases/unit/conditionOptimizer.test.ts +129 -0
  119. package/tests/cases/unit/predicatesFactory.test.ts +97 -0
  120. package/tests/cases/unit/predicatesInjector.test.ts +8 -12
  121. 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.5",
3
+ "version": "1.2.0-alpha.6",
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.5",
14
- "@contember/dic": "^1.2.0-alpha.5",
15
- "@contember/engine-common": "^1.2.0-alpha.5",
16
- "@contember/graphql-utils": "^1.2.0-alpha.5",
17
- "@contember/schema": "^1.2.0-alpha.5",
18
- "@contember/schema-utils": "^1.2.0-alpha.5",
13
+ "@contember/database": "^1.2.0-alpha.6",
14
+ "@contember/dic": "^1.2.0-alpha.6",
15
+ "@contember/engine-common": "^1.2.0-alpha.6",
16
+ "@contember/graphql-utils": "^1.2.0-alpha.6",
17
+ "@contember/schema": "^1.2.0-alpha.6",
18
+ "@contember/schema-utils": "^1.2.0-alpha.6",
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.5",
24
- "@contember/engine-api-tester": "^1.2.0-alpha.5",
25
- "@contember/schema-definition": "^1.2.0-alpha.5",
23
+ "@contember/database-tester": "^1.2.0-alpha.6",
24
+ "@contember/engine-api-tester": "^1.2.0-alpha.6",
25
+ "@contember/schema-definition": "^1.2.0-alpha.6",
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(private readonly permissions: Acl.Permissions, private readonly variableInjector: VariableInjector) {}
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
- if (predicatesWhere.length === 1) {
72
- return predicatesWhere[0]
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
- return this.createWhere(entity, fields, resultWhere)
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.Relation & Model.JoiningColumnRelation] {
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.Relation & Model.JoiningColumnRelation] => {
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, JoiningColumns, Mapper, RelationFetcher } from '../../mapper'
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
- this.mapper,
24
- this.objectNode.args.filter,
25
- targetEntity,
26
- targetRelation,
27
- this.ids,
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
- this.mapper,
44
- this.objectNode.args.filter,
45
- targetEntity,
46
- relation,
47
- this.ids,
48
- columns,
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
- this.mapper,
65
- this.objectNode.args.filter,
66
- targetEntity,
67
- targetRelation,
68
- this.ids,
69
- columns,
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 { GroupedCounts, JoiningColumns, Mapper, RelationFetcher, SelectGroupedObjects } from '../../mapper'
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
- this.mapper,
24
- this.objectNode,
25
- targetEntity,
26
- relation,
27
- targetRelation,
28
- this.ids,
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
- this.mapper,
45
- this.objectNode,
46
- targetEntity,
47
- relation,
48
- relation,
49
- columns,
50
- this.ids,
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
- this.mapper,
67
- this.objectNode,
68
- targetEntity,
69
- relation,
70
- targetRelation,
71
- columns,
72
- this.ids,
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, DependencyCollector } from './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
  }
@@ -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>): Promise<SelectResultObject[]>
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.Relation,
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('whereBuilder', ({ joinBuilder, conditionBuilder, pathFactory }) =>
64
- new WhereBuilder(schema.model, joinBuilder, conditionBuilder, pathFactory))
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 }) =>