@contember/engine-content-api 1.1.1 → 1.2.0-alpha.10

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 (198) hide show
  1. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts +9 -0
  2. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts.map +1 -0
  3. package/dist/src/acl/EvaluatedPredicateReplacer.js +31 -0
  4. package/dist/src/acl/EvaluatedPredicateReplacer.js.map +1 -0
  5. package/dist/src/acl/PermissionFactory.d.ts.map +1 -1
  6. package/dist/src/acl/PermissionFactory.js +15 -11
  7. package/dist/src/acl/PermissionFactory.js.map +1 -1
  8. package/dist/src/acl/PredicateFactory.d.ts +4 -2
  9. package/dist/src/acl/PredicateFactory.d.ts.map +1 -1
  10. package/dist/src/acl/PredicateFactory.js +24 -6
  11. package/dist/src/acl/PredicateFactory.js.map +1 -1
  12. package/dist/src/acl/PredicatesInjector.d.ts +1 -1
  13. package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
  14. package/dist/src/acl/PredicatesInjector.js +18 -14
  15. package/dist/src/acl/PredicatesInjector.js.map +1 -1
  16. package/dist/src/acl/VariableInjector.d.ts +0 -1
  17. package/dist/src/acl/VariableInjector.d.ts.map +1 -1
  18. package/dist/src/acl/VariableInjector.js +4 -14
  19. package/dist/src/acl/VariableInjector.js.map +1 -1
  20. package/dist/src/acl/VariableUtils.d.ts +2 -13
  21. package/dist/src/acl/VariableUtils.d.ts.map +1 -1
  22. package/dist/src/acl/VariableUtils.js +5 -16
  23. package/dist/src/acl/VariableUtils.js.map +1 -1
  24. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js +1 -1
  25. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js.map +1 -1
  26. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.d.ts.map +1 -1
  27. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js +25 -13
  28. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.js.map +1 -1
  29. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.d.ts.map +1 -1
  30. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js +26 -13
  31. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.js.map +1 -1
  32. package/dist/src/input-validation/ValidationContext.d.ts +2 -2
  33. package/dist/src/input-validation/ValidationContext.d.ts.map +1 -1
  34. package/dist/src/input-validation/ValidationContext.js.map +1 -1
  35. package/dist/src/input-validation/ValidationDataSelector.d.ts.map +1 -1
  36. package/dist/src/input-validation/ValidationDataSelector.js +1 -1
  37. package/dist/src/input-validation/ValidationDataSelector.js.map +1 -1
  38. package/dist/src/introspection/content-schema.types.d.ts +19 -223
  39. package/dist/src/introspection/content-schema.types.d.ts.map +1 -1
  40. package/dist/src/introspection/content-schema.types.js +2 -2
  41. package/dist/src/introspection/content-schema.types.js.map +1 -1
  42. package/dist/src/mapper/Mapper.d.ts +4 -4
  43. package/dist/src/mapper/Mapper.d.ts.map +1 -1
  44. package/dist/src/mapper/Mapper.js +7 -7
  45. package/dist/src/mapper/Mapper.js.map +1 -1
  46. package/dist/src/mapper/MapperContainer.d.ts.map +1 -1
  47. package/dist/src/mapper/MapperContainer.js +5 -2
  48. package/dist/src/mapper/MapperContainer.js.map +1 -1
  49. package/dist/src/mapper/select/RelationFetcher.d.ts +40 -5
  50. package/dist/src/mapper/select/RelationFetcher.d.ts.map +1 -1
  51. package/dist/src/mapper/select/RelationFetcher.js +19 -6
  52. package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
  53. package/dist/src/mapper/select/WhereBuilder.d.ts +3 -1
  54. package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
  55. package/dist/src/mapper/select/WhereBuilder.js +4 -2
  56. package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
  57. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts +2 -2
  58. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts.map +1 -1
  59. package/dist/src/mapper/select/handlers/FieldsVisitor.js +31 -18
  60. package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
  61. package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts +5 -0
  62. package/dist/src/mapper/select/optimizer/ConditionOptimizer.d.ts.map +1 -0
  63. package/dist/src/mapper/select/optimizer/ConditionOptimizer.js +33 -0
  64. package/dist/src/mapper/select/optimizer/ConditionOptimizer.js.map +1 -0
  65. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts +14 -0
  66. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -0
  67. package/dist/src/mapper/select/optimizer/WhereOptimizer.js +86 -0
  68. package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -0
  69. package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts +3 -0
  70. package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts.map +1 -0
  71. package/dist/src/mapper/select/optimizer/WhereReplacer.js +27 -0
  72. package/dist/src/mapper/select/optimizer/WhereReplacer.js.map +1 -0
  73. package/dist/src/mapper/select/optimizer/helpers.d.ts +10 -0
  74. package/dist/src/mapper/select/optimizer/helpers.d.ts.map +1 -0
  75. package/dist/src/mapper/select/optimizer/helpers.js +72 -0
  76. package/dist/src/mapper/select/optimizer/helpers.js.map +1 -0
  77. package/dist/src/resolvers/GraphQlQueryAstFactory.js +2 -2
  78. package/dist/src/resolvers/GraphQlQueryAstFactory.js.map +1 -1
  79. package/dist/src/resolvers/MutationResolver.js +15 -15
  80. package/dist/src/resolvers/MutationResolver.js.map +1 -1
  81. package/dist/src/resolvers/ReadHelpers.js +10 -10
  82. package/dist/src/resolvers/ReadHelpers.js.map +1 -1
  83. package/dist/src/resolvers/ReadResolver.js +2 -2
  84. package/dist/src/resolvers/ReadResolver.js.map +1 -1
  85. package/dist/src/schema/MutationProvider.js +4 -4
  86. package/dist/src/schema/MutationProvider.js.map +1 -1
  87. package/dist/src/schema/QueryProvider.js +3 -3
  88. package/dist/src/schema/QueryProvider.js.map +1 -1
  89. package/dist/src/schema/ResultSchemaTypeProvider.d.ts +2 -2
  90. package/dist/src/schema/ResultSchemaTypeProvider.d.ts.map +1 -1
  91. package/dist/src/schema/entities/FieldArgsVisitor.d.ts +4 -4
  92. package/dist/src/schema/entities/FieldArgsVisitor.d.ts.map +1 -1
  93. package/dist/src/tsconfig.tsbuildinfo +1 -1
  94. package/dist/src/utils/object.d.ts.map +1 -1
  95. package/dist/src/utils/object.js +3 -4
  96. package/dist/src/utils/object.js.map +1 -1
  97. package/dist/tests/cases/integration/graphQlRequests/delete/aclDelete.test.js +1 -1
  98. package/dist/tests/cases/integration/graphQlRequests/delete/aclDelete.test.js.map +1 -1
  99. package/dist/tests/cases/integration/graphQlRequests/insert/acl/setField.test.js +2 -2
  100. package/dist/tests/cases/integration/graphQlRequests/insert/acl/setField.test.js.map +1 -1
  101. package/dist/tests/cases/integration/graphQlRequests/insert/aclInsert.test.js +1 -1
  102. package/dist/tests/cases/integration/graphQlRequests/insert/aclInsert.test.js.map +1 -1
  103. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js +7 -6
  104. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js.map +1 -1
  105. package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js +1 -1
  106. package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js.map +1 -1
  107. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts +2 -0
  108. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.d.ts.map +1 -0
  109. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js +137 -0
  110. package/dist/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.js.map +1 -0
  111. package/dist/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.js +1 -1
  112. package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js +1 -1
  113. package/dist/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.js.map +1 -1
  114. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js +73 -0
  115. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.js.map +1 -1
  116. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js +73 -0
  117. package/dist/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.js.map +1 -1
  118. package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js +6 -6
  119. package/dist/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.js.map +1 -1
  120. package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js +6 -3
  121. package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js.map +1 -1
  122. package/dist/tests/cases/integration/graphQlSchema/model.js.map +1 -1
  123. package/dist/tests/cases/unit/aclVariablesFactory.test.js +7 -33
  124. package/dist/tests/cases/unit/aclVariablesFactory.test.js.map +1 -1
  125. package/dist/tests/cases/unit/conditionOptimizer.test.d.ts +2 -0
  126. package/dist/tests/cases/unit/conditionOptimizer.test.d.ts.map +1 -0
  127. package/dist/tests/cases/unit/conditionOptimizer.test.js +118 -0
  128. package/dist/tests/cases/unit/conditionOptimizer.test.js.map +1 -0
  129. package/dist/tests/cases/unit/orderbyHelper.test.js +1 -0
  130. package/dist/tests/cases/unit/orderbyHelper.test.js.map +1 -1
  131. package/dist/tests/cases/unit/permissionMerger.test.js +2 -0
  132. package/dist/tests/cases/unit/permissionMerger.test.js.map +1 -1
  133. package/dist/tests/cases/unit/predicatesFactory.test.d.ts +2 -0
  134. package/dist/tests/cases/unit/predicatesFactory.test.d.ts.map +1 -0
  135. package/dist/tests/cases/unit/predicatesFactory.test.js +103 -0
  136. package/dist/tests/cases/unit/predicatesFactory.test.js.map +1 -0
  137. package/dist/tests/cases/unit/predicatesInjector.test.js +88 -14
  138. package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
  139. package/dist/tests/cases/unit/variableInjector.test.js +4 -3
  140. package/dist/tests/cases/unit/variableInjector.test.js.map +1 -1
  141. package/dist/tests/cases/unit/whereOptimizer.test.d.ts +2 -0
  142. package/dist/tests/cases/unit/whereOptimizer.test.d.ts.map +1 -0
  143. package/dist/tests/cases/unit/whereOptimizer.test.js +183 -0
  144. package/dist/tests/cases/unit/whereOptimizer.test.js.map +1 -0
  145. package/dist/tests/src/testGraphql.d.ts.map +1 -1
  146. package/dist/tests/src/testGraphql.js +10 -2
  147. package/dist/tests/src/testGraphql.js.map +1 -1
  148. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  149. package/graphql.codegen.yml +0 -3
  150. package/package.json +16 -16
  151. package/src/acl/EvaluatedPredicateReplacer.ts +32 -0
  152. package/src/acl/PermissionFactory.ts +18 -13
  153. package/src/acl/PredicateFactory.ts +39 -8
  154. package/src/acl/PredicatesInjector.ts +26 -13
  155. package/src/acl/VariableInjector.ts +2 -15
  156. package/src/acl/VariableUtils.ts +7 -23
  157. package/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.ts +3 -3
  158. package/src/extensions/paginatedHasMany/PaginatedHasManyCountVisitor.ts +26 -28
  159. package/src/extensions/paginatedHasMany/PaginatedHasManyNodesVisitor.ts +27 -31
  160. package/src/input-validation/ValidationContext.ts +2 -2
  161. package/src/input-validation/ValidationDataSelector.ts +2 -2
  162. package/src/introspection/content-schema.types.ts +19 -286
  163. package/src/mapper/Mapper.ts +10 -6
  164. package/src/mapper/MapperContainer.ts +7 -3
  165. package/src/mapper/select/RelationFetcher.ts +95 -34
  166. package/src/mapper/select/WhereBuilder.ts +5 -2
  167. package/src/mapper/select/handlers/FieldsVisitor.ts +31 -24
  168. package/src/mapper/select/optimizer/ConditionOptimizer.ts +35 -0
  169. package/src/mapper/select/optimizer/WhereOptimizer.ts +100 -0
  170. package/src/mapper/select/optimizer/WhereReplacer.ts +22 -0
  171. package/src/mapper/select/optimizer/helpers.ts +58 -0
  172. package/src/resolvers/GraphQlQueryAstFactory.ts +3 -3
  173. package/src/resolvers/MutationResolver.ts +1 -1
  174. package/src/resolvers/ReadHelpers.ts +3 -3
  175. package/src/resolvers/ReadResolver.ts +2 -2
  176. package/src/utils/object.ts +6 -6
  177. package/tests/cases/integration/graphQlRequests/delete/aclDelete.test.ts +2 -2
  178. package/tests/cases/integration/graphQlRequests/insert/acl/setField.test.ts +3 -3
  179. package/tests/cases/integration/graphQlRequests/insert/aclInsert.test.ts +2 -2
  180. package/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.ts +7 -6
  181. package/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.ts +1 -1
  182. package/tests/cases/integration/graphQlRequests/query/acl/predicateElimination.test.ts +130 -0
  183. package/tests/cases/integration/graphQlRequests/query/filter/complexCondition.test.ts +1 -1
  184. package/tests/cases/integration/graphQlRequests/query/filter/composedFilter.test.ts +1 -1
  185. package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyInverse.test.ts +74 -0
  186. package/tests/cases/integration/graphQlRequests/query/relations/manyHasManyOwning.test.ts +77 -0
  187. package/tests/cases/integration/graphQlRequests/update/acl/aclUpdate.test.ts +7 -7
  188. package/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.ts +5 -5
  189. package/tests/cases/unit/aclVariablesFactory.test.ts +8 -35
  190. package/tests/cases/unit/conditionOptimizer.test.ts +135 -0
  191. package/tests/cases/unit/orderbyHelper.test.ts +1 -0
  192. package/tests/cases/unit/permissionMerger.test.ts +2 -0
  193. package/tests/cases/unit/predicatesFactory.test.ts +97 -0
  194. package/tests/cases/unit/predicatesInjector.test.ts +78 -16
  195. package/tests/cases/unit/variableInjector.test.ts +6 -5
  196. package/tests/cases/unit/whereOptimizer.test.ts +195 -0
  197. package/tests/src/testGraphql.ts +9 -2
  198. package/jasmine.json +0 -7
@@ -5,6 +5,7 @@ import { Path, PathFactory } from './Path'
5
5
  import { JoinBuilder } from './JoinBuilder'
6
6
  import { ConditionBuilder } from './ConditionBuilder'
7
7
  import { ConditionBuilder as SqlConditionBuilder, Operator, QueryBuilder, SelectBuilder } from '@contember/database'
8
+ import { WhereOptimizer } from './optimizer/WhereOptimizer'
8
9
 
9
10
  export class WhereBuilder {
10
11
  constructor(
@@ -12,6 +13,7 @@ export class WhereBuilder {
12
13
  private readonly joinBuilder: JoinBuilder,
13
14
  private readonly conditionBuilder: ConditionBuilder,
14
15
  private readonly pathFactory: PathFactory,
16
+ private readonly whereOptimizer: WhereOptimizer,
15
17
  ) {}
16
18
 
17
19
  public build(
@@ -31,9 +33,10 @@ export class WhereBuilder {
31
33
  callback: (clauseCb: (clause: SqlConditionBuilder) => SqlConditionBuilder) => SelectBuilder<SelectBuilder.Result>,
32
34
  allowManyJoin: boolean = false,
33
35
  ) {
36
+ const optimizedWhere = this.whereOptimizer.optimize(where, entity)
34
37
  const joinList: WhereJoinDefinition[] = []
35
38
 
36
- const qbWithWhere = callback(clause => this.buildInternal(clause, entity, path, where, allowManyJoin, joinList))
39
+ const qbWithWhere = callback(clause => this.buildInternal(clause, entity, path, optimizedWhere, allowManyJoin, joinList))
37
40
  return joinList.reduce<SelectBuilder<SelectBuilder.Result>>(
38
41
  (qb, { path, entity, relationName }) => this.joinBuilder.join(qb, path, entity, relationName),
39
42
  qbWithWhere,
@@ -44,7 +47,7 @@ export class WhereBuilder {
44
47
  conditionBuilder: SqlConditionBuilder,
45
48
  entity: Model.Entity,
46
49
  path: Path,
47
- where: Input.OptionalWhere,
50
+ where: Input.Where,
48
51
  allowManyJoin: boolean,
49
52
  joinList: WhereJoinDefinition[],
50
53
  ): SqlConditionBuilder {
@@ -4,7 +4,6 @@ import { RelationFetcher } from '../RelationFetcher'
4
4
  import { SelectExecutionHandlerContext } from '../SelectExecutionHandler'
5
5
  import { PredicateFactory } from '../../../acl'
6
6
  import { WhereBuilder } from '../WhereBuilder'
7
- import { JoiningColumns } from '../../types'
8
7
 
9
8
  export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.ColumnVisitor<void> {
10
9
  constructor(
@@ -56,11 +55,6 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
56
55
  targetEntity: Model.Entity,
57
56
  targetRelation: Model.ManyHasManyOwningRelation,
58
57
  ): void {
59
- const joiningTable = targetRelation.joiningTable
60
- const columns: JoiningColumns = {
61
- sourceColumn: joiningTable.inverseJoiningColumn,
62
- targetColumn: joiningTable.joiningColumn,
63
- }
64
58
  const field = this.executionContext.objectNode
65
59
  if (!field) {
66
60
  throw new Error()
@@ -69,13 +63,15 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
69
63
  entity.primary,
70
64
  async ids =>
71
65
  this.relationFetcher.fetchManyHasManyGroups(
72
- this.mapper,
73
- field,
74
- targetEntity,
75
- relation,
76
- targetRelation,
77
- columns,
78
- ids,
66
+ {
67
+ mapper: this.mapper,
68
+ field: field,
69
+ targetEntity: targetEntity,
70
+ sourceRelation: relation,
71
+ targetRelation,
72
+ directionFrom: 'inverse',
73
+ ids: ids,
74
+ },
79
75
  ),
80
76
  [],
81
77
  )
@@ -85,12 +81,8 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
85
81
  entity: Model.Entity,
86
82
  relation: Model.ManyHasManyOwningRelation,
87
83
  targetEntity: Model.Entity,
84
+ targetRelation: Model.ManyHasManyInverseRelation | null,
88
85
  ): void {
89
- const joiningTable = relation.joiningTable
90
- const columns: JoiningColumns = {
91
- sourceColumn: joiningTable.joiningColumn,
92
- targetColumn: joiningTable.inverseJoiningColumn,
93
- }
94
86
  const field = this.executionContext.objectNode
95
87
  if (!field) {
96
88
  throw new Error()
@@ -99,7 +91,15 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
99
91
  this.executionContext.addData(
100
92
  entity.primary,
101
93
  async ids =>
102
- this.relationFetcher.fetchManyHasManyGroups(this.mapper, field, targetEntity, relation, relation, columns, ids),
94
+ this.relationFetcher.fetchManyHasManyGroups({
95
+ mapper: this.mapper,
96
+ field: field,
97
+ targetEntity: targetEntity,
98
+ sourceRelation: relation,
99
+ targetRelation,
100
+ directionFrom: 'owning',
101
+ ids: ids,
102
+ }),
103
103
  [],
104
104
  )
105
105
  }
@@ -118,7 +118,14 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
118
118
  this.executionContext.addData(
119
119
  entity.primary,
120
120
  async ids =>
121
- this.relationFetcher.fetchOneHasManyGroups(this.mapper, field, targetEntity, relation, targetRelation, ids),
121
+ this.relationFetcher.fetchOneHasManyGroups({
122
+ mapper: this.mapper,
123
+ objectNode: field,
124
+ targetEntity: targetEntity,
125
+ relation: relation,
126
+ targetRelation: targetRelation,
127
+ ids: ids,
128
+ }),
122
129
  [],
123
130
  )
124
131
  }
@@ -148,7 +155,7 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
148
155
  }
149
156
  const objectWithWhere = field.withArg('filter', where)
150
157
 
151
- return this.mapper.select(targetEntity, objectWithWhere, targetRelation.name)
158
+ return this.mapper.select(targetEntity, objectWithWhere, targetRelation, targetRelation.name)
152
159
  },
153
160
  null,
154
161
  )
@@ -177,13 +184,13 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
177
184
  }
178
185
  const objectWithWhere = objectNode.withArg('filter', where)
179
186
 
180
- return this.mapper.select(targetEntity, objectWithWhere, targetEntity.primary)
187
+ return this.mapper.select(targetEntity, objectWithWhere, targetRelation, targetEntity.primary)
181
188
  },
182
189
  null,
183
190
  )
184
191
  }
185
192
 
186
- public visitManyHasOne(entity: Model.Entity, relation: Model.ManyHasOneRelation, targetEntity: Model.Entity): void {
193
+ public visitManyHasOne(entity: Model.Entity, relation: Model.ManyHasOneRelation, targetEntity: Model.Entity, targetRelation: Model.OneHasManyRelation | null): void {
187
194
  this.executionContext.addData(
188
195
  relation.name,
189
196
  async ids => {
@@ -201,7 +208,7 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
201
208
  }
202
209
  const objectWithWhere = objectNode.withArg('filter', where)
203
210
 
204
- return this.mapper.select(targetEntity, objectWithWhere, targetEntity.primary)
211
+ return this.mapper.select(targetEntity, objectWithWhere, targetRelation, targetEntity.primary)
205
212
  },
206
213
  null,
207
214
  )
@@ -0,0 +1,35 @@
1
+ import { Input } from '@contember/schema'
2
+ import { optimizeAnd, optimizeNot, optimizeOr } from './helpers'
3
+
4
+ type ObjectEntry<T> = { [K in keyof Required<T>]: [K, T[K]] }[keyof T]
5
+
6
+ export class ConditionOptimizer {
7
+ public optimize(condition: Input.Condition): Omit<Input.Condition, 'always' | 'never'> | boolean {
8
+ if (condition.never) {
9
+ return false
10
+ }
11
+
12
+ return optimizeAnd(
13
+ (Object.entries(condition) as ObjectEntry<typeof condition>[]).map(([key, value]) => {
14
+ if (value === undefined || value === null) {
15
+ return undefined
16
+
17
+ } else if (key === 'always') {
18
+ return true
19
+
20
+ } else if (key === 'and') {
21
+ return optimizeAnd(value.map(it => this.optimize(it)))
22
+
23
+ } else if (key === 'or') {
24
+ return optimizeOr(value.map(it => this.optimize(it)))
25
+
26
+ } else if (key === 'not') {
27
+ return optimizeNot(this.optimize(value))
28
+
29
+ } else {
30
+ return { [key]: value }
31
+ }
32
+ }),
33
+ )
34
+ }
35
+ }
@@ -0,0 +1,100 @@
1
+ import { Input, Model } from '@contember/schema'
2
+ import { ConditionOptimizer } from './ConditionOptimizer'
3
+ import { acceptFieldVisitor } from '@contember/schema-utils'
4
+ import { optimizeAnd, optimizeNot, optimizeOr } from './helpers'
5
+ import { replaceWhere } from './WhereReplacer'
6
+
7
+ export class WhereOptimizer {
8
+ constructor(
9
+ private readonly model: Model.Schema,
10
+ private readonly conditionOptimizer: ConditionOptimizer,
11
+ ) {
12
+ }
13
+
14
+ public optimize(where: Input.OptionalWhere, entity: Model.Entity): Input.Where {
15
+ const result = this.optimizeWhere(where, entity)
16
+
17
+ if (typeof result === 'boolean') {
18
+ return { [entity.primary]: { [result ? 'always' : 'never']: true } }
19
+ }
20
+
21
+ return result
22
+ }
23
+
24
+ private optimizeWhere(where: Input.OptionalWhere, entity: Model.Entity): Input.Where | boolean {
25
+ return this.optimizeAnd(
26
+ Object.entries(where).map(([key, value]) => {
27
+ if (value === undefined || value === null) {
28
+ return undefined
29
+
30
+ } else if (key === 'and') {
31
+ return this.optimizeAnd((value as readonly Input.Where[]).map(it => this.optimizeWhere(it, entity)), entity)
32
+
33
+ } else if (key === 'or') {
34
+ return this.optimizeOr((value as readonly Input.Where[]).map(it => this.optimizeWhere(it, entity)), entity)
35
+
36
+ } else if (key === 'not') {
37
+ return optimizeNot(this.optimizeWhere(value as Input.Where, entity))
38
+
39
+ } else {
40
+ return this.resolveFieldValue(entity, key, value)
41
+ }
42
+ }),
43
+ entity,
44
+ )
45
+ }
46
+
47
+ private optimizeOr(operands: readonly (Input.Where | undefined | boolean)[], entity: Model.Entity): Input.Where | boolean {
48
+ const optimized = optimizeOr(operands)
49
+ if (typeof optimized === 'boolean' || !Array.isArray(optimized.or)) {
50
+ return optimized
51
+ }
52
+ const result = this.crossOptimize(optimized.or, entity, { never: true })
53
+ return optimizeOr(result.map(it => this.optimizeWhere(it, entity)))
54
+ }
55
+
56
+ private optimizeAnd(operands: readonly (Input.Where | undefined | boolean)[], entity: Model.Entity): Input.Where | boolean {
57
+ const optimized = optimizeAnd(operands)
58
+ if (typeof optimized === 'boolean' || !Array.isArray(optimized.and)) {
59
+ return optimized
60
+ }
61
+ const result = this.crossOptimize(optimized.and, entity, { always: true })
62
+ return optimizeAnd(result.map(it => this.optimizeWhere(it, entity)))
63
+ }
64
+
65
+ private crossOptimize(operands: Input.Where[], entity: Model.Entity, replacement: Input.Condition): Input.Where[] {
66
+ const result = [...operands]
67
+ const count = result.length
68
+ for (let i = 0; i < count; i++) {
69
+ for (let j = 0; j < count; j++) {
70
+ if (i !== j) {
71
+ result[j] = replaceWhere(result[j], result[i], { [entity.primary]: replacement })
72
+ }
73
+ }
74
+ }
75
+ return result
76
+ }
77
+
78
+ private resolveFieldValue(entity: Model.Entity, key: string, value: Input.OptionalWhere[string]) {
79
+ return acceptFieldVisitor<Input.Where | boolean>(this.model, entity, key, {
80
+ visitColumn: () => {
81
+ const optimizedCondition = this.conditionOptimizer.optimize(value as Input.Condition)
82
+
83
+ if (typeof optimizedCondition === 'boolean') {
84
+ return optimizedCondition
85
+ }
86
+
87
+ return { [key]: optimizedCondition }
88
+ },
89
+ visitRelation: (entity, relation, targetEntity) => {
90
+ const optimizedWhere = this.optimizeWhere(value as Input.Where, targetEntity)
91
+
92
+ if (typeof optimizedWhere === 'boolean') {
93
+ return optimizedWhere
94
+ }
95
+
96
+ return { [key]: optimizedWhere }
97
+ },
98
+ })
99
+ }
100
+ }
@@ -0,0 +1,22 @@
1
+ import { Input, Writable } from '@contember/schema'
2
+ import deepEqual from 'fast-deep-equal'
3
+
4
+ export const replaceWhere = (subject: Input.Where, find: Input.Where, replace: Input.Where): Input.Where => {
5
+ if (deepEqual(subject, find)) {
6
+ return replace
7
+ }
8
+ const result: Writable<Input.Where> = {}
9
+ for (const [key, value] of Object.entries(subject)) {
10
+ if (key === 'not') {
11
+ result['not'] = replaceWhere(value as Input.Where, find, replace)
12
+
13
+ } else if (key === 'and' || key === 'or') {
14
+ result[key] = (value as Input.Where[]).map(it => replaceWhere(it, find, replace))
15
+
16
+ } else {
17
+ result[key] = value
18
+ }
19
+
20
+ }
21
+ return result
22
+ }
@@ -0,0 +1,58 @@
1
+ export const optimizeOr = <P extends Record<string, unknown>>(operands: readonly (P | boolean | undefined)[]): P | { or?: P[] } | boolean => {
2
+ const normalized: P[] = []
3
+ let hasNever = false
4
+ for (const operand of operands) {
5
+ if (operand === true) {
6
+ return true
7
+ } else if (operand === false) {
8
+ hasNever = true
9
+ } else if (operand !== undefined) {
10
+ normalized.push(...Array.isArray(operand.or) ? operand.or : [operand])
11
+ }
12
+ }
13
+
14
+ if (normalized.length > 1) {
15
+ return { or: normalized }
16
+ } else if (normalized.length === 1) {
17
+ return normalized[0]
18
+ } else if (hasNever) {
19
+ return false
20
+ } else {
21
+ return {}
22
+ }
23
+ }
24
+
25
+ export const optimizeAnd = <P extends Record<string, unknown>>(operands: readonly (P | boolean | undefined)[]): P | { and?: P[] } | boolean => {
26
+ const normalized: P[] = []
27
+ let hasAlways = false
28
+
29
+ for (const operand of operands) {
30
+ if (operand === true) {
31
+ hasAlways = true
32
+ } else if (operand === false) {
33
+ return false
34
+ } else if (operand !== undefined) {
35
+ normalized.push(...Array.isArray(operand.and) ? operand.and : [operand])
36
+ }
37
+ }
38
+
39
+ if (normalized.length > 1) {
40
+ return { and: normalized }
41
+ } else if (normalized.length === 1) {
42
+ return normalized[0]
43
+ } else if (hasAlways) {
44
+ return true
45
+ } else {
46
+ return {}
47
+ }
48
+ }
49
+
50
+ export const optimizeNot = <P extends Record<string, unknown>>(operand: P | boolean): P['not'] | { not: P } | boolean => {
51
+ if (typeof operand === 'boolean') {
52
+ return !operand
53
+ } else if (operand.not) {
54
+ return operand.not as P['not']
55
+ } else {
56
+ return { not: operand }
57
+ }
58
+ }
@@ -7,7 +7,7 @@ import {
7
7
  GraphQLResolveInfo,
8
8
  isListType,
9
9
  isNonNullType,
10
- isObjectType,
10
+ isObjectType, Kind,
11
11
  } from 'graphql'
12
12
  import { SelectionSetNode } from 'graphql/language/ast'
13
13
  import { getArgumentValues } from 'graphql/execution/values'
@@ -67,13 +67,13 @@ export class GraphQlQueryAstFactory {
67
67
  ): AnyNode[] {
68
68
  const fields: ObjectNode | FieldNode[] = []
69
69
  for (const subNode of selectionSet.selections) {
70
- if (isIt<GraphQlFieldNode>(subNode, 'kind', 'Field')) {
70
+ if (isIt<GraphQlFieldNode>(subNode, 'kind', Kind.FIELD)) {
71
71
  const field = this.createFromNode(info, parentType, subNode, path, filter)
72
72
  if (field === null) {
73
73
  continue
74
74
  }
75
75
  fields.push(field)
76
- } else if (isIt<FragmentSpreadNode>(subNode, 'kind', 'FragmentSpread')) {
76
+ } else if (isIt<FragmentSpreadNode>(subNode, 'kind', Kind.FRAGMENT_SPREAD)) {
77
77
  const fragmentDefinition = info.fragments[subNode.name.value]
78
78
  if (!fragmentDefinition) {
79
79
  throw new Error(`GraphQlQueryAstFactory: Fragment definition ${subNode.name.value} not found`)
@@ -457,7 +457,7 @@ export class MutationResolver {
457
457
  throw new Error('MutationResolver: expected ObjectNode')
458
458
  }
459
459
  const objectWithArgs = singleNodeQuery.withArgs({ by: where })
460
- nodes[singleNodeQuery.alias] = await mapper.selectUnique(entity, objectWithArgs)
460
+ nodes[singleNodeQuery.alias] = await mapper.selectUnique(entity, objectWithArgs, null)
461
461
  }
462
462
  return nodes
463
463
  }
@@ -15,7 +15,7 @@ export const paginate = async (
15
15
  const totalCount = paginationHelper.requiresTotalCount
16
16
  ? await mapper.count(entity, queryAst.args.filter || {})
17
17
  : undefined
18
- const nodes = paginationHelper.nodeField ? await mapper.select(entity, paginationHelper.nodeField) : undefined
18
+ const nodes = paginationHelper.nodeField ? await mapper.select(entity, paginationHelper.nodeField, null) : undefined
19
19
 
20
20
  return paginationHelper.createResponse(totalCount, nodes)
21
21
  }
@@ -37,9 +37,9 @@ export const executeReadOperations = async (
37
37
  trxResult[field.alias] = await (() => {
38
38
  switch (meta.operation) {
39
39
  case Operation.get:
40
- return mapper.selectUnique(meta.entity, field)
40
+ return mapper.selectUnique(meta.entity, field, null)
41
41
  case Operation.list:
42
- return mapper.select(meta.entity, field)
42
+ return mapper.select(meta.entity, field, null)
43
43
  case Operation.paginate:
44
44
  return paginate(mapper, meta.entity, field)
45
45
  case Operation.create:
@@ -33,12 +33,12 @@ export class ReadResolver {
33
33
 
34
34
  public async resolveListQuery(entity: Model.Entity, info: GraphQLResolveInfo) {
35
35
  const queryAst: ObjectNode<Input.ListQueryInput> = this.queryAstFactory.create(info)
36
- return await this.mapperFactory(this.db).select(entity, queryAst)
36
+ return await this.mapperFactory(this.db).select(entity, queryAst, null)
37
37
  }
38
38
 
39
39
  public async resolveGetQuery(entity: Model.Entity, info: GraphQLResolveInfo) {
40
40
  const queryAst: ObjectNode<Input.UniqueQueryInput> = this.queryAstFactory.create(info)
41
- return await this.mapperFactory(this.db).selectUnique(entity, queryAst)
41
+ return await this.mapperFactory(this.db).selectUnique(entity, queryAst, null)
42
42
  }
43
43
 
44
44
  public async resolvePaginationQuery(entity: Model.Entity, info: GraphQLResolveInfo) {
@@ -9,18 +9,18 @@ function filterObject<Input extends {}, Result extends { [Key in keyof Input]: I
9
9
  object: Input,
10
10
  callback: TypeGuard<Input, Result> | Filter<Input>,
11
11
  ): Result {
12
- return (Object.entries(object) as any)
13
- .filter(<K extends keyof Input>(it: [K, Input[K]]): it is [K, Result[K]] =>
14
- (callback as any)(it[0] as K, it[1] as Input[K], object),
15
- )
16
- .reduce((result: Result, [key, value]: [string, any]) => ({ ...(result as any), [key]: value }), {} as Result)
12
+ return Object.fromEntries(Object.entries(object)
13
+ .filter(([key, value]) => (callback as any)(key, value, object)),
14
+ ) as Result
17
15
  }
18
16
 
19
17
  function mapObject<Input, Result>(
20
18
  input: { [key: string]: Input },
21
19
  callback: (value: Input, key: string) => Result,
22
20
  ): { [key: string]: Result } {
23
- return Object.keys(input).reduce((acc, key) => ({ ...acc, [key]: callback(input[key], key) }), {})
21
+ return Object.fromEntries(
22
+ Object.entries(input).map(([key, value]) => [key, callback(value, key)]),
23
+ )
24
24
  }
25
25
 
26
26
  export { filterObject, mapObject }
@@ -1,7 +1,7 @@
1
1
  import { test } from 'vitest'
2
2
  import { execute, sqlDeferred, sqlTransaction } from '../../../../src/test'
3
3
  import { SchemaBuilder } from '@contember/schema-definition'
4
- import { Model } from '@contember/schema'
4
+ import { Acl, Model } from '@contember/schema'
5
5
  import { GQL, SQL } from '../../../../src/tags'
6
6
  import { testUuid } from '../../../../src/testUuid'
7
7
 
@@ -32,7 +32,7 @@ test('delete post with acl', async () => {
32
32
  },
33
33
  },
34
34
  variables: {
35
- locale_variable: ['cs'],
35
+ locale_variable: { in: ['cs'] },
36
36
  },
37
37
  executes: [
38
38
  ...sqlTransaction([
@@ -1,7 +1,7 @@
1
1
  import { test } from 'vitest'
2
2
  import { execute, failedTransaction, sqlTransaction } from '../../../../../src/test'
3
3
  import { SchemaBuilder } from '@contember/schema-definition'
4
- import { Model } from '@contember/schema'
4
+ import { Acl, Model } from '@contember/schema'
5
5
  import { GQL, SQL } from '../../../../../src/tags'
6
6
  import { testUuid } from '../../../../../src/testUuid'
7
7
 
@@ -34,7 +34,7 @@ test('set a name - allowed', async () => {
34
34
  },
35
35
  },
36
36
  variables: {
37
- name_variable: ['John', 'Jack'],
37
+ name_variable: { in: ['John', 'Jack'] },
38
38
  },
39
39
  executes: [
40
40
  ...sqlTransaction([
@@ -89,7 +89,7 @@ test('set a name - denied', async () => {
89
89
  },
90
90
  },
91
91
  variables: {
92
- name_variable: ['John', 'Jack'],
92
+ name_variable: { in: ['John', 'Jack'] },
93
93
  },
94
94
  executes: [
95
95
  ...failedTransaction([
@@ -1,7 +1,7 @@
1
1
  import { test } from 'vitest'
2
2
  import { execute, sqlTransaction } from '../../../../src/test'
3
3
  import { SchemaBuilder } from '@contember/schema-definition'
4
- import { Model } from '@contember/schema'
4
+ import { Acl, Model } from '@contember/schema'
5
5
  import { GQL, SQL } from '../../../../src/tags'
6
6
  import { testUuid } from '../../../../src/testUuid'
7
7
 
@@ -27,7 +27,7 @@ test('insert author (with acl)', async () => {
27
27
  },
28
28
  },
29
29
  variables: {
30
- name_variable: ['John', 'Jack'],
30
+ name_variable: { in: ['John', 'Jack'] },
31
31
  },
32
32
  query: GQL`
33
33
  mutation {
@@ -7,7 +7,7 @@ import { testUuid } from '../../../../../src/testUuid'
7
7
 
8
8
  const schema = new SchemaBuilder()
9
9
  .enum('locale', ['cs', 'en'])
10
- .entity('Post', entityBuilder => entityBuilder.oneHasMany('locales', c => c.target('PostLocale')))
10
+ .entity('Post', entityBuilder => entityBuilder.oneHasMany('locales', c => c.target('PostLocale').ownedBy('post')))
11
11
  .entity('PostLocale', entity =>
12
12
  entity
13
13
  .column('title', column => column.type(Model.ColumnType.String))
@@ -34,6 +34,7 @@ const permissions: Acl.Permissions = {
34
34
  operations: {
35
35
  read: {
36
36
  id: true,
37
+ post: true,
37
38
  title: 'localePredicate',
38
39
  },
39
40
  },
@@ -45,7 +46,7 @@ test('querying id and title', async () => {
45
46
  schema: schema,
46
47
  permissions: permissions,
47
48
  variables: {
48
- localeVariable: ['cs'],
49
+ localeVariable: { in: ['cs'] },
49
50
  },
50
51
  query: GQL`
51
52
  query {
@@ -121,7 +122,7 @@ test('querying with extra where', async () => {
121
122
  schema: schema,
122
123
  permissions: permissions,
123
124
  variables: {
124
- localeVariable: ['cs'],
125
+ localeVariable: { in: ['cs'] },
125
126
  },
126
127
  query: GQL`
127
128
  query {
@@ -167,7 +168,7 @@ test('querying only id', async () => {
167
168
  schema: schema,
168
169
  permissions: permissions,
169
170
  variables: {
170
- localeVariable: ['cs'],
171
+ localeVariable: { in: ['cs'] },
171
172
  },
172
173
  query: GQL`
173
174
  query {
@@ -239,7 +240,7 @@ test('querying on nested field', async () => {
239
240
  schema: schema,
240
241
  permissions: permissions,
241
242
  variables: {
242
- localeVariable: ['cs'],
243
+ localeVariable: { in: ['cs'] },
243
244
  },
244
245
  query: GQL`
245
246
  query {
@@ -274,7 +275,7 @@ test('querying on nested field', async () => {
274
275
  "root_"."id" as "root_id",
275
276
  case when "root_"."locale" in (?) then "root_"."title" else null end as "root_title"
276
277
  from "public"."post_locale" as "root_"
277
- where "root_"."post_id" in (?, ?) and false`,
278
+ where "root_"."post_id" in (?, ?)`,
278
279
  parameters: ['cs', testUuid(1), testUuid(2)],
279
280
  response: {
280
281
  rows: [
@@ -58,7 +58,7 @@ test('querying post + author name', async () => {
58
58
  schema: schema,
59
59
  permissions: permissions,
60
60
  variables: {
61
- countryVariable: ['Czechia'],
61
+ countryVariable: { in: ['Czechia'] },
62
62
  },
63
63
  query: GQL`
64
64
  query {