@contember/engine-content-api 1.2.0-beta.3 → 1.2.0-rc.2
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/PredicatesInjector.d.ts.map +1 -1
- package/dist/src/acl/PredicatesInjector.js +7 -1
- package/dist/src/acl/PredicatesInjector.js.map +1 -1
- package/dist/src/acl/VariableInjector.d.ts +1 -0
- package/dist/src/acl/VariableInjector.d.ts.map +1 -1
- package/dist/src/acl/VariableInjector.js +8 -1
- package/dist/src/acl/VariableInjector.js.map +1 -1
- package/dist/src/mapper/select/ConditionBuilder.d.ts +1 -1
- package/dist/src/mapper/select/ConditionBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/ConditionBuilder.js +3 -0
- package/dist/src/mapper/select/ConditionBuilder.js.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.js +13 -11
- package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
- package/dist/src/mapper/select/optimizer/helpers.d.ts +2 -2
- package/dist/src/mapper/select/optimizer/helpers.d.ts.map +1 -1
- package/dist/src/mapper/select/optimizer/helpers.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/unit/predicatesInjector.test.js +20 -0
- package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
- package/dist/tests/cases/unit/whereBuilder.test.js +15 -0
- package/dist/tests/cases/unit/whereBuilder.test.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/acl/PredicatesInjector.ts +7 -1
- package/src/acl/VariableInjector.ts +9 -1
- package/src/mapper/select/ConditionBuilder.ts +4 -1
- package/src/mapper/select/WhereBuilder.ts +20 -17
- package/src/mapper/select/optimizer/helpers.ts +2 -2
- package/tests/cases/unit/predicatesInjector.test.ts +26 -2
- package/tests/cases/unit/whereBuilder.test.ts +21 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/engine-content-api",
|
|
3
|
-
"version": "1.2.0-
|
|
3
|
+
"version": "1.2.0-rc.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src/index.d.ts",
|
|
@@ -10,31 +10,31 @@
|
|
|
10
10
|
"test": "vitest --no-threads"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@contember/database": "^1.2.0-
|
|
14
|
-
"@contember/dic": "^1.2.0-
|
|
15
|
-
"@contember/logger": "^1.2.0-
|
|
16
|
-
"@contember/graphql-utils": "^1.2.0-
|
|
17
|
-
"@contember/schema": "^1.2.0-
|
|
18
|
-
"@contember/schema-definition": "^1.2.0-
|
|
19
|
-
"@contember/schema-utils": "^1.2.0-
|
|
13
|
+
"@contember/database": "^1.2.0-rc.2",
|
|
14
|
+
"@contember/dic": "^1.2.0-rc.2",
|
|
15
|
+
"@contember/logger": "^1.2.0-rc.2",
|
|
16
|
+
"@contember/graphql-utils": "^1.2.0-rc.2",
|
|
17
|
+
"@contember/schema": "^1.2.0-rc.2",
|
|
18
|
+
"@contember/schema-definition": "^1.2.0-rc.2",
|
|
19
|
+
"@contember/schema-utils": "^1.2.0-rc.2",
|
|
20
20
|
"@graphql-tools/schema": "^8.3.5",
|
|
21
21
|
"fast-deep-equal": "^3.1.3",
|
|
22
22
|
"graphql-tag": "^2.12.5"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@contember/database-tester": "^1.2.0-
|
|
26
|
-
"@contember/engine-api-tester": "^1.2.0-
|
|
27
|
-
"@contember/schema-definition": "^1.2.0-
|
|
25
|
+
"@contember/database-tester": "^1.2.0-rc.2",
|
|
26
|
+
"@contember/engine-api-tester": "^1.2.0-rc.2",
|
|
27
|
+
"@contember/schema-definition": "^1.2.0-rc.2",
|
|
28
28
|
"@graphql-codegen/cli": "^2.6.2",
|
|
29
29
|
"@graphql-codegen/typescript": "^2.5.1",
|
|
30
30
|
"@graphql-codegen/typescript-operations": "^2.4.2",
|
|
31
31
|
"@graphql-codegen/typescript-resolvers": "^2.6.6",
|
|
32
32
|
"@types/node": "^18",
|
|
33
33
|
"graphql": "^16.5.0",
|
|
34
|
-
"pg": "^8.
|
|
34
|
+
"pg": "^8.9.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"graphql": ">= 14.6.0",
|
|
38
|
-
"pg": "^8.
|
|
38
|
+
"pg": "^8.9.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -54,7 +54,13 @@ export class PredicatesInjector {
|
|
|
54
54
|
for (let field of fields) {
|
|
55
55
|
resultWhere[field] = acceptFieldVisitor(this.schema, entity, field, {
|
|
56
56
|
visitColumn: () => where[field],
|
|
57
|
-
visitRelation: context =>
|
|
57
|
+
visitRelation: context => {
|
|
58
|
+
const relationWhere = where[field] as Input.OptionalWhere | null
|
|
59
|
+
if (relationWhere === null) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
return this.injectToWhere(relationWhere, context.targetEntity, false, context)
|
|
63
|
+
},
|
|
58
64
|
})
|
|
59
65
|
}
|
|
60
66
|
const fieldsForPredicate = !isRoot
|
|
@@ -2,12 +2,18 @@ import { Acl, Input, Model } from '@contember/schema'
|
|
|
2
2
|
import { PredicateDefinitionProcessor } from '@contember/schema-utils'
|
|
3
3
|
|
|
4
4
|
export class VariableInjector {
|
|
5
|
+
private injectorCache = new WeakMap<Acl.PredicateDefinition, Input.Where>()
|
|
6
|
+
|
|
5
7
|
constructor(private readonly schema: Model.Schema, private readonly variables: Acl.VariablesMap) {}
|
|
6
8
|
|
|
7
9
|
public inject(entity: Model.Entity, where: Acl.PredicateDefinition): Input.Where {
|
|
10
|
+
const cacheEntry = this.injectorCache.get(where)
|
|
11
|
+
if (cacheEntry !== undefined) {
|
|
12
|
+
return cacheEntry
|
|
13
|
+
}
|
|
8
14
|
const predicateDefinitionProcessor = new PredicateDefinitionProcessor(this.schema)
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
const resultWhere = predicateDefinitionProcessor.process(entity, where, {
|
|
11
17
|
handleColumn: ({ value }) => {
|
|
12
18
|
if (typeof value === 'string') {
|
|
13
19
|
return this.variables[value] ?? { never: true }
|
|
@@ -21,5 +27,7 @@ export class VariableInjector {
|
|
|
21
27
|
return value
|
|
22
28
|
},
|
|
23
29
|
})
|
|
30
|
+
this.injectorCache.set(where, resultWhere)
|
|
31
|
+
return resultWhere
|
|
24
32
|
}
|
|
25
33
|
}
|
|
@@ -8,8 +8,11 @@ export class ConditionBuilder {
|
|
|
8
8
|
tableName: string,
|
|
9
9
|
columnName: string,
|
|
10
10
|
columnType: string,
|
|
11
|
-
condition: Input.Condition<any
|
|
11
|
+
condition: Input.Condition<any> | null,
|
|
12
12
|
): SqlConditionBuilder {
|
|
13
|
+
if (condition === null) {
|
|
14
|
+
return builder
|
|
15
|
+
}
|
|
13
16
|
const entries = Object.entries(condition)
|
|
14
17
|
.filter(<T extends keyof Input.Condition>(it: [string, any]): it is [T, Input.Condition[T]] =>
|
|
15
18
|
it[1] !== null,
|
|
@@ -87,7 +87,7 @@ export class WhereBuilder {
|
|
|
87
87
|
): SqlConditionBuilder {
|
|
88
88
|
const tableName = path.alias
|
|
89
89
|
|
|
90
|
-
if (where.and !== undefined && where.and.length > 0) {
|
|
90
|
+
if (where.and !== undefined && where.and !== null && where.and.length > 0) {
|
|
91
91
|
const expr = where.and
|
|
92
92
|
conditionBuilder = conditionBuilder.and(clause =>
|
|
93
93
|
expr.reduce(
|
|
@@ -104,7 +104,7 @@ export class WhereBuilder {
|
|
|
104
104
|
),
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
|
-
if (where.or !== undefined && where.or.length > 0) {
|
|
107
|
+
if (where.or !== undefined && where.or !== null && where.or.length > 0) {
|
|
108
108
|
const expr = where.or
|
|
109
109
|
conditionBuilder = conditionBuilder.or(clause =>
|
|
110
110
|
expr.reduce(
|
|
@@ -123,7 +123,7 @@ export class WhereBuilder {
|
|
|
123
123
|
),
|
|
124
124
|
)
|
|
125
125
|
}
|
|
126
|
-
if (where.not !== undefined) {
|
|
126
|
+
if (where.not !== undefined && where.not !== null) {
|
|
127
127
|
const expr = where.not
|
|
128
128
|
conditionBuilder = conditionBuilder.not(clause =>
|
|
129
129
|
this.buildRecursive({
|
|
@@ -141,12 +141,16 @@ export class WhereBuilder {
|
|
|
141
141
|
if (fieldName === 'and' || fieldName === 'or' || fieldName === 'not') {
|
|
142
142
|
continue
|
|
143
143
|
}
|
|
144
|
+
const fieldWhere = where[fieldName]
|
|
145
|
+
if (!fieldWhere) {
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
144
148
|
|
|
145
149
|
const targetPath = path.for(fieldName)
|
|
146
150
|
|
|
147
151
|
const joinedWhere = ({ targetEntity, relation, entity }: Model.AnyRelationContext): SqlConditionBuilder => {
|
|
148
|
-
const relationWhere = where[fieldName] as Input.
|
|
149
|
-
if (Object.keys(relationWhere).length === 0) {
|
|
152
|
+
const relationWhere = where[fieldName] as Input.OptionalWhere | null
|
|
153
|
+
if (!relationWhere || Object.keys(relationWhere).length === 0) {
|
|
150
154
|
return conditionBuilder
|
|
151
155
|
}
|
|
152
156
|
if (isIt<Model.JoiningColumnRelation>(relation, 'joiningColumn')) {
|
|
@@ -174,10 +178,11 @@ export class WhereBuilder {
|
|
|
174
178
|
})
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
|
|
182
|
+
|
|
177
183
|
conditionBuilder = acceptFieldVisitor<SqlConditionBuilder>(this.schema, entity, fieldName, {
|
|
178
184
|
visitColumn: ({ entity, column }) => {
|
|
179
|
-
|
|
180
|
-
return this.conditionBuilder.build(conditionBuilder, tableName, column.columnName, column.columnType, subWhere)
|
|
185
|
+
return this.conditionBuilder.build(conditionBuilder, tableName, column.columnName, column.columnType, fieldWhere as Input.Condition<Input.ColumnValue>)
|
|
181
186
|
},
|
|
182
187
|
visitOneHasOneInverse: joinedWhere,
|
|
183
188
|
visitOneHasOneOwning: joinedWhere,
|
|
@@ -187,12 +192,10 @@ export class WhereBuilder {
|
|
|
187
192
|
return joinedWhere(context)
|
|
188
193
|
}
|
|
189
194
|
|
|
190
|
-
const relationWhere = where[fieldName] as Input.Where
|
|
191
|
-
|
|
192
195
|
return conditionBuilder.exists(
|
|
193
196
|
this.createManyHasManySubquery(
|
|
194
197
|
[tableName, entity.primaryColumn],
|
|
195
|
-
|
|
198
|
+
fieldWhere as Input.OptionalWhere,
|
|
196
199
|
context.targetEntity,
|
|
197
200
|
context.targetRelation.joiningTable,
|
|
198
201
|
'inverse',
|
|
@@ -205,12 +208,12 @@ export class WhereBuilder {
|
|
|
205
208
|
return joinedWhere(context)
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
const relationWhere = where[fieldName] as Input.Where
|
|
211
|
+
const relationWhere = where[fieldName] as Input.Where | null
|
|
209
212
|
|
|
210
213
|
return conditionBuilder.exists(
|
|
211
214
|
this.createManyHasManySubquery(
|
|
212
215
|
[tableName, entity.primaryColumn],
|
|
213
|
-
|
|
216
|
+
fieldWhere as Input.OptionalWhere,
|
|
214
217
|
context.targetEntity,
|
|
215
218
|
context.relation.joiningTable,
|
|
216
219
|
'owning',
|
|
@@ -223,7 +226,7 @@ export class WhereBuilder {
|
|
|
223
226
|
return joinedWhere(context)
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
const relationWhere =
|
|
229
|
+
const relationWhere = fieldWhere as Input.OptionalWhere
|
|
227
230
|
|
|
228
231
|
const qb = this.hasRootIsNull(relationWhere, context.targetEntity)
|
|
229
232
|
? SelectBuilder.create()
|
|
@@ -255,7 +258,7 @@ export class WhereBuilder {
|
|
|
255
258
|
|
|
256
259
|
private createManyHasManySubquery(
|
|
257
260
|
outerColumn: QueryBuilder.ColumnIdentifier,
|
|
258
|
-
relationWhere: Input.
|
|
261
|
+
relationWhere: Input.OptionalWhere,
|
|
259
262
|
targetEntity: Model.Entity,
|
|
260
263
|
joiningTable: Model.JoiningTable,
|
|
261
264
|
fromSide: 'owning' | 'inverse',
|
|
@@ -291,7 +294,7 @@ export class WhereBuilder {
|
|
|
291
294
|
})
|
|
292
295
|
}
|
|
293
296
|
|
|
294
|
-
private transformWhereToPrimaryCondition(where: Input.
|
|
297
|
+
private transformWhereToPrimaryCondition(where: Input.OptionalWhere, primaryField: string): Input.Condition<never> | null {
|
|
295
298
|
const keys = Object.keys(where)
|
|
296
299
|
if (keys.filter(it => !['and', 'or', 'not', primaryField].includes(it)).length > 0) {
|
|
297
300
|
return null
|
|
@@ -335,10 +338,10 @@ export class WhereBuilder {
|
|
|
335
338
|
return condition
|
|
336
339
|
}
|
|
337
340
|
|
|
338
|
-
private hasRootIsNull(where: Input.
|
|
341
|
+
private hasRootIsNull(where: Input.OptionalWhere, entity: Model.Entity): boolean {
|
|
339
342
|
for (const key in where) {
|
|
340
343
|
if (key === 'and' || key === 'or') {
|
|
341
|
-
if (where[key]?.some(it => this.hasRootIsNull(it, entity))) {
|
|
344
|
+
if (where[key]?.some(it => it && this.hasRootIsNull(it, entity))) {
|
|
342
345
|
return true
|
|
343
346
|
}
|
|
344
347
|
} else if (key === 'not') {
|
|
@@ -47,11 +47,11 @@ export const optimizeAnd = <P extends Record<string, unknown>>(operands: readonl
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export const optimizeNot = <P extends Record<string, unknown>>(operand: P | boolean): P['not'] | { not: P } | boolean => {
|
|
50
|
+
export const optimizeNot = <P extends Record<string, unknown>>(operand: P | boolean): Exclude<P['not'] | { not: P } | boolean, null | undefined> => {
|
|
51
51
|
if (typeof operand === 'boolean') {
|
|
52
52
|
return !operand
|
|
53
53
|
} else if (operand.not) {
|
|
54
|
-
return operand.not as P['not']
|
|
54
|
+
return operand.not as Exclude<P['not'], null | undefined>
|
|
55
55
|
} else {
|
|
56
56
|
return { not: operand }
|
|
57
57
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { PermissionFactory, PredicateFactory, PredicatesInjector, VariableInjector } from '../../../src/acl'
|
|
2
|
-
import { SchemaBuilder, SchemaDefinition as def, AclDefinition as acl, createSchema } from '@contember/schema-definition'
|
|
2
|
+
import { SchemaBuilder, SchemaDefinition as def, AclDefinition as acl, createSchema, PermissionsBuilder } from '@contember/schema-definition'
|
|
3
3
|
import { Acl, Model } from '@contember/schema'
|
|
4
4
|
import { describe, it, assert } from 'vitest'
|
|
5
5
|
import { PermissionsFactory } from '@contember/engine-system-api/dist/src/model'
|
|
6
6
|
import { WhereOptimizer } from '../../../src/mapper/select/optimizer/WhereOptimizer'
|
|
7
7
|
import { ConditionOptimizer } from '../../../src/mapper/select/optimizer/ConditionOptimizer'
|
|
8
|
-
import { acceptFieldVisitor } from '@contember/schema-utils'
|
|
8
|
+
import { acceptFieldVisitor, AllowAllPermissionFactory } from '@contember/schema-utils'
|
|
9
9
|
import { testUuid } from '@contember/engine-api-tester'
|
|
10
10
|
|
|
11
11
|
|
|
@@ -204,3 +204,27 @@ describe('predicates injector elimination', () => {
|
|
|
204
204
|
})
|
|
205
205
|
})
|
|
206
206
|
})
|
|
207
|
+
|
|
208
|
+
describe('predicate injector input handling', () => {
|
|
209
|
+
const schema = createSchema(DeepFilterModel)
|
|
210
|
+
const permissions = new AllowAllPermissionFactory().create(schema.model)
|
|
211
|
+
const injector = new PredicatesInjector(
|
|
212
|
+
schema.model,
|
|
213
|
+
new PredicateFactory(permissions, schema.model, new VariableInjector(schema.model, {})),
|
|
214
|
+
)
|
|
215
|
+
it('handles null', () => {
|
|
216
|
+
injector.inject(schema.model.entities.ImageUse, {
|
|
217
|
+
image: null,
|
|
218
|
+
articles: {
|
|
219
|
+
and: [
|
|
220
|
+
null,
|
|
221
|
+
{
|
|
222
|
+
title: null,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
not: null,
|
|
226
|
+
or: null,
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
})
|
|
@@ -25,7 +25,7 @@ namespace WhereBuilderModel {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const createWhere = (schema: Schema, where: Input.
|
|
28
|
+
const createWhere = (schema: Schema, where: Input.OptionalWhere) => {
|
|
29
29
|
const pathFactory = new PathFactory()
|
|
30
30
|
const joinBuilder = new JoinBuilder(schema.model)
|
|
31
31
|
const conditionBuilder = new ConditionBuilder()
|
|
@@ -133,6 +133,26 @@ describe('where builder', () => {
|
|
|
133
133
|
where "root_"."id" = "root_articles"."author_id" and "root_articles_tags"."name" = ? and "root_articles_tags"."name" = ?)`,
|
|
134
134
|
)
|
|
135
135
|
})
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
it('handles nulls', () => {
|
|
139
|
+
const schema: Schema = { ...createSchema(WhereBuilderModel) }
|
|
140
|
+
const where = createWhere(schema, {
|
|
141
|
+
name: null,
|
|
142
|
+
articles: {
|
|
143
|
+
and: [
|
|
144
|
+
null,
|
|
145
|
+
{ and: null },
|
|
146
|
+
{ tags: null },
|
|
147
|
+
],
|
|
148
|
+
not: null,
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
compareWhere(
|
|
152
|
+
where,
|
|
153
|
+
`where exists (select 1 from "__SCHEMA__"."article" as "root_articles" where "root_"."id" = "root_articles"."author_id")`,
|
|
154
|
+
)
|
|
155
|
+
})
|
|
136
156
|
})
|
|
137
157
|
|
|
138
158
|
|