@contember/engine-content-api 1.2.0-alpha.12 → 1.2.0-alpha.13

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 (100) hide show
  1. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts +2 -2
  2. package/dist/src/acl/EvaluatedPredicateReplacer.d.ts.map +1 -1
  3. package/dist/src/acl/EvaluatedPredicateReplacer.js.map +1 -1
  4. package/dist/src/acl/PredicateFactory.d.ts +9 -3
  5. package/dist/src/acl/PredicateFactory.d.ts.map +1 -1
  6. package/dist/src/acl/PredicateFactory.js +42 -24
  7. package/dist/src/acl/PredicateFactory.js.map +1 -1
  8. package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
  9. package/dist/src/acl/PredicatesInjector.js +1 -0
  10. package/dist/src/acl/PredicatesInjector.js.map +1 -1
  11. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js +31 -27
  12. package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js.map +1 -1
  13. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.js +18 -14
  14. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.js.map +1 -1
  15. package/dist/src/mapper/JunctionTableManager.js.map +1 -1
  16. package/dist/src/mapper/Mapper.js +3 -3
  17. package/dist/src/mapper/Mapper.js.map +1 -1
  18. package/dist/src/mapper/MapperContainer.d.ts.map +1 -1
  19. package/dist/src/mapper/MapperContainer.js +2 -2
  20. package/dist/src/mapper/MapperContainer.js.map +1 -1
  21. package/dist/src/mapper/delete/DeleteExecutor.js +1 -1
  22. package/dist/src/mapper/delete/DeleteExecutor.js.map +1 -1
  23. package/dist/src/mapper/insert/InsertBuilder.d.ts +1 -1
  24. package/dist/src/mapper/insert/InsertBuilder.d.ts.map +1 -1
  25. package/dist/src/mapper/insert/InsertBuilder.js.map +1 -1
  26. package/dist/src/mapper/select/RelationFetcher.js +1 -1
  27. package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
  28. package/dist/src/mapper/select/SelectBuilder.d.ts +3 -1
  29. package/dist/src/mapper/select/SelectBuilder.d.ts.map +1 -1
  30. package/dist/src/mapper/select/SelectBuilder.js +50 -12
  31. package/dist/src/mapper/select/SelectBuilder.js.map +1 -1
  32. package/dist/src/mapper/select/SelectExecutionHandler.d.ts +15 -4
  33. package/dist/src/mapper/select/SelectExecutionHandler.d.ts.map +1 -1
  34. package/dist/src/mapper/select/SelectHydrator.d.ts +3 -2
  35. package/dist/src/mapper/select/SelectHydrator.d.ts.map +1 -1
  36. package/dist/src/mapper/select/SelectHydrator.js +4 -5
  37. package/dist/src/mapper/select/SelectHydrator.js.map +1 -1
  38. package/dist/src/mapper/select/WhereBuilder.d.ts +3 -3
  39. package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
  40. package/dist/src/mapper/select/WhereBuilder.js +4 -4
  41. package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
  42. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts +1 -0
  43. package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts.map +1 -1
  44. package/dist/src/mapper/select/handlers/FieldsVisitor.js +118 -91
  45. package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
  46. package/dist/src/mapper/select/handlers/MetaHandler.d.ts.map +1 -1
  47. package/dist/src/mapper/select/handlers/MetaHandler.js +6 -11
  48. package/dist/src/mapper/select/handlers/MetaHandler.js.map +1 -1
  49. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts +5 -1
  50. package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -1
  51. package/dist/src/mapper/select/optimizer/WhereOptimizer.js +10 -3
  52. package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -1
  53. package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts +1 -1
  54. package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts.map +1 -1
  55. package/dist/src/mapper/select/optimizer/WhereReplacer.js.map +1 -1
  56. package/dist/src/tsconfig.tsbuildinfo +1 -1
  57. package/dist/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.js +51 -4
  58. package/dist/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.js.map +1 -1
  59. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js +26 -25
  60. package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js.map +1 -1
  61. package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js +9 -6
  62. package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js.map +1 -1
  63. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.d.ts +2 -0
  64. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.d.ts.map +1 -0
  65. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js +109 -0
  66. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js.map +1 -0
  67. package/dist/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.js +9 -13
  68. package/dist/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.js.map +1 -1
  69. package/dist/tests/cases/unit/predicatesInjector.test.js +31 -3
  70. package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
  71. package/dist/tests/cases/unit/whereOptimizer.test.js +24 -21
  72. package/dist/tests/cases/unit/whereOptimizer.test.js.map +1 -1
  73. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +10 -10
  75. package/src/acl/EvaluatedPredicateReplacer.ts +4 -4
  76. package/src/acl/PredicateFactory.ts +59 -36
  77. package/src/acl/PredicatesInjector.ts +2 -1
  78. package/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.ts +5 -5
  79. package/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.ts +5 -5
  80. package/src/mapper/JunctionTableManager.ts +3 -3
  81. package/src/mapper/Mapper.ts +3 -3
  82. package/src/mapper/MapperContainer.ts +2 -1
  83. package/src/mapper/delete/DeleteExecutor.ts +1 -1
  84. package/src/mapper/insert/InsertBuilder.ts +2 -2
  85. package/src/mapper/select/RelationFetcher.ts +1 -1
  86. package/src/mapper/select/SelectBuilder.ts +69 -13
  87. package/src/mapper/select/SelectExecutionHandler.ts +15 -8
  88. package/src/mapper/select/SelectHydrator.ts +13 -7
  89. package/src/mapper/select/WhereBuilder.ts +5 -5
  90. package/src/mapper/select/handlers/FieldsVisitor.ts +53 -67
  91. package/src/mapper/select/handlers/MetaHandler.ts +6 -17
  92. package/src/mapper/select/optimizer/WhereOptimizer.ts +25 -13
  93. package/src/mapper/select/optimizer/WhereReplacer.ts +4 -4
  94. package/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.ts +54 -4
  95. package/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.ts +26 -25
  96. package/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.ts +9 -6
  97. package/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.ts +104 -0
  98. package/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.ts +9 -13
  99. package/tests/cases/unit/predicatesInjector.test.ts +37 -7
  100. package/tests/cases/unit/whereOptimizer.test.ts +20 -17
@@ -18,35 +18,12 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
18
18
 
19
19
  visitColumn({ entity, column }: Model.ColumnContext): void {
20
20
  const columnPath = this.executionContext.path
21
- this.executionContext.addColumn(qb => {
22
- const tableAlias = columnPath.back().alias
23
- const columnAlias = columnPath.alias
21
+ const tableAlias = columnPath.back().alias
22
+ const columnAlias = columnPath.alias
24
23
 
25
- const fieldPredicate = this.predicateFactory.shouldApplyCellLevelPredicate(
26
- entity,
27
- Acl.Operation.read,
28
- column.name,
29
- )
30
- ? this.predicateFactory.create(entity, Acl.Operation.read, [column.name])
31
- : undefined
32
-
33
- if (!fieldPredicate || Object.keys(fieldPredicate).length === 0) {
34
- return qb.select([tableAlias, column.columnName], columnAlias)
35
- }
36
- return this.whereBuilder.buildAdvanced(entity, columnPath.back(), fieldPredicate, cb =>
37
- qb.select(
38
- expr =>
39
- expr.case(caseExpr =>
40
- caseExpr
41
- .when(
42
- whenExpr => whenExpr.selectCondition(cb),
43
- thenExpr => thenExpr.select([tableAlias, column.columnName]),
44
- )
45
- .else(elseExpr => elseExpr.raw('null')),
46
- ),
47
- columnAlias,
48
- ),
49
- )
24
+ this.executionContext.addColumn({
25
+ query: qb => qb.select([tableAlias, column.columnName], columnAlias),
26
+ predicate: this.getRequiredPredicate(entity, column),
50
27
  })
51
28
  }
52
29
 
@@ -55,20 +32,19 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
55
32
  if (!field) {
56
33
  throw new Error()
57
34
  }
58
- this.executionContext.addData(
59
- relationContext.entity.primary,
60
- async ids =>
61
- this.relationFetcher.fetchManyHasManyGroups(
62
- {
63
- mapper: this.mapper,
64
- field: field,
65
- relationContext,
66
- relationPath: this.relationPath,
67
- ids: ids,
68
- },
69
- ),
70
- [],
71
- )
35
+ this.executionContext.addData({
36
+ field: relationContext.entity.primary,
37
+ dataProvider: async ids =>
38
+ this.relationFetcher.fetchManyHasManyGroups({
39
+ mapper: this.mapper,
40
+ field: field,
41
+ relationContext,
42
+ relationPath: this.relationPath,
43
+ ids: ids,
44
+ }),
45
+ defaultValue: [],
46
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
47
+ })
72
48
  }
73
49
 
74
50
  public visitManyHasManyOwning(relationContext: Model.ManyHasManyOwningContext): void {
@@ -77,9 +53,9 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
77
53
  throw new Error()
78
54
  }
79
55
 
80
- this.executionContext.addData(
81
- relationContext.entity.primary,
82
- async ids =>
56
+ this.executionContext.addData({
57
+ field: relationContext.entity.primary,
58
+ dataProvider: async ids =>
83
59
  this.relationFetcher.fetchManyHasManyGroups({
84
60
  mapper: this.mapper,
85
61
  field: field,
@@ -87,8 +63,9 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
87
63
  relationPath: this.relationPath,
88
64
  ids: ids,
89
65
  }),
90
- [],
91
- )
66
+ defaultValue: [],
67
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
68
+ })
92
69
  }
93
70
 
94
71
  public visitOneHasMany(relationContext: Model.OneHasManyContext): void {
@@ -97,9 +74,9 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
97
74
  throw new Error()
98
75
  }
99
76
 
100
- this.executionContext.addData(
101
- relationContext.entity.primary,
102
- async ids =>
77
+ this.executionContext.addData({
78
+ field: relationContext.entity.primary,
79
+ dataProvider: async ids =>
103
80
  this.relationFetcher.fetchOneHasManyGroups({
104
81
  mapper: this.mapper,
105
82
  objectNode: field,
@@ -107,15 +84,16 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
107
84
  relationPath: this.relationPath,
108
85
  ids: ids,
109
86
  }),
110
- [],
111
- )
87
+ defaultValue: [],
88
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
89
+ })
112
90
  }
113
91
 
114
92
  public visitOneHasOneInverse(relationContext: Model.OneHasOneInverseContext): void {
115
93
  const { entity, targetRelation, targetEntity } = relationContext
116
- this.executionContext.addData(
117
- entity.primary,
118
- async ids => {
94
+ this.executionContext.addData({
95
+ field: entity.primary,
96
+ dataProvider: async ids => {
119
97
  const idsWhere: Input.Where = {
120
98
  [targetRelation.name]: {
121
99
  [entity.primary]: {
@@ -137,15 +115,16 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
137
115
  relationContext,
138
116
  ], targetRelation.name)
139
117
  },
140
- null,
141
- )
118
+ defaultValue: null,
119
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
120
+ })
142
121
  }
143
122
 
144
123
  public visitOneHasOneOwning(relationContext: Model.OneHasOneOwningContext): void {
145
124
  const { relation, targetEntity } = relationContext
146
- this.executionContext.addData(
147
- relation.name,
148
- async ids => {
125
+ this.executionContext.addData({
126
+ field: relation.name,
127
+ dataProvider: async ids => {
149
128
  const idsWhere: Input.Where = {
150
129
  [targetEntity.primary]: {
151
130
  in: ids,
@@ -165,15 +144,16 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
165
144
  relationContext,
166
145
  ], targetEntity.primary)
167
146
  },
168
- null,
169
- )
147
+ defaultValue: null,
148
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
149
+ })
170
150
  }
171
151
 
172
152
  public visitManyHasOne(relationContext: Model.ManyHasOneContext): void {
173
153
  const { relation, targetEntity } = relationContext
174
- this.executionContext.addData(
175
- relation.name,
176
- async ids => {
154
+ this.executionContext.addData({
155
+ field: relation.name,
156
+ dataProvider: async ids => {
177
157
  const idsWhere: Input.Where = {
178
158
  [targetEntity.primary]: {
179
159
  in: ids,
@@ -193,7 +173,13 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
193
173
  relationContext,
194
174
  ], targetEntity.primary)
195
175
  },
196
- null,
197
- )
176
+ defaultValue: null,
177
+ predicate: this.getRequiredPredicate(relationContext.entity, relationContext.relation),
178
+ })
179
+ }
180
+
181
+ private getRequiredPredicate(entity: Model.Entity, field: Model.AnyField): Acl.Predicate | undefined {
182
+ const fieldPredicate = this.predicateFactory.getFieldPredicate(entity, Acl.Operation.read, field.name)
183
+ return fieldPredicate.isSameAsPrimary ? undefined : fieldPredicate.predicate
198
184
  }
199
185
  }
@@ -32,25 +32,14 @@ export class MetaHandler implements SelectExecutionHandler<{}> {
32
32
  metaPath: Path,
33
33
  operation: Acl.Operation.read | Acl.Operation.update,
34
34
  ): void {
35
- const { entity, path } = context
35
+ const { entity } = context
36
36
  if (entity.primary === fieldName) {
37
37
  return
38
38
  }
39
- const fieldPredicate = this.predicateFactory.create(entity, operation, [fieldName])
40
- context.addColumn(qb => {
41
- return this.whereBuilder.buildAdvanced(entity, path.back(), fieldPredicate, cb =>
42
- qb.select(
43
- expr =>
44
- expr.selectCondition(condition => {
45
- condition = cb(condition)
46
- if (condition.isEmpty()) {
47
- return condition.raw('true')
48
- }
49
- return condition
50
- }),
51
- metaPath.alias,
52
- ),
53
- )
54
- }, metaPath)
39
+ const fieldPredicate = this.predicateFactory.getFieldPredicate(entity, operation, fieldName)
40
+ context.addColumn({
41
+ path: metaPath,
42
+ valueGetter: context.addPredicate(fieldPredicate.predicate),
43
+ })
55
44
  }
56
45
  }
@@ -11,6 +11,11 @@ type ExtendedRelationContext =
11
11
  isLast: boolean
12
12
  }
13
13
 
14
+ export interface WhereOptimizationHints {
15
+ relationPath?: Model.AnyRelationContext[]
16
+ evaluatedPredicates?: Input.OptionalWhere[]
17
+ }
18
+
14
19
  export class WhereOptimizer {
15
20
  private static eliminableRelations = new Set<Model.AnyRelationContext['type']>(['oneHasMany', 'oneHasOneInverse', 'oneHasOneOwning'])
16
21
 
@@ -20,7 +25,7 @@ export class WhereOptimizer {
20
25
  ) {
21
26
  }
22
27
 
23
- public optimize(where: Input.OptionalWhere, entity: Model.Entity, relationPath: Model.AnyRelationContext[] = []): Input.Where {
28
+ public optimize(where: Input.OptionalWhere, entity: Model.Entity, { relationPath = [], evaluatedPredicates = [] }: WhereOptimizationHints = {}): Input.OptionalWhere {
24
29
  let processedRelationPath: ExtendedRelationContext[] = []
25
30
  for (const i in relationPath) {
26
31
  const el = relationPath[i]
@@ -29,31 +34,38 @@ export class WhereOptimizer {
29
34
  } else {
30
35
  processedRelationPath.push({ ...el, canEliminate: true, isLast: Number(i) === relationPath.length })
31
36
  }
32
- processedRelationPath.push({ ...el, canEliminate: true, isLast: Number(i) === relationPath.length })
33
37
  }
34
- const result = this.optimizeWhere(where, entity, processedRelationPath)
38
+ let result = this.optimizeWhere(where, entity, processedRelationPath)
35
39
 
36
40
  if (typeof result === 'boolean') {
37
41
  return { [entity.primary]: { [result ? 'always' : 'never']: true } }
38
42
  }
43
+ const resultTracker = { count: 0 }
44
+ for (const evaluated of evaluatedPredicates) {
45
+ const evaluatedPredicate = this.optimize(evaluated, entity)
46
+ result = replaceWhere(result, evaluatedPredicate, { [entity.primary]: { always: true } }, resultTracker)
47
+ }
48
+ if (resultTracker.count > 0) {
49
+ return this.optimize(result, entity)
50
+ }
39
51
 
40
52
  return result
41
53
  }
42
54
 
43
- private optimizeWhere(where: Input.OptionalWhere, entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.Where | boolean {
55
+ private optimizeWhere(where: Input.OptionalWhere, entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
44
56
  return this.optimizeAnd(
45
57
  Object.entries(where).map(([key, value]) => {
46
58
  if (value === undefined || value === null) {
47
59
  return undefined
48
60
 
49
61
  } else if (key === 'and') {
50
- return this.optimizeAnd((value as readonly Input.Where[]).map(it => this.optimizeWhere(it, entity, relationPath)), entity, relationPath)
62
+ return this.optimizeAnd((value as readonly Input.OptionalWhere[]).map(it => this.optimizeWhere(it, entity, relationPath)), entity, relationPath)
51
63
 
52
64
  } else if (key === 'or') {
53
- return this.optimizeOr((value as readonly Input.Where[]).map(it => this.optimizeWhere(it, entity, relationPath)), entity, relationPath)
65
+ return this.optimizeOr((value as readonly Input.OptionalWhere[]).map(it => this.optimizeWhere(it, entity, relationPath)), entity, relationPath)
54
66
 
55
67
  } else if (key === 'not') {
56
- return optimizeNot(this.optimizeWhere(value as Input.Where, entity, relationPath))
68
+ return optimizeNot(this.optimizeWhere(value as Input.OptionalWhere, entity, relationPath))
57
69
 
58
70
  } else {
59
71
  return this.resolveFieldValue(entity, key, value, relationPath)
@@ -64,7 +76,7 @@ export class WhereOptimizer {
64
76
  )
65
77
  }
66
78
 
67
- private optimizeOr(operands: readonly (Input.Where | undefined | boolean)[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.Where | boolean {
79
+ private optimizeOr(operands: readonly (Input.OptionalWhere | undefined | boolean)[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
68
80
  const optimized = optimizeOr(operands)
69
81
  if (typeof optimized === 'boolean' || !Array.isArray(optimized.or)) {
70
82
  return optimized
@@ -73,7 +85,7 @@ export class WhereOptimizer {
73
85
  return optimizeOr(result)
74
86
  }
75
87
 
76
- private optimizeAnd(operands: readonly (Input.Where | undefined | boolean)[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.Where | boolean {
88
+ private optimizeAnd(operands: readonly (Input.OptionalWhere | undefined | boolean)[], entity: Model.Entity, relationPath: ExtendedRelationContext[]): Input.OptionalWhere | boolean {
77
89
  const optimized = optimizeAnd(operands)
78
90
  if (typeof optimized === 'boolean' || !Array.isArray(optimized.and)) {
79
91
  return optimized
@@ -82,8 +94,8 @@ export class WhereOptimizer {
82
94
  return optimizeAnd(result)
83
95
  }
84
96
 
85
- private crossOptimize(operands: Input.Where[], entity: Model.Entity, replacement: Input.Condition, relationPath: ExtendedRelationContext[]): (Input.Where | boolean)[] {
86
- const result: (Input.Where | boolean)[] = [...operands]
97
+ private crossOptimize(operands: Input.OptionalWhere[], entity: Model.Entity, replacement: Input.Condition, relationPath: ExtendedRelationContext[]): (Input.OptionalWhere | boolean)[] {
98
+ const result: (Input.OptionalWhere | boolean)[] = [...operands]
87
99
  const count = result.length
88
100
  for (let i = 0; i < count; i++) {
89
101
  for (let j = 0; j < count; j++) {
@@ -100,7 +112,7 @@ export class WhereOptimizer {
100
112
  }
101
113
 
102
114
  private resolveFieldValue(entity: Model.Entity, key: string, value: Input.OptionalWhere[string], relationPath: ExtendedRelationContext[]) {
103
- return acceptFieldVisitor<Input.Where | boolean>(this.model, entity, key, {
115
+ return acceptFieldVisitor<Input.OptionalWhere | boolean>(this.model, entity, key, {
104
116
  visitColumn: () => {
105
117
  const optimizedCondition = this.conditionOptimizer.optimize(value as Input.Condition)
106
118
 
@@ -112,7 +124,7 @@ export class WhereOptimizer {
112
124
  },
113
125
  visitRelation: context => {
114
126
  const { targetEntity, relation } = context
115
- let where = value as Input.Where
127
+ let where = value as Input.OptionalWhere
116
128
  const newRelationPath: ExtendedRelationContext[] = [...relationPath]
117
129
  if (relationPath.length && relationPath[relationPath.length - 1].targetRelation?.name === relation.name) {
118
130
  const item = newRelationPath.pop()
@@ -5,18 +5,18 @@ export interface ReplaceWhereResult {
5
5
  count: number
6
6
  }
7
7
 
8
- export const replaceWhere = (subject: Input.Where, find: Input.Where, replace: Input.Where, resultTracker: ReplaceWhereResult = { count: 0 }): Input.Where => {
8
+ export const replaceWhere = (subject: Input.OptionalWhere, find: Input.OptionalWhere, replace: Input.OptionalWhere, resultTracker: ReplaceWhereResult = { count: 0 }): Input.OptionalWhere => {
9
9
  if (deepEqual(subject, find)) {
10
10
  resultTracker.count++
11
11
  return replace
12
12
  }
13
- const result: Writable<Input.Where> = {}
13
+ const result: Writable<Input.OptionalWhere> = {}
14
14
  for (const [key, value] of Object.entries(subject)) {
15
15
  if (key === 'not') {
16
- result['not'] = replaceWhere(value as Input.Where, find, replace, resultTracker)
16
+ result['not'] = replaceWhere(value as Input.OptionalWhere, find, replace, resultTracker)
17
17
 
18
18
  } else if (key === 'and' || key === 'or') {
19
- result[key] = (value as Input.Where[]).map(it => replaceWhere(it, find, replace, resultTracker))
19
+ result[key] = (value as Input.OptionalWhere[]).map(it => replaceWhere(it, find, replace, resultTracker))
20
20
 
21
21
  } else {
22
22
  result[key] = value
@@ -41,16 +41,18 @@ test('different predicate on row and field level', async () => {
41
41
  executes: [
42
42
  {
43
43
  sql: SQL`
44
- select "root_"."id" as "root_id",
45
- case when "root_"."username" = ? then "root_"."username" else null end as "root_username"
46
- from "public"."author" as "root_"
47
- where not("root_"."username" is null)
44
+ select
45
+ "root_"."id" as "root_id",
46
+ "root_"."username" = ? as "root___predicate_authorPredicate",
47
+ "root_"."username" as "root_username"
48
+ from "public"."author" as "root_" where not("root_"."username" is null)
48
49
  `,
49
50
  parameters: ['johndoe'],
50
51
  response: {
51
52
  rows: [
52
53
  {
53
54
  root_id: testUuid(1),
55
+ root___predicate_authorPredicate: true,
54
56
  root_username: 'johndoe',
55
57
  },
56
58
  ],
@@ -71,3 +73,51 @@ test('different predicate on row and field level', async () => {
71
73
  })
72
74
 
73
75
 
76
+
77
+ test('different predicate on row and field level - false', async () => {
78
+ await execute({
79
+ schema: schema,
80
+ permissions: permissions,
81
+ variables: {},
82
+ query: GQL`
83
+ query {
84
+ listAuthor {
85
+ id
86
+ username
87
+ }
88
+ }`,
89
+ executes: [
90
+ {
91
+ sql: SQL`
92
+ select
93
+ "root_"."id" as "root_id",
94
+ "root_"."username" = ? as "root___predicate_authorPredicate",
95
+ "root_"."username" as "root_username"
96
+ from "public"."author" as "root_" where not("root_"."username" is null)
97
+ `,
98
+ parameters: ['johndoe'],
99
+ response: {
100
+ rows: [
101
+ {
102
+ root_id: testUuid(1),
103
+ root___predicate_authorPredicate: false,
104
+ root_username: 'johndoe',
105
+ },
106
+ ],
107
+ },
108
+ },
109
+ ],
110
+ return: {
111
+ data: {
112
+ listAuthor: [
113
+ {
114
+ id: testUuid(1),
115
+ username: null,
116
+ },
117
+ ],
118
+ },
119
+ },
120
+ })
121
+ })
122
+
123
+
@@ -63,26 +63,23 @@ test('querying id and title', async () => {
63
63
  }`,
64
64
  executes: [
65
65
  {
66
- sql: SQL`select
67
- "root_"."id" as "root_id",
68
- case when "root_"."locale" in (?) then "root_"."title" else null end as "root_title",
69
- "root_"."locale" in (?) as "root__meta_title_readable",
70
- false as "root__meta_title_updatable"
71
- from "public"."post_locale" as "root_"`,
72
- parameters: ['cs', 'cs'],
66
+ sql: SQL`SELECT
67
+ "root_"."id" AS "root_id",
68
+ "root_"."locale" IN (?) AS "root___predicate_localePredicate",
69
+ "root_"."title" AS "root_title"
70
+ FROM "public"."post_locale" AS "root_"`,
71
+ parameters: ['cs'],
73
72
  response: {
74
73
  rows: [
75
74
  {
76
75
  root_id: testUuid(1),
77
76
  root_title: null,
78
- root__meta_title_readable: false,
79
- root__meta_title_updatable: false,
77
+ root___predicate_localePredicate: false,
80
78
  },
81
79
  {
82
80
  root_id: testUuid(2),
83
81
  root_title: 'bar',
84
- root__meta_title_readable: true,
85
- root__meta_title_updatable: false,
82
+ root___predicate_localePredicate: true,
86
83
  },
87
84
  ],
88
85
  },
@@ -134,8 +131,9 @@ test('querying with extra where', async () => {
134
131
  executes: [
135
132
  {
136
133
  sql: SQL`select
137
- "root_"."id" as "root_id",
138
- case when "root_"."locale" in (?) then "root_"."title" else null end as "root_title"
134
+ "root_"."id" as "root_id",
135
+ "root_"."locale" in (?) as "root___predicate_localePredicate",
136
+ "root_"."title" as "root_title"
139
137
  from "public"."post_locale" as "root_"
140
138
  where "root_"."title" = ? and "root_"."locale" in (?)`,
141
139
  parameters: ['cs', 'foo', 'cs'],
@@ -144,7 +142,7 @@ test('querying with extra where', async () => {
144
142
  {
145
143
  root_id: testUuid(1),
146
144
  root_title: 'foo',
147
- root_title__readable: true,
145
+ root___predicate_localePredicate: true,
148
146
  },
149
147
  ],
150
148
  },
@@ -217,10 +215,11 @@ test('not defined acl variable', async () => {
217
215
  }`,
218
216
  executes: [
219
217
  {
220
- sql: SQL`select
221
- "root_"."id" as "root_id",
222
- case when false then "root_"."title" else null end as "root_title"
223
- from "public"."post_locale" as "root_"`,
218
+ sql: SQL`
219
+ select "root_"."id" as "root_id",
220
+ false as "root___predicate_localePredicate",
221
+ "root_"."title" as "root_title"
222
+ from "public"."post_locale" as "root_"`,
224
223
  parameters: [],
225
224
  response: {
226
225
  rows: [],
@@ -270,23 +269,25 @@ test('querying on nested field', async () => {
270
269
  },
271
270
  },
272
271
  {
273
- sql: SQL`select
274
- "root_"."post_id" as "__grouping_key",
275
- "root_"."id" as "root_id",
276
- case when "root_"."locale" in (?) then "root_"."title" else null end as "root_title"
277
- from "public"."post_locale" as "root_"
278
- where "root_"."post_id" in (?, ?)`,
272
+ sql: SQL`
273
+ select "root_"."post_id" as "__grouping_key",
274
+ "root_"."id" as "root_id",
275
+ "root_"."locale" in (?) as "root___predicate_localePredicate",
276
+ "root_"."title" as "root_title"
277
+ from "public"."post_locale" as "root_" where "root_"."post_id" in (?, ?)`,
279
278
  parameters: ['cs', testUuid(1), testUuid(2)],
280
279
  response: {
281
280
  rows: [
282
281
  {
283
282
  __grouping_key: testUuid(1),
284
283
  root_id: testUuid(3),
285
- root_title: null,
284
+ root___predicate_localePredicate: false,
285
+ root_title: 'foo',
286
286
  },
287
287
  {
288
288
  __grouping_key: testUuid(2),
289
289
  root_id: testUuid(4),
290
+ root___predicate_localePredicate: true,
290
291
  root_title: 'bar',
291
292
  },
292
293
  ],
@@ -90,17 +90,20 @@ test('querying post + author name', async () => {
90
90
  },
91
91
  },
92
92
  {
93
- sql: SQL`select
94
- "root_"."id" as "root_id",
95
- case when "root_country"."name" in (?) then "root_"."name" else null end as "root_name",
96
- "root_"."id" as "root_id"
97
- from "public"."author" as "root_" left join "public"."country" as "root_country" on "root_"."country_id" = "root_country"."id"
98
- where "root_"."id" in (?, ?)`,
93
+ sql: SQL`SELECT
94
+ "root_"."id" AS "root_id",
95
+ "root_country"."name" IN (?) AS "root___predicate_countryPredicate",
96
+ "root_"."name" AS "root_name",
97
+ "root_"."id" AS "root_id"
98
+ FROM "public"."author" AS "root_"
99
+ LEFT JOIN "public"."country" AS "root_country" ON "root_"."country_id" = "root_country"."id"
100
+ WHERE "root_"."id" IN (?, ?)`,
99
101
  parameters: ['Czechia', testUuid(3), testUuid(4)],
100
102
  response: {
101
103
  rows: [
102
104
  {
103
105
  root_id: testUuid(3),
106
+ root___predicate_countryPredicate: true,
104
107
  root_name: 'John',
105
108
  },
106
109
  ],
@@ -0,0 +1,104 @@
1
+ import { SchemaDefinition as def, AclDefinition as acl, createSchema } from '@contember/schema-definition'
2
+ import { Acl } from '@contember/schema'
3
+ import { test } from 'vitest'
4
+ import { execute } from '../../../../../src/test'
5
+ import { GQL, SQL } from '../../../../../src/tags'
6
+ import { testUuid } from '../../../../../src/testUuid'
7
+ import { PermissionFactory } from '../../../../../../src'
8
+
9
+
10
+ namespace RowPredicateSubset {
11
+ export const readerRole = acl.createRole('reader')
12
+ @acl.allow(readerRole, {
13
+ when: {
14
+ isPublished: { eq: true },
15
+ },
16
+ read: ['isPublished', 'isUnlocked', 'title'],
17
+ })
18
+ @acl.allow(readerRole, {
19
+ when: {
20
+ and: [
21
+ { isUnlocked: { eq: true } },
22
+ { isPublished: { eq: true } },
23
+ ],
24
+ },
25
+ read: ['content'],
26
+ })
27
+ export class Article {
28
+ isPublished = def.boolColumn()
29
+ isUnlocked = def.boolColumn()
30
+ title = def.stringColumn()
31
+ content = def.stringColumn()
32
+ }
33
+ }
34
+
35
+
36
+
37
+ test('row level predicate includes main predicate', async () => {
38
+ const schema = createSchema(RowPredicateSubset)
39
+
40
+ const permissions = new PermissionFactory(schema.model).create(schema.acl, ['reader'])
41
+
42
+ await execute({
43
+ schema: schema.model,
44
+ permissions: permissions,
45
+ variables: {},
46
+ query: GQL`
47
+ query {
48
+ listArticle {
49
+ id
50
+ title
51
+ content
52
+ }
53
+ }`,
54
+ executes: [
55
+ {
56
+ sql: SQL`SELECT
57
+ "root_"."id" AS "root_id",
58
+ TRUE AS "root___predicate_isPublished_eq_true",
59
+ "root_"."title" AS "root_title",
60
+ "root_"."is_unlocked" = ? AS "root___predicate_and_isUnlocked_eq_true_isPubl",
61
+ "root_"."content" AS "root_content"
62
+ FROM "public"."article" AS "root_"
63
+ WHERE "root_"."is_published" = ?`,
64
+ parameters: [true, true],
65
+ response: {
66
+ rows: [
67
+ {
68
+ root_id: testUuid(1),
69
+ root___predicate_isPublished_eq_true: true,
70
+ root_title: 'Hello',
71
+ root___predicate_and_isUnlocked_eq_true_isPubl: false,
72
+ root_content: 'Foo bar',
73
+ },
74
+ {
75
+ root_id: testUuid(2),
76
+ root___predicate_isPublished_eq_true: true,
77
+ root_title: 'Hello 2',
78
+ root___predicate_and_isUnlocked_eq_true_isPubl: true,
79
+ root_content: 'Lorem ipsum',
80
+ },
81
+ ],
82
+ },
83
+ },
84
+ ],
85
+ return: {
86
+ data: {
87
+ listArticle: [
88
+ {
89
+ id: testUuid(1),
90
+ title: 'Hello',
91
+ content: null,
92
+ },
93
+ {
94
+ id: testUuid(2),
95
+ title: 'Hello 2',
96
+ content: 'Lorem ipsum',
97
+ },
98
+ ],
99
+ },
100
+ },
101
+ })
102
+ })
103
+
104
+