@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-content-api",
3
- "version": "1.2.0-alpha.12",
3
+ "version": "1.2.0-alpha.13",
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.12",
14
- "@contember/dic": "^1.2.0-alpha.12",
15
- "@contember/engine-common": "^1.2.0-alpha.12",
16
- "@contember/graphql-utils": "^1.2.0-alpha.12",
17
- "@contember/schema": "^1.2.0-alpha.12",
18
- "@contember/schema-utils": "^1.2.0-alpha.12",
13
+ "@contember/database": "^1.2.0-alpha.13",
14
+ "@contember/dic": "^1.2.0-alpha.13",
15
+ "@contember/engine-common": "^1.2.0-alpha.13",
16
+ "@contember/graphql-utils": "^1.2.0-alpha.13",
17
+ "@contember/schema": "^1.2.0-alpha.13",
18
+ "@contember/schema-utils": "^1.2.0-alpha.13",
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.12",
24
- "@contember/engine-api-tester": "^1.2.0-alpha.12",
25
- "@contember/schema-definition": "^1.2.0-alpha.12",
23
+ "@contember/database-tester": "^1.2.0-alpha.13",
24
+ "@contember/engine-api-tester": "^1.2.0-alpha.13",
25
+ "@contember/schema-definition": "^1.2.0-alpha.13",
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",
@@ -3,17 +3,17 @@ import { replaceWhere } from '../mapper/select/optimizer/WhereReplacer'
3
3
 
4
4
  export class EvaluatedPredicateReplacer {
5
5
  constructor(
6
- private readonly evaluatedPredicate: Input.Where,
6
+ private readonly evaluatedPredicate: Input.OptionalWhere,
7
7
  private readonly sourceEntity: Model.Entity,
8
8
  private readonly relation: Model.AnyRelation,
9
9
  ) {
10
10
  }
11
11
 
12
- public replace(where: Input.Where): Input.Where {
13
- const result: Writable<Input.Where> = {}
12
+ public replace(where: Input.OptionalWhere): Input.OptionalWhere {
13
+ const result: Writable<Input.OptionalWhere> = {}
14
14
  for (const [key, value] of Object.entries(where)) {
15
15
  if (key === 'not') {
16
- result['not'] = this.replace(value as Input.Where)
16
+ result['not'] = this.replace(value as Input.OptionalWhere)
17
17
 
18
18
  } else if (key === 'and' || key === 'or') {
19
19
  result[key] = (value as Input.Where[]).map(it => this.replace(it))
@@ -4,6 +4,11 @@ import { EvaluatedPredicateReplacer } from './EvaluatedPredicateReplacer'
4
4
 
5
5
  const getRowLevelPredicatePseudoField = (entity: Model.Entity) => entity.primary
6
6
 
7
+ export interface FieldRequiredPredicate {
8
+ predicate: Acl.Predicate
9
+ isSameAsPrimary: boolean
10
+ }
11
+
7
12
  export class PredicateFactory {
8
13
  constructor(
9
14
  private readonly permissions: Acl.Permissions,
@@ -11,6 +16,24 @@ export class PredicateFactory {
11
16
  private readonly variableInjector: VariableInjector,
12
17
  ) {}
13
18
 
19
+ public getFieldPredicate(
20
+ entity: Model.Entity,
21
+ operation: Acl.Operation.update | Acl.Operation.read | Acl.Operation.create,
22
+ fieldName: string,
23
+ ): FieldRequiredPredicate {
24
+ const permissions = this.permissions[entity.name]?.operations?.[operation]
25
+ const predicate = permissions?.[fieldName] ?? false
26
+ const rowLevelField = getRowLevelPredicatePseudoField(entity)
27
+
28
+ const primaryPredicate = permissions?.[rowLevelField] ?? false
29
+ const isSameAsPrimary = predicate === primaryPredicate
30
+
31
+ return {
32
+ isSameAsPrimary,
33
+ predicate,
34
+ }
35
+ }
36
+
14
37
  public shouldApplyCellLevelPredicate(
15
38
  entity: Model.Entity,
16
39
  operation: Acl.Operation.read,
@@ -21,22 +44,28 @@ export class PredicateFactory {
21
44
  return permissions?.[fieldName] !== permissions?.[rowLevelField]
22
45
  }
23
46
 
24
- public create(
25
- entity: Model.Entity,
26
- operation: Acl.Operation.delete,
27
- ): Input.Where
47
+ public createDeletePredicate(entity: Model.Entity) {
48
+ const neverCondition: Input.Where = { [entity.primary]: { never: true } }
49
+ const entityPermissions = this.permissions[entity.name]
50
+ if (!entityPermissions) {
51
+ return neverCondition
52
+ }
53
+ const deletePredicate = entityPermissions.operations.delete
54
+ if (deletePredicate === undefined || deletePredicate === false) {
55
+ return neverCondition
56
+ }
57
+ if (deletePredicate === true) {
58
+ return {}
59
+ }
60
+ return this.buildPredicates(entity, [deletePredicate])
61
+ }
62
+
28
63
  public create(
29
64
  entity: Model.Entity,
30
65
  operation: Acl.Operation.update | Acl.Operation.read | Acl.Operation.create,
31
- fieldNames?: string[],
32
- relationContext?: Model.AnyRelationContext,
33
- ): Input.Where
34
- public create(
35
- entity: Model.Entity,
36
- operation: Acl.Operation,
37
- fieldNames?: string[],
66
+ fieldNames: string[] = [getRowLevelPredicatePseudoField(entity)],
38
67
  relationContext?: Model.AnyRelationContext,
39
- ): Input.Where {
68
+ ): Input.OptionalWhere {
40
69
  const entityPermissions: Acl.EntityPermissions = this.permissions[entity.name]
41
70
  const neverCondition: Input.Where = { [entity.primary]: { never: true } }
42
71
 
@@ -44,31 +73,24 @@ export class PredicateFactory {
44
73
  return neverCondition
45
74
  }
46
75
 
47
- let predicates: Acl.PredicateReference[]
48
- if (operation === Acl.Operation.delete) {
49
- const deletePredicate = entityPermissions.operations.delete
50
- if (deletePredicate === undefined || deletePredicate === false) {
51
- return neverCondition
52
- }
53
- if (deletePredicate === true) {
54
- return {}
55
- }
56
- predicates = [deletePredicate]
57
- } else {
58
- if (fieldNames === undefined) {
59
- fieldNames = [getRowLevelPredicatePseudoField(entity)]
60
- }
61
- const fieldPermissions = entityPermissions.operations[operation]
62
- if (fieldPermissions === undefined) {
63
- return neverCondition
64
- }
65
- const operationPredicates = this.getRequiredPredicates(fieldNames, fieldPermissions)
66
- if (operationPredicates === false) {
67
- return neverCondition
68
- }
69
- predicates = operationPredicates
76
+ if (fieldNames === undefined) {
77
+ fieldNames = [getRowLevelPredicatePseudoField(entity)]
78
+ }
79
+ const fieldPermissions = entityPermissions.operations[operation]
80
+ if (fieldPermissions === undefined) {
81
+ return neverCondition
82
+ }
83
+ const operationPredicates = this.getRequiredPredicates(fieldNames, fieldPermissions)
84
+ if (operationPredicates === false) {
85
+ return neverCondition
70
86
  }
71
87
 
88
+ return this.buildPredicates(entity, operationPredicates, relationContext)
89
+ }
90
+
91
+ public buildPredicates(entity: Model.Entity, predicates: Acl.PredicateReference[], relationContext?: Model.AnyRelationContext): Input.OptionalWhere {
92
+ const entityPermissions: Acl.EntityPermissions = this.permissions[entity.name] ?? {}
93
+
72
94
  const predicatesWhere: Input.Where[] = predicates.reduce(
73
95
  (result: Input.Where[], name: Acl.PredicateReference): Input.Where[] => {
74
96
  if (!entityPermissions.predicates[name]) {
@@ -84,6 +106,7 @@ export class PredicateFactory {
84
106
  }
85
107
  const where: Input.Where = predicatesWhere.length === 1 ? predicatesWhere[0] : { and: predicatesWhere }
86
108
  return this.optimizePredicates(where, relationContext)
109
+
87
110
  }
88
111
 
89
112
  private getRequiredPredicates(
@@ -106,7 +129,7 @@ export class PredicateFactory {
106
129
  return predicates
107
130
  }
108
131
 
109
- private optimizePredicates(where: Input.Where, relationContext?: Model.AnyRelationContext) {
132
+ public optimizePredicates(where: Input.OptionalWhere, relationContext?: Model.AnyRelationContext) {
110
133
  if (!relationContext || !relationContext.targetRelation) {
111
134
  return where
112
135
  }
@@ -16,7 +16,8 @@ export class PredicatesInjector {
16
16
  where: Input.OptionalWhere,
17
17
  relationContext?: Model.AnyRelationContext,
18
18
  ): Input.OptionalWhere {
19
- const predicatesWhere: Input.Where = this.predicateFactory.create(entity, Acl.Operation.read, fieldNames, relationContext)
19
+ const predicatesWhere = this.predicateFactory.create(entity, Acl.Operation.read, fieldNames, relationContext)
20
+ where = this.predicateFactory.optimizePredicates(where, relationContext)
20
21
 
21
22
  const and = [where, predicatesWhere].filter(it => Object.keys(it).length > 0)
22
23
  if (and.length === 0) {
@@ -15,9 +15,9 @@ export class HasManyToHasOneReducerExecutionHandler implements SelectExecutionHa
15
15
  if (!objectNode) {
16
16
  throw new Error()
17
17
  }
18
- addData(
19
- entity.primary,
20
- async (ids: Input.PrimaryValue[]) => {
18
+ addData({
19
+ field: entity.primary,
20
+ dataProvider: async (ids: Input.PrimaryValue[]) => {
21
21
 
22
22
  const relationContext = acceptFieldVisitor(this.schema, entity, objectNode.extensions.relationName, {
23
23
  visitRelation: context => context,
@@ -48,7 +48,7 @@ export class HasManyToHasOneReducerExecutionHandler implements SelectExecutionHa
48
48
  relationContext,
49
49
  ], targetRelation.name)
50
50
  },
51
- null,
52
- )
51
+ defaultValue: null,
52
+ })
53
53
  }
54
54
  }
@@ -20,9 +20,9 @@ export class PaginatedHasManyExecutionHandler implements SelectExecutionHandler<
20
20
  throw new Error()
21
21
  }
22
22
  const pagination = createPaginationHelper(objectNode)
23
- addData(
24
- context.entity.primary,
25
- async ids => {
23
+ addData({
24
+ field: context.entity.primary,
25
+ dataProvider: async ids => {
26
26
  const counts = pagination.requiresTotalCount
27
27
  ? await acceptFieldVisitor(
28
28
  this.schema,
@@ -46,7 +46,7 @@ export class PaginatedHasManyExecutionHandler implements SelectExecutionHandler<
46
46
  }
47
47
  return Object.fromEntries(result)
48
48
  },
49
- pagination.createResponse(0, []),
50
- )
49
+ defaultValue: pagination.createResponse(0, []),
50
+ })
51
51
  }
52
52
  }
@@ -87,7 +87,7 @@ export class JunctionTableManager {
87
87
  }
88
88
 
89
89
  const owningPredicate = this.predicateFactory.create(owningEntity, Acl.Operation.update, [relation.name])
90
- let inversePredicate: Input.Where = {}
90
+ let inversePredicate: Input.OptionalWhere = {}
91
91
  if (relation.inversedBy) {
92
92
  inversePredicate = this.predicateFactory.create(inverseEntity, Acl.Operation.update, [relation.inversedBy])
93
93
  }
@@ -99,11 +99,11 @@ export class JunctionTableManager {
99
99
  if (hasNoPredicates) {
100
100
  return await handler.executeSimple({ db, joiningTable, owningPrimary, inversePrimary, okResultFactory })
101
101
  } else {
102
- const owningWhere: Input.Where = {
102
+ const owningWhere: Input.OptionalWhere = {
103
103
  and: [owningPredicate, { [owningEntity.primary]: { eq: owningPrimary } }],
104
104
  }
105
105
 
106
- const inverseWhere: Input.Where = {
106
+ const inverseWhere: Input.OptionalWhere = {
107
107
  and: [inversePredicate, { [inverseEntity.primary]: { eq: inversePrimary } }],
108
108
  }
109
109
 
@@ -50,7 +50,7 @@ export class Mapper {
50
50
  .from(entity.tableName, 'root_')
51
51
  .select(['root_', columnName])
52
52
  const expandedWhere = this.uniqueWhereExpander.expand(entity, where)
53
- const builtQb = this.whereBuilder.build(qb, entity, this.pathFactory.create([]), expandedWhere, [])
53
+ const builtQb = this.whereBuilder.build(qb, entity, this.pathFactory.create([]), expandedWhere)
54
54
  const result = await builtQb.getResult(this.db)
55
55
 
56
56
  return result[0] !== undefined ? result[0][columnName] : undefined
@@ -136,7 +136,7 @@ export class Mapper {
136
136
  .from(entity.tableName, path.alias)
137
137
  .select(expr => expr.raw('count(*)'), 'row_count')
138
138
  const withPredicates = this.predicatesInjector.inject(entity, filter)
139
- const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates, [])
139
+ const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates)
140
140
  const result = await qbWithWhere.getResult(this.db)
141
141
  return result[0].row_count
142
142
  }
@@ -154,7 +154,7 @@ export class Mapper {
154
154
  .select([path.alias, relation.joiningColumn.columnName])
155
155
  .groupBy([path.alias, relation.joiningColumn.columnName])
156
156
  const withPredicates = this.predicatesInjector.inject(entity, filter)
157
- const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates, relationPath)
157
+ const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates, { relationPath })
158
158
  const rows = await qbWithWhere.getResult(this.db)
159
159
  const result = new Map<string, number>()
160
160
  for (const row of rows) {
@@ -84,7 +84,7 @@ export const createMapperContainer = ({ permissions, schema, identityVariables,
84
84
  [HasManyToHasOneReducer.extensionName]: hasManyToHasOneReducer,
85
85
  [PaginatedHasManyFieldProvider.extensionName]: paginatedHasManyExecutionHandler,
86
86
  }))
87
- .addService('selectBuilderFactory', ({ whereBuilder, orderByBuilder, fieldsVisitorFactory, metaHandler, selectHandlers, pathFactory }) =>
87
+ .addService('selectBuilderFactory', ({ whereBuilder, orderByBuilder, fieldsVisitorFactory, metaHandler, selectHandlers, pathFactory, predicateFactory }) =>
88
88
  new (class implements SelectBuilderFactory {
89
89
  create(qb: DbSelectBuilder, hydrator: SelectHydrator, relationPath: Model.AnyRelationContext[]): SelectBuilder {
90
90
  return new SelectBuilder(
@@ -98,6 +98,7 @@ export const createMapperContainer = ({ permissions, schema, identityVariables,
98
98
  selectHandlers,
99
99
  pathFactory,
100
100
  relationPath,
101
+ predicateFactory,
101
102
  )
102
103
  }
103
104
  })())
@@ -57,7 +57,7 @@ export class DeleteExecutor {
57
57
 
58
58
  private async delete(mapper: Mapper, entity: Model.Entity, where: Input.OptionalWhere): Promise<string[]> {
59
59
  const db = mapper.db
60
- const predicate = this.predicateFactory.create(entity, Acl.Operation.delete)
60
+ const predicate = this.predicateFactory.createDeletePredicate(entity)
61
61
  const inQb = SelectBuilder.create() //
62
62
  .from(entity.tableName, 'root_')
63
63
  .select(['root_', entity.primaryColumn])
@@ -20,7 +20,7 @@ export class InsertBuilder {
20
20
  public readonly insert: Promise<Value.PrimaryValue | null> = new Promise(resolve => (this.resolver = resolve))
21
21
 
22
22
  private rowData: ColumnValue<AbortInsert | undefined>[] = []
23
- private where: Input.Where = {}
23
+ private where: Input.OptionalWhere = {}
24
24
 
25
25
  constructor(
26
26
  private readonly schema: Model.Schema,
@@ -35,7 +35,7 @@ export class InsertBuilder {
35
35
  this.rowData.push({ columnName, value: resolveGenericValue(value), columnType, fieldName })
36
36
  }
37
37
 
38
- public addWhere(where: Input.Where): void {
38
+ public addWhere(where: Input.OptionalWhere): void {
39
39
  this.where = { and: [where, this.where] }
40
40
  }
41
41
 
@@ -278,7 +278,7 @@ export class RelationFetcher {
278
278
  }
279
279
 
280
280
  if (where && hasWhere) {
281
- qb = this.whereBuilder.build(qb, targetEntity, this.pathFactory.create([]), where, relationPath)
281
+ qb = this.whereBuilder.build(qb, targetEntity, this.pathFactory.create([]), where, { relationPath })
282
282
  }
283
283
  return qb
284
284
  }
@@ -1,6 +1,6 @@
1
- import { Input, Model } from '@contember/schema'
1
+ import { Acl, Input, Model } from '@contember/schema'
2
2
  import { acceptFieldVisitor, getColumnName } from '@contember/schema-utils'
3
- import { SelectHydrator, SelectRow } from './SelectHydrator'
3
+ import { ColumnValueGetter, SelectHydrator, SelectRow } from './SelectHydrator'
4
4
  import { Path, PathFactory } from './Path'
5
5
  import { WhereBuilder } from './WhereBuilder'
6
6
  import { Client, LimitByGroupWrapper, SelectBuilder as DbSelectBuilder } from '@contember/database'
@@ -11,6 +11,7 @@ import { MetaHandler } from './handlers'
11
11
  import { Mapper } from '../Mapper'
12
12
  import { FieldNode, ObjectNode } from '../../inputProcessing'
13
13
  import { assertNever } from '../../utils'
14
+ import { PredicateFactory } from '../../acl'
14
15
 
15
16
  export class SelectBuilder {
16
17
  private resolver: (value: SelectRow[]) => void = () => {
@@ -31,6 +32,7 @@ export class SelectBuilder {
31
32
  private readonly selectHandlers: { [key: string]: SelectExecutionHandler<any> },
32
33
  private readonly pathFactory: PathFactory,
33
34
  private readonly relationPath: Model.AnyRelationContext[],
35
+ private readonly predicateFactory: PredicateFactory,
34
36
  ) {}
35
37
 
36
38
  public async execute(db: Client): Promise<SelectRow[]> {
@@ -54,7 +56,7 @@ export class SelectBuilder {
54
56
  this.selectInternal(mapper, entity, path, input)
55
57
  const where = input.args.filter
56
58
  if (where) {
57
- this.qb = this.whereBuilder.build(this.qb, entity, path, where, this.relationPath)
59
+ this.qb = this.whereBuilder.build(this.qb, entity, path, where, { relationPath: this.relationPath })
58
60
  }
59
61
  const orderBy = input.args.orderBy || []
60
62
 
@@ -84,6 +86,40 @@ export class SelectBuilder {
84
86
  input = input.withField(new FieldNode(entity.primary, entity.primary, {}))
85
87
  }
86
88
 
89
+ const fetchedPredicates = new Set()
90
+ const addPredicate = (predicate: Acl.Predicate): ColumnValueGetter<boolean> => {
91
+ if (typeof predicate === 'boolean') {
92
+ return () => predicate
93
+ }
94
+ const predicatePath = path.for('__predicate').for(predicate)
95
+
96
+ if (!fetchedPredicates.has(predicate)) {
97
+ const relationContext = this.relationPath[this.relationPath.length - 1]
98
+
99
+ const primaryPredicate = this.predicateFactory.create(entity, Acl.Operation.read, undefined, relationContext)
100
+ const fieldPredicate = this.predicateFactory.buildPredicates(entity, [predicate], relationContext)
101
+
102
+ this.qb = this.whereBuilder.buildAdvanced(
103
+ entity,
104
+ path.back(),
105
+ fieldPredicate,
106
+ apply => this.qb.select(expr =>
107
+ expr.selectCondition(condition => {
108
+ condition = apply(condition)
109
+ if (condition.isEmpty()) {
110
+ return condition.raw('true')
111
+ }
112
+ return condition
113
+ }),
114
+ predicatePath.alias,
115
+ ),
116
+ { relationPath: this.relationPath, evaluatedPredicates: [primaryPredicate] },
117
+ )
118
+ fetchedPredicates.add(predicate)
119
+ }
120
+ return row => row[predicatePath.alias] === true
121
+ }
122
+
87
123
  for (let field of input.fields) {
88
124
  const fieldPath = path.for(field.alias)
89
125
  const fieldProperty = (() => {
@@ -95,20 +131,38 @@ export class SelectBuilder {
95
131
  }
96
132
  return assertNever(field)
97
133
  })()
134
+
135
+
98
136
  const executionContext: SelectExecutionHandlerContext = {
99
137
  mapper,
100
138
  relationPath: this.relationPath,
101
- addData: async (fieldName, cb, defaultValue = null) => {
102
- const columnName = getColumnName(this.schema, entity, fieldName)
103
- const ids = (await this.getColumnValues(path.for(fieldName), columnName)).filter(it => it !== null)
139
+ addData: async ({ field, dataProvider, defaultValue, predicate }) => {
140
+ if (predicate === false) {
141
+ this.hydrator.addPromise(fieldPath, path.for(field), Promise.resolve({}), defaultValue ?? null)
142
+ return
143
+ }
144
+ const predicateGetter = predicate !== undefined && predicate !== true ? addPredicate(predicate) : null
145
+ const columnName = getColumnName(this.schema, entity, field)
146
+ let ids = await this.getColumnValues(path.for(field), columnName, predicateGetter)
104
147
 
105
- const data = (async () => (ids.length > 0 ? cb(ids) : {}))()
106
- this.hydrator.addPromise(fieldPath, path.for(fieldName), data, defaultValue)
148
+ const data = ids.length > 0 ? dataProvider(ids) : Promise.resolve({})
149
+ this.hydrator.addPromise(fieldPath, path.for(field), data, defaultValue ?? null)
107
150
  },
108
- addColumn: (qbCallback, path) => {
109
- this.qb = qbCallback(this.qb)
110
- this.hydrator.addColumn(path || fieldPath)
151
+ addColumn: ({ path = fieldPath, predicate, query, valueGetter }) => {
152
+ if (predicate === false) {
153
+ this.hydrator.addColumn(path, () => null)
154
+ return
155
+ }
156
+ const predicateGetter = predicate !== undefined && predicate !== true ? addPredicate(predicate) : null
157
+ if (query) {
158
+ this.qb = query(this.qb)
159
+ }
160
+ this.hydrator.addColumn(
161
+ path,
162
+ valueGetter ?? (row => predicateGetter === null || predicateGetter(row) ? row[path.alias] : null),
163
+ )
111
164
  },
165
+ addPredicate: addPredicate,
112
166
  path: fieldPath,
113
167
  entity: entity,
114
168
  ...fieldProperty,
@@ -138,10 +192,12 @@ export class SelectBuilder {
138
192
  }
139
193
  }
140
194
 
141
- private async getColumnValues(columnPath: Path, columnName: string): Promise<Input.PrimaryValue[]> {
195
+ private async getColumnValues(columnPath: Path, columnName: string, predicateGetter: null | ColumnValueGetter<boolean>): Promise<Input.PrimaryValue[]> {
142
196
  this.qb = this.qb.select([columnPath.back().alias, columnName], columnPath.alias)
143
197
  const rows = await this.rows
198
+ const filteredRows = predicateGetter === null ? rows : rows.filter(predicateGetter)
144
199
  const columnAlias = columnPath.alias
145
- return Array.from(new Set(rows.map((it): Input.PrimaryValue => it[columnAlias] as Input.PrimaryValue)))
200
+ const ids = filteredRows.map((it): Input.PrimaryValue => it[columnAlias] as Input.PrimaryValue).filter(it => it !== null)
201
+ return Array.from(new Set(ids))
146
202
  }
147
203
  }
@@ -1,6 +1,6 @@
1
1
  import { Path } from './Path'
2
- import { Input, Model } from '@contember/schema'
3
- import { SelectNestedData, SelectNestedDefaultValue } from './SelectHydrator'
2
+ import { Acl, Input, Model } from '@contember/schema'
3
+ import { ColumnValueGetter, SelectNestedData, SelectNestedDefaultValue, SelectRow } from './SelectHydrator'
4
4
  import { SelectBuilder } from '@contember/database'
5
5
  import { Mapper } from '../Mapper'
6
6
  import { FieldNode, ObjectNode } from '../../inputProcessing'
@@ -22,12 +22,19 @@ export type SelectExecutionHandlerContext<
22
22
  path: Path
23
23
  entity: Model.Entity
24
24
  relationPath: Model.AnyRelationContext[]
25
-
26
- addColumn: (
27
- queryCallback: (qb: SelectBuilder<SelectBuilder.Result>) => SelectBuilder<SelectBuilder.Result>,
28
- path?: Path,
29
- ) => void
30
- addData: (parentField: string, cb: DataCallback, defaultValue?: SelectNestedDefaultValue) => void
25
+ addPredicate: (predicate: Acl.Predicate) => (row: SelectRow) => boolean
26
+ addColumn: (args: {
27
+ predicate?: Acl.Predicate
28
+ query?: (qb: SelectBuilder<SelectBuilder.Result>) => SelectBuilder<SelectBuilder.Result>
29
+ path?: Path
30
+ valueGetter?: ColumnValueGetter
31
+ }) => void
32
+ addData: (args: {
33
+ field: string
34
+ dataProvider: DataCallback
35
+ predicate?: Acl.Predicate
36
+ defaultValue?: SelectNestedDefaultValue
37
+ }) => void
31
38
  } & (
32
39
  | {
33
40
  fieldNode: FieldNode<FieldExtensions>
@@ -16,12 +16,19 @@ type ResolvedData = {
16
16
  defaultValue: SelectNestedDefaultValue
17
17
  }
18
18
 
19
+ export type ColumnValueGetter<T extends Value.FieldValue = Value.FieldValue> = (row: SelectRow) => T
20
+
21
+ type Column = {
22
+ path: Path
23
+ getValue: ColumnValueGetter
24
+ }
25
+
19
26
  export class SelectHydrator {
20
- private columns: Path[] = []
27
+ private columns: Column[] = []
21
28
  private promises: DataPromises[] = []
22
29
 
23
- public addColumn(path: Path) {
24
- this.columns.push(path)
30
+ public addColumn(path: Path, getValue: ColumnValueGetter) {
31
+ this.columns.push({ path, getValue })
25
32
  }
26
33
 
27
34
  public addPromise(
@@ -72,12 +79,11 @@ export class SelectHydrator {
72
79
  const result: SelectResultObject = {}
73
80
 
74
81
  for (let columnPath of this.columns) {
75
- const path = [...columnPath.path]
82
+ const path = [...columnPath.path.path]
76
83
  const last: string = path.pop() as string
77
84
  const currentObject = path.reduce<any>((obj, part) => (obj[part] = obj[part] || {}), result)
78
85
 
79
- const field = row[columnPath.alias]
80
- currentObject[last] = this.formatValue(field)
86
+ currentObject[last] = this.formatValue(columnPath.getValue(row))
81
87
  }
82
88
 
83
89
  for (let { path, parentKeyPath, data, defaultValue } of resolvedData) {
@@ -122,7 +128,7 @@ export class SelectHydrator {
122
128
  }
123
129
  }
124
130
 
125
- export type SelectRow = { [key: string]: Value.AtomicValue }
131
+ export type SelectRow = { [key: string]: Value.FieldValue }
126
132
  export type SelectResultObject = Value.Object
127
133
  export type SelectIndexedResultObjects = { [key: string]: SelectResultObject }
128
134
  export type SelectGroupedObjects = { [groupingKey: string]: SelectResultObject[] }
@@ -11,7 +11,7 @@ import {
11
11
  QueryBuilder,
12
12
  SelectBuilder, wrapIdentifier,
13
13
  } from '@contember/database'
14
- import { WhereOptimizer } from './optimizer/WhereOptimizer'
14
+ import { WhereOptimizationHints, WhereOptimizer } from './optimizer/WhereOptimizer'
15
15
 
16
16
  export class WhereBuilder {
17
17
  constructor(
@@ -27,9 +27,9 @@ export class WhereBuilder {
27
27
  entity: Model.Entity,
28
28
  path: Path,
29
29
  where: Input.OptionalWhere,
30
- relationPath: Model.AnyRelationContext[] = [],
30
+ optimizationHints: WhereOptimizationHints = {},
31
31
  ) {
32
- const optimizedWhere = this.whereOptimizer.optimize(where, entity, relationPath)
32
+ const optimizedWhere = this.whereOptimizer.optimize(where, entity, optimizationHints)
33
33
  return this.buildInternal(entity, path, optimizedWhere, cb => qb.where(clause => cb(clause)), false)
34
34
  }
35
35
 
@@ -39,9 +39,9 @@ export class WhereBuilder {
39
39
  path: Path,
40
40
  where: Input.OptionalWhere,
41
41
  callback: (clauseCb: (clause: SqlConditionBuilder) => SqlConditionBuilder) => SelectBuilder<SelectBuilder.Result>,
42
- relationPath: Model.AnyRelationContext[] = [],
42
+ optimizationHints: WhereOptimizationHints = {},
43
43
  ) {
44
- const optimizedWhere = this.whereOptimizer.optimize(where, entity, relationPath)
44
+ const optimizedWhere = this.whereOptimizer.optimize(where, entity, optimizationHints)
45
45
  return this.buildInternal(entity, path, optimizedWhere, callback, false)
46
46
  }
47
47