@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.
Files changed (31) hide show
  1. package/dist/src/acl/PredicatesInjector.d.ts.map +1 -1
  2. package/dist/src/acl/PredicatesInjector.js +7 -1
  3. package/dist/src/acl/PredicatesInjector.js.map +1 -1
  4. package/dist/src/acl/VariableInjector.d.ts +1 -0
  5. package/dist/src/acl/VariableInjector.d.ts.map +1 -1
  6. package/dist/src/acl/VariableInjector.js +8 -1
  7. package/dist/src/acl/VariableInjector.js.map +1 -1
  8. package/dist/src/mapper/select/ConditionBuilder.d.ts +1 -1
  9. package/dist/src/mapper/select/ConditionBuilder.d.ts.map +1 -1
  10. package/dist/src/mapper/select/ConditionBuilder.js +3 -0
  11. package/dist/src/mapper/select/ConditionBuilder.js.map +1 -1
  12. package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
  13. package/dist/src/mapper/select/WhereBuilder.js +13 -11
  14. package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
  15. package/dist/src/mapper/select/optimizer/helpers.d.ts +2 -2
  16. package/dist/src/mapper/select/optimizer/helpers.d.ts.map +1 -1
  17. package/dist/src/mapper/select/optimizer/helpers.js.map +1 -1
  18. package/dist/src/tsconfig.tsbuildinfo +1 -1
  19. package/dist/tests/cases/unit/predicatesInjector.test.js +20 -0
  20. package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
  21. package/dist/tests/cases/unit/whereBuilder.test.js +15 -0
  22. package/dist/tests/cases/unit/whereBuilder.test.js.map +1 -1
  23. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +13 -13
  25. package/src/acl/PredicatesInjector.ts +7 -1
  26. package/src/acl/VariableInjector.ts +9 -1
  27. package/src/mapper/select/ConditionBuilder.ts +4 -1
  28. package/src/mapper/select/WhereBuilder.ts +20 -17
  29. package/src/mapper/select/optimizer/helpers.ts +2 -2
  30. package/tests/cases/unit/predicatesInjector.test.ts +26 -2
  31. 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-beta.3",
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-beta.3",
14
- "@contember/dic": "^1.2.0-beta.3",
15
- "@contember/logger": "^1.2.0-beta.3",
16
- "@contember/graphql-utils": "^1.2.0-beta.3",
17
- "@contember/schema": "^1.2.0-beta.3",
18
- "@contember/schema-definition": "^1.2.0-beta.3",
19
- "@contember/schema-utils": "^1.2.0-beta.3",
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-beta.3",
26
- "@contember/engine-api-tester": "^1.2.0-beta.3",
27
- "@contember/schema-definition": "^1.2.0-beta.3",
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.5.0"
34
+ "pg": "^8.9.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "graphql": ">= 14.6.0",
38
- "pg": "^8.5.0"
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 => this.injectToWhere(where[field] as Input.Where, context.targetEntity, false, 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
- return predicateDefinitionProcessor.process(entity, where, {
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.Where
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
- const subWhere: Input.Condition<Input.ColumnValue> = where[column.name] as Input.Condition<Input.ColumnValue>
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
- relationWhere,
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
- relationWhere,
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 = where[fieldName] as Input.Where
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.Where,
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.Where, primaryField: string): Input.Condition<never> | null {
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.Where, entity: Model.Entity): boolean {
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.Where) => {
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