@contember/engine-content-api 1.2.0-alpha.11 → 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.
- package/dist/src/acl/EvaluatedPredicateReplacer.d.ts +2 -2
- package/dist/src/acl/EvaluatedPredicateReplacer.d.ts.map +1 -1
- package/dist/src/acl/EvaluatedPredicateReplacer.js.map +1 -1
- package/dist/src/acl/PredicateFactory.d.ts +9 -3
- package/dist/src/acl/PredicateFactory.d.ts.map +1 -1
- package/dist/src/acl/PredicateFactory.js +42 -24
- package/dist/src/acl/PredicateFactory.js.map +1 -1
- package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
- package/dist/src/acl/PredicatesInjector.js +1 -0
- package/dist/src/acl/PredicatesInjector.js.map +1 -1
- package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js +31 -27
- package/dist/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.js.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.js +18 -14
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.js.map +1 -1
- package/dist/src/mapper/JunctionTableManager.js.map +1 -1
- package/dist/src/mapper/Mapper.js +3 -3
- package/dist/src/mapper/Mapper.js.map +1 -1
- package/dist/src/mapper/MapperContainer.d.ts.map +1 -1
- package/dist/src/mapper/MapperContainer.js +2 -2
- package/dist/src/mapper/MapperContainer.js.map +1 -1
- package/dist/src/mapper/delete/DeleteExecutor.js +1 -1
- package/dist/src/mapper/delete/DeleteExecutor.js.map +1 -1
- package/dist/src/mapper/insert/InsertBuilder.d.ts +1 -1
- package/dist/src/mapper/insert/InsertBuilder.d.ts.map +1 -1
- package/dist/src/mapper/insert/InsertBuilder.js.map +1 -1
- package/dist/src/mapper/select/RelationFetcher.js +1 -1
- package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
- package/dist/src/mapper/select/SelectBuilder.d.ts +3 -1
- package/dist/src/mapper/select/SelectBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectBuilder.js +50 -12
- package/dist/src/mapper/select/SelectBuilder.js.map +1 -1
- package/dist/src/mapper/select/SelectExecutionHandler.d.ts +15 -4
- package/dist/src/mapper/select/SelectExecutionHandler.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.d.ts +3 -2
- package/dist/src/mapper/select/SelectHydrator.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.js +4 -5
- package/dist/src/mapper/select/SelectHydrator.js.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.d.ts +5 -3
- package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.js +47 -8
- package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
- package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts +1 -0
- package/dist/src/mapper/select/handlers/FieldsVisitor.d.ts.map +1 -1
- package/dist/src/mapper/select/handlers/FieldsVisitor.js +118 -91
- package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
- package/dist/src/mapper/select/handlers/MetaHandler.d.ts.map +1 -1
- package/dist/src/mapper/select/handlers/MetaHandler.js +6 -11
- package/dist/src/mapper/select/handlers/MetaHandler.js.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts +5 -1
- package/dist/src/mapper/select/optimizer/WhereOptimizer.d.ts.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js +10 -3
- package/dist/src/mapper/select/optimizer/WhereOptimizer.js.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts +1 -1
- package/dist/src/mapper/select/optimizer/WhereReplacer.d.ts.map +1 -1
- package/dist/src/mapper/select/optimizer/WhereReplacer.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.js +51 -4
- package/dist/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js +26 -25
- package/dist/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js +9 -6
- package/dist/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.d.ts +2 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js +109 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.js +9 -13
- package/dist/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.js.map +1 -1
- package/dist/tests/cases/unit/predicatesInjector.test.js +31 -3
- package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
- package/dist/tests/cases/unit/whereBuilder.test.d.ts +2 -0
- package/dist/tests/cases/unit/whereBuilder.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/whereBuilder.test.js +75 -0
- package/dist/tests/cases/unit/whereBuilder.test.js.map +1 -0
- package/dist/tests/cases/unit/whereOptimizer.test.js +24 -21
- package/dist/tests/cases/unit/whereOptimizer.test.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/acl/EvaluatedPredicateReplacer.ts +4 -4
- package/src/acl/PredicateFactory.ts +59 -36
- package/src/acl/PredicatesInjector.ts +2 -1
- package/src/extensions/hasManyToHasOneReducer/HasManyToHasOneReducerExecutionHandler.ts +5 -5
- package/src/extensions/paginatedHasMany/PaginatedHasManyExecutionHandler.ts +5 -5
- package/src/mapper/JunctionTableManager.ts +3 -3
- package/src/mapper/Mapper.ts +3 -3
- package/src/mapper/MapperContainer.ts +2 -1
- package/src/mapper/delete/DeleteExecutor.ts +1 -1
- package/src/mapper/insert/InsertBuilder.ts +2 -2
- package/src/mapper/select/RelationFetcher.ts +1 -1
- package/src/mapper/select/SelectBuilder.ts +69 -13
- package/src/mapper/select/SelectExecutionHandler.ts +15 -8
- package/src/mapper/select/SelectHydrator.ts +13 -7
- package/src/mapper/select/WhereBuilder.ts +58 -11
- package/src/mapper/select/handlers/FieldsVisitor.ts +53 -67
- package/src/mapper/select/handlers/MetaHandler.ts +6 -17
- package/src/mapper/select/optimizer/WhereOptimizer.ts +25 -13
- package/src/mapper/select/optimizer/WhereReplacer.ts +4 -4
- package/tests/cases/integration/graphQlRequests/query/acl/differentRowAndFieldPredicate.test.ts +54 -4
- package/tests/cases/integration/graphQlRequests/query/acl/fieldRestrictedByLocale.test.ts +26 -25
- package/tests/cases/integration/graphQlRequests/query/acl/nestedPredicate.test.ts +9 -6
- package/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.ts +104 -0
- package/tests/cases/integration/graphQlRequests/query/graphqlFeatures/alias.test.ts +9 -13
- package/tests/cases/unit/predicatesInjector.test.ts +37 -7
- package/tests/cases/unit/whereBuilder.test.ts +84 -0
- 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.
|
|
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.
|
|
14
|
-
"@contember/dic": "^1.2.0-alpha.
|
|
15
|
-
"@contember/engine-common": "^1.2.0-alpha.
|
|
16
|
-
"@contember/graphql-utils": "^1.2.0-alpha.
|
|
17
|
-
"@contember/schema": "^1.2.0-alpha.
|
|
18
|
-
"@contember/schema-utils": "^1.2.0-alpha.
|
|
13
|
+
"@contember/database": "^1.2.0-alpha.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.
|
|
24
|
-
"@contember/engine-api-tester": "^1.2.0-alpha.
|
|
25
|
-
"@contember/schema-definition": "^1.2.0-alpha.
|
|
23
|
+
"@contember/database-tester": "^1.2.0-alpha.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.
|
|
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.
|
|
13
|
-
const result: Writable<Input.
|
|
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.
|
|
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
|
|
25
|
-
entity:
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
102
|
+
const owningWhere: Input.OptionalWhere = {
|
|
103
103
|
and: [owningPredicate, { [owningEntity.primary]: { eq: owningPrimary } }],
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
const inverseWhere: Input.
|
|
106
|
+
const inverseWhere: Input.OptionalWhere = {
|
|
107
107
|
and: [inversePredicate, { [inverseEntity.primary]: { eq: inversePrimary } }],
|
|
108
108
|
}
|
|
109
109
|
|
package/src/mapper/Mapper.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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 (
|
|
102
|
-
|
|
103
|
-
|
|
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 =
|
|
106
|
-
this.hydrator.addPromise(fieldPath, path.for(
|
|
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: (
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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[] }
|