@contember/engine-content-api 1.3.0-alpha.6 → 1.3.0-alpha.8

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 (94) hide show
  1. package/dist/src/ExecutionContainer.d.ts +5 -2
  2. package/dist/src/ExecutionContainer.d.ts.map +1 -1
  3. package/dist/src/ExecutionContainer.js +7 -6
  4. package/dist/src/ExecutionContainer.js.map +1 -1
  5. package/dist/src/acl/PermissionFactory.d.ts +5 -4
  6. package/dist/src/acl/PermissionFactory.d.ts.map +1 -1
  7. package/dist/src/acl/PermissionFactory.js +10 -13
  8. package/dist/src/acl/PermissionFactory.js.map +1 -1
  9. package/dist/src/acl/index.d.ts +0 -1
  10. package/dist/src/acl/index.d.ts.map +1 -1
  11. package/dist/src/acl/index.js +0 -1
  12. package/dist/src/acl/index.js.map +1 -1
  13. package/dist/src/mapper/ErrorUtils.d.ts +3 -2
  14. package/dist/src/mapper/ErrorUtils.d.ts.map +1 -1
  15. package/dist/src/mapper/ErrorUtils.js +55 -35
  16. package/dist/src/mapper/ErrorUtils.js.map +1 -1
  17. package/dist/src/mapper/Mapper.d.ts +3 -1
  18. package/dist/src/mapper/Mapper.d.ts.map +1 -1
  19. package/dist/src/mapper/Mapper.js +9 -6
  20. package/dist/src/mapper/Mapper.js.map +1 -1
  21. package/dist/src/mapper/MapperFactory.d.ts +3 -2
  22. package/dist/src/mapper/MapperFactory.d.ts.map +1 -1
  23. package/dist/src/mapper/MapperFactory.js +3 -2
  24. package/dist/src/mapper/MapperFactory.js.map +1 -1
  25. package/dist/src/mapper/Result.d.ts +2 -1
  26. package/dist/src/mapper/Result.d.ts.map +1 -1
  27. package/dist/src/mapper/Result.js +2 -2
  28. package/dist/src/mapper/Result.js.map +1 -1
  29. package/dist/src/mapper/insert/Inserter.d.ts +3 -2
  30. package/dist/src/mapper/insert/Inserter.d.ts.map +1 -1
  31. package/dist/src/mapper/insert/Inserter.js +4 -3
  32. package/dist/src/mapper/insert/Inserter.js.map +1 -1
  33. package/dist/src/mapper/update/Updater.d.ts +3 -1
  34. package/dist/src/mapper/update/Updater.d.ts.map +1 -1
  35. package/dist/src/mapper/update/Updater.js +3 -2
  36. package/dist/src/mapper/update/Updater.js.map +1 -1
  37. package/dist/src/resolvers/MutationResolver.d.ts +3 -1
  38. package/dist/src/resolvers/MutationResolver.d.ts.map +1 -1
  39. package/dist/src/resolvers/MutationResolver.js +3 -2
  40. package/dist/src/resolvers/MutationResolver.js.map +1 -1
  41. package/dist/src/tsconfig.tsbuildinfo +1 -1
  42. package/dist/src/types.d.ts +1 -1
  43. package/dist/src/types.d.ts.map +1 -1
  44. package/dist/src/utils/index.d.ts +0 -1
  45. package/dist/src/utils/index.d.ts.map +1 -1
  46. package/dist/src/utils/index.js +0 -1
  47. package/dist/src/utils/index.js.map +1 -1
  48. package/dist/src/utils/uniqueWhereFields.js +1 -1
  49. package/dist/src/utils/uniqueWhereFields.js.map +1 -1
  50. package/dist/tests/cases/integration/graphQlRequests/query/acl/isNullAcl.test.d.ts +2 -0
  51. package/dist/tests/cases/integration/graphQlRequests/query/acl/isNullAcl.test.d.ts.map +1 -0
  52. package/dist/tests/cases/integration/graphQlRequests/query/acl/isNullAcl.test.js +91 -0
  53. package/dist/tests/cases/integration/graphQlRequests/query/acl/isNullAcl.test.js.map +1 -0
  54. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js +1 -1
  55. package/dist/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.js.map +1 -1
  56. package/dist/tests/cases/unit/orderbyHelper.test.js +2 -2
  57. package/dist/tests/cases/unit/permissionMerger.test.js +8 -3
  58. package/dist/tests/cases/unit/permissionMerger.test.js.map +1 -1
  59. package/dist/tests/cases/unit/predicatesInjector.test.js +1 -1
  60. package/dist/tests/cases/unit/predicatesInjector.test.js.map +1 -1
  61. package/dist/tests/src/test.d.ts.map +1 -1
  62. package/dist/tests/src/test.js +1 -0
  63. package/dist/tests/src/test.js.map +1 -1
  64. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +11 -11
  66. package/src/ExecutionContainer.ts +13 -9
  67. package/src/acl/PermissionFactory.ts +15 -13
  68. package/src/acl/index.ts +0 -1
  69. package/src/mapper/ErrorUtils.ts +66 -41
  70. package/src/mapper/Mapper.ts +9 -6
  71. package/src/mapper/MapperFactory.ts +3 -1
  72. package/src/mapper/Result.ts +3 -1
  73. package/src/mapper/insert/Inserter.ts +4 -4
  74. package/src/mapper/update/Updater.ts +3 -2
  75. package/src/resolvers/MutationResolver.ts +3 -1
  76. package/src/types.ts +1 -1
  77. package/src/utils/index.ts +0 -1
  78. package/src/utils/uniqueWhereFields.ts +1 -1
  79. package/tests/cases/integration/graphQlRequests/query/acl/isNullAcl.test.ts +77 -0
  80. package/tests/cases/integration/graphQlRequests/query/acl/rowPredicateSubset.test.ts +1 -1
  81. package/tests/cases/unit/orderbyHelper.test.ts +2 -2
  82. package/tests/cases/unit/permissionMerger.test.ts +9 -4
  83. package/tests/cases/unit/predicatesInjector.test.ts +1 -1
  84. package/tests/src/test.ts +2 -1
  85. package/dist/src/acl/PermissionsByIdentityFactory.d.ts +0 -12
  86. package/dist/src/acl/PermissionsByIdentityFactory.d.ts.map +0 -1
  87. package/dist/src/acl/PermissionsByIdentityFactory.js +0 -15
  88. package/dist/src/acl/PermissionsByIdentityFactory.js.map +0 -1
  89. package/dist/src/utils/arrays.d.ts +0 -3
  90. package/dist/src/utils/arrays.d.ts.map +0 -1
  91. package/dist/src/utils/arrays.js +0 -22
  92. package/dist/src/utils/arrays.js.map +0 -1
  93. package/src/acl/PermissionsByIdentityFactory.ts +0 -21
  94. package/src/utils/arrays.ts +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-content-api",
3
- "version": "1.3.0-alpha.6",
3
+ "version": "1.3.0-alpha.8",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -14,21 +14,21 @@
14
14
  "test": "vitest --no-threads"
15
15
  },
16
16
  "dependencies": {
17
- "@contember/database": "^1.3.0-alpha.6",
18
- "@contember/dic": "^1.3.0-alpha.6",
19
- "@contember/logger": "^1.3.0-alpha.6",
20
- "@contember/graphql-utils": "^1.3.0-alpha.6",
21
- "@contember/schema": "^1.3.0-alpha.6",
22
- "@contember/schema-definition": "^1.3.0-alpha.6",
23
- "@contember/schema-utils": "^1.3.0-alpha.6",
17
+ "@contember/database": "^1.3.0-alpha.8",
18
+ "@contember/dic": "^1.3.0-alpha.8",
19
+ "@contember/logger": "^1.3.0-alpha.8",
20
+ "@contember/graphql-utils": "^1.3.0-alpha.8",
21
+ "@contember/schema": "^1.3.0-alpha.8",
22
+ "@contember/schema-definition": "^1.3.0-alpha.8",
23
+ "@contember/schema-utils": "^1.3.0-alpha.8",
24
24
  "@graphql-tools/schema": "^8.3.5",
25
25
  "fast-deep-equal": "^3.1.3",
26
26
  "graphql-tag": "^2.12.5"
27
27
  },
28
28
  "devDependencies": {
29
- "@contember/database-tester": "^1.3.0-alpha.6",
30
- "@contember/engine-api-tester": "^1.3.0-alpha.6",
31
- "@contember/schema-definition": "^1.3.0-alpha.6",
29
+ "@contember/database-tester": "^1.3.0-alpha.8",
30
+ "@contember/engine-api-tester": "^1.3.0-alpha.8",
31
+ "@contember/schema-definition": "^1.3.0-alpha.8",
32
32
  "@graphql-codegen/cli": "^2.6.2",
33
33
  "@graphql-codegen/typescript": "^2.5.1",
34
34
  "@graphql-codegen/typescript-operations": "^2.4.2",
@@ -28,7 +28,7 @@ import {
28
28
  import { Builder } from '@contember/dic'
29
29
  import { Acl, Model, Schema } from '@contember/schema'
30
30
  import { Client, SelectBuilder as DbSelectBuilder } from '@contember/database'
31
- import { Providers } from '@contember/schema-utils'
31
+ import { Providers, SchemaDatabaseMetadata } from '@contember/schema-utils'
32
32
  import { PaginatedHasManyExecutionHandler } from './extensions/paginatedHasMany/PaginatedHasManyExecutionHandler'
33
33
  import { PaginatedHasManyFieldProvider } from './extensions/paginatedHasMany/PaginatedHasManyFieldProvider'
34
34
  import { WhereOptimizer } from './mapper/select/optimizer/WhereOptimizer'
@@ -44,6 +44,7 @@ import {
44
44
 
45
45
  export type ExecutionContainerArgs = {
46
46
  schema: Schema & { id: number }
47
+ schemaDatabaseMetadata: SchemaDatabaseMetadata
47
48
  db: Client
48
49
  identityId: string
49
50
  identityVariables: Acl.VariablesMap
@@ -75,7 +76,7 @@ export class ExecutionContainerFactory {
75
76
  return this.hooks.reduce((acc, cb) => cb(acc), builder)
76
77
  }
77
78
 
78
- createBuilderInternal({ permissions, identityVariables, identityId, db, schema, systemSchema, stage, project }: ExecutionContainerArgs) {
79
+ createBuilderInternal({ permissions, identityVariables, identityId, db, schema, systemSchema, stage, project, schemaDatabaseMetadata }: ExecutionContainerArgs) {
79
80
  return new Builder({})
80
81
  .addService('systemSchema', () =>
81
82
  systemSchema)
@@ -87,6 +88,8 @@ export class ExecutionContainerFactory {
87
88
  stage)
88
89
  .addService('schema', () =>
89
90
  schema)
91
+ .addService('schemaDatabaseMetadata', () =>
92
+ schemaDatabaseMetadata)
90
93
  .addService('providers', () =>
91
94
  this.providers)
92
95
  .addService('variableInjector', ({ schema }) =>
@@ -153,15 +156,16 @@ export class ExecutionContainerFactory {
153
156
  new JunctionTableManager(schema.model, predicateFactory, whereBuilder, connectJunctionHandler, disconnectJunctionHandler, pathFactory))
154
157
  .addService('deleteExecutor', ({ predicateFactory, updateBuilderFactory, whereBuilder, pathFactory, schema }) =>
155
158
  new DeleteExecutor(schema.model, predicateFactory, whereBuilder, updateBuilderFactory, pathFactory))
156
- .addService('updater', ({ predicateFactory, updateBuilderFactory, schema }) =>
157
- new Updater(schema.model, predicateFactory, updateBuilderFactory))
158
- .addService('inserter', ({ insertBuilderFactory, providers, schema }) =>
159
- new Inserter(schema.model, insertBuilderFactory, providers))
160
- .addService('mapperFactory', ({ predicatesInjector, selectBuilderFactory, uniqueWhereExpander, whereBuilder, junctionTableManager, deleteExecutor, updater, inserter, pathFactory, providers, schema }) => {
159
+ .addService('updater', ({ predicateFactory, updateBuilderFactory, schema, schemaDatabaseMetadata }) =>
160
+ new Updater(schema.model, schemaDatabaseMetadata, predicateFactory, updateBuilderFactory))
161
+ .addService('inserter', ({ insertBuilderFactory, providers, schema, schemaDatabaseMetadata }) =>
162
+ new Inserter(schema.model, schemaDatabaseMetadata, insertBuilderFactory, providers))
163
+ .addService('mapperFactory', ({ predicatesInjector, selectBuilderFactory, uniqueWhereExpander, whereBuilder, junctionTableManager, deleteExecutor, updater, inserter, pathFactory, providers, schema, schemaDatabaseMetadata }) => {
161
164
  return new MapperFactory(
162
165
  db,
163
166
  identityId,
164
167
  schema.model,
168
+ schemaDatabaseMetadata,
165
169
  predicatesInjector,
166
170
  selectBuilderFactory,
167
171
  uniqueWhereExpander,
@@ -191,8 +195,8 @@ export class ExecutionContainerFactory {
191
195
  new EntityRulesResolver(schema.validation, schema.model))
192
196
  .addService('inputPreValidator', ({ entityRulesResolver, columnValueResolver, dataSelector, schema }) =>
193
197
  new InputPreValidator(schema.model, entityRulesResolver, columnValueResolver, dataSelector))
194
- .addService('mutationResolver', ({ mapperFactory, inputPreValidator, queryAstFactory, schema }) =>
195
- new MutationResolver(schema.model, mapperFactory, inputPreValidator, queryAstFactory))
198
+ .addService('mutationResolver', ({ mapperFactory, inputPreValidator, queryAstFactory, schema, schemaDatabaseMetadata }) =>
199
+ new MutationResolver(schema.model, mapperFactory, inputPreValidator, queryAstFactory, schemaDatabaseMetadata))
196
200
  .addService('validationResolver', ({ inputPreValidator, mapperFactory }) =>
197
201
  new ValidationResolver(mapperFactory, inputPreValidator))
198
202
  }
@@ -1,33 +1,35 @@
1
- import { Acl, Model, Writable } from '@contember/schema'
1
+ import { Acl, Model, Schema, Writable } from '@contember/schema'
2
2
  import { getEntity, PredicateDefinitionProcessor } from '@contember/schema-utils'
3
3
  import { mapObject } from '../utils'
4
4
  import { prefixVariable } from './VariableUtils'
5
5
 
6
- export class PermissionFactory {
7
- constructor(private readonly schema: Model.Schema) {}
6
+ export interface Identity {
7
+ projectRoles: readonly string[]
8
+ }
8
9
 
9
- public create(acl: Acl.Schema, roles: readonly string[], prefix?: string): Acl.Permissions {
10
+ export class PermissionFactory {
11
+ public create(schema: Schema, roles: readonly string[], prefix?: string): Acl.Permissions {
10
12
  let result: Acl.Permissions = {}
11
13
  for (let role of roles) {
12
- const roleDefinition = acl.roles[role] || { entities: {} }
13
- let rolePermissions: Acl.Permissions = this.prefixPredicatesWithRole(roleDefinition.entities, prefix || role)
14
+ const roleDefinition = schema.acl.roles[role] || { entities: {} }
15
+ let rolePermissions: Acl.Permissions = this.prefixPredicatesWithRole(schema.model, roleDefinition.entities, prefix || role)
14
16
  if (roleDefinition.inherits) {
15
- const inheritedPermissions = this.create(acl, roleDefinition.inherits, prefix || role)
17
+ const inheritedPermissions = this.create(schema, roleDefinition.inherits, prefix || role)
16
18
  rolePermissions = this.mergePermissions(inheritedPermissions, rolePermissions)
17
19
  }
18
20
  result = this.mergePermissions(result, rolePermissions)
19
21
  }
20
- result = this.makePrimaryPredicatesUnionOfAllFields(result)
22
+ result = this.makePrimaryPredicatesUnionOfAllFields(schema.model, result)
21
23
 
22
24
  return result
23
25
  }
24
26
 
25
- private prefixPredicatesWithRole(permissions: Acl.Permissions, role: string): Acl.Permissions {
27
+ private prefixPredicatesWithRole(model: Model.Schema, permissions: Acl.Permissions, role: string): Acl.Permissions {
26
28
  return mapObject(permissions, ({ operations, predicates }, entityName) => ({
27
29
  operations,
28
30
  predicates: mapObject(predicates, predicate => {
29
- const predicateDefinitionProcessor = new PredicateDefinitionProcessor(this.schema)
30
- return predicateDefinitionProcessor.process(getEntity(this.schema, entityName), predicate, {
31
+ const predicateDefinitionProcessor = new PredicateDefinitionProcessor(model)
32
+ return predicateDefinitionProcessor.process(getEntity(model, entityName), predicate, {
31
33
  handleColumn: ({ value }) => {
32
34
  if (typeof value === 'string') {
33
35
  return prefixVariable(role, value)
@@ -45,9 +47,9 @@ export class PermissionFactory {
45
47
  }))
46
48
  }
47
49
 
48
- private makePrimaryPredicatesUnionOfAllFields(permissions: Acl.Permissions): Acl.Permissions {
50
+ private makePrimaryPredicatesUnionOfAllFields(model: Model.Schema, permissions: Acl.Permissions): Acl.Permissions {
49
51
  return mapObject(permissions, (permission, entityName): Acl.EntityPermissions => {
50
- const entity = getEntity(this.schema, entityName)
52
+ const entity = getEntity(model, entityName)
51
53
  const entityPredicates: Writable<Acl.PredicateMap> = { ...permission.predicates }
52
54
  const entityOperations: Writable<Acl.EntityOperations> = { ...permission.operations }
53
55
 
package/src/acl/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from './Authorizator'
2
2
  export * from './PermissionFactory'
3
- export * from './PermissionsByIdentityFactory'
4
3
  export * from './PredicateFactory'
5
4
  export * from './PredicatesInjector'
6
5
  export * from './VariableInjector'
@@ -11,31 +11,37 @@ import {
11
11
  import * as Database from '@contember/database'
12
12
  import { UniqueWhereError } from '../inputProcessing'
13
13
  import { Model } from '@contember/schema'
14
- import { isOwningRelation, isRelation, NamingHelper } from '@contember/schema-utils'
14
+ import { acceptFieldVisitor, SchemaDatabaseMetadata, tryGetColumnName } from '@contember/schema-utils'
15
15
  import { logger } from '@contember/logger'
16
- import RelationType = Model.RelationType
17
16
 
18
17
 
19
- const findUniqueConstraint = (schema: Model.Schema, constraintName: string): [Model.Entity, Model.UniqueConstraint] | [null, null] => {
20
- for (const entity of Object.values(schema.entities)) {
21
- for (const constraint of Object.values(entity.unique)) {
22
- if (constraint.name === constraintName) {
23
- return [entity, constraint]
24
- }
25
- }
18
+ type UniqueConstraintResult = {
19
+ entity: Model.Entity
20
+ fields: string[]
21
+ }
22
+
23
+ const getEntityByTableName = (model: Model.Schema, tableName: string) => Object.values(model.entities).find(it => it.tableName === tableName)
24
+
25
+ const findUniqueConstraint = (model: Model.Schema, databaseMetadata: SchemaDatabaseMetadata, tableName: string, constraintName: string): UniqueConstraintResult | null => {
26
+ const constraint = databaseMetadata.getUniqueConstraint(tableName, constraintName)
27
+ if (!constraint) {
28
+ return null
26
29
  }
27
- for (const entity of Object.values(schema.entities)) {
28
- for (const field of Object.values(entity.fields)) {
29
- if (
30
- field.type === RelationType.OneHasOne &&
31
- isOwningRelation(field) &&
32
- NamingHelper.createUniqueConstraintName(entity.name, [field.name]) === constraintName
33
- ) {
34
- return [entity, { name: constraintName, fields: [field.name] }]
35
- }
30
+ const entity = getEntityByTableName(model, constraint.tableName)
31
+ if (!entity) {
32
+ return null
33
+ }
34
+ const fieldsInConstraint: string[] = []
35
+ for (const field of Object.values(entity.fields)) {
36
+ const columnName = tryGetColumnName(model, entity, field.name)
37
+ if (columnName && constraint.columnNames.includes(columnName)) {
38
+ fieldsInConstraint.push(field.name)
36
39
  }
37
40
  }
38
- return [null, null]
41
+ if (fieldsInConstraint.length === constraint.columnNames.length) {
42
+ return { entity, fields: fieldsInConstraint }
43
+ }
44
+ return null
39
45
  }
40
46
 
41
47
  type RelationInfo = {
@@ -44,28 +50,47 @@ type RelationInfo = {
44
50
  targetEntity: Model.Entity
45
51
  }
46
52
 
47
- const findForeignConstraint = (schema: Model.Schema, constraintName: string): RelationInfo | null => {
48
- for (const owningEntity of Object.values(schema.entities)) {
49
- for (const field of Object.values(owningEntity.fields)) {
50
- if (!isRelation(field) || !isOwningRelation(field) || !('joiningColumn' in field)) {
51
- continue
52
- }
53
- const targetEntity = schema.entities[field.target]
54
- const fkName = NamingHelper.createForeignKeyName(
55
- owningEntity.tableName,
56
- field.joiningColumn.columnName,
57
- targetEntity.tableName,
58
- targetEntity.primaryColumn,
59
- )
60
- if (fkName === constraintName) {
61
- return { owningEntity, targetEntity, owningRelation: field }
53
+ const findForeignConstraint = (model: Model.Schema, databaseMetadata: SchemaDatabaseMetadata, tableName: string, constraintName: string): RelationInfo | null => {
54
+ const constraint = databaseMetadata.getForeignKeyConstraint(tableName, constraintName)
55
+ if (!constraint) {
56
+ return null
57
+ }
58
+ const owningEntity = getEntityByTableName(model, constraint.fromTable)
59
+ if (!owningEntity) {
60
+ // todo m:n
61
+ return null
62
+ }
63
+
64
+ const targetEntity = getEntityByTableName(model, constraint.toTable)
65
+ if (!targetEntity) {
66
+ return null
67
+ }
68
+ for (const field of Object.values(owningEntity.fields)) {
69
+ const relation = acceptFieldVisitor<(Model.Relation & Model.JoiningColumnRelation) | null>(model, owningEntity, field, {
70
+ visitColumn: () => null,
71
+ visitOneHasOneInverse: () => null,
72
+ visitManyHasManyInverse: () => null,
73
+ visitManyHasManyOwning: () => null,
74
+ visitOneHasMany: () => null,
75
+ visitOneHasOneOwning: ({ relation }) => relation,
76
+ visitManyHasOne: ({ relation }) => relation,
77
+ })
78
+ if (relation && relation.joiningColumn.columnName === constraint.fromColumn) {
79
+ return {
80
+ owningEntity,
81
+ owningRelation: relation,
82
+ targetEntity,
62
83
  }
63
84
  }
64
85
  }
65
86
  return null
66
87
  }
67
88
 
68
- export const convertError = (schema: Model.Schema, e: any): MutationResult => {
89
+ export const convertError = (
90
+ schema: Model.Schema,
91
+ databaseMetadata: SchemaDatabaseMetadata,
92
+ e: any,
93
+ ): MutationResult => {
69
94
 
70
95
  if (e instanceof Database.NotNullViolationError) {
71
96
  return new MutationConstraintViolationError([], ConstraintType.notNull, e.originalMessage, [
@@ -73,7 +98,7 @@ export const convertError = (schema: Model.Schema, e: any): MutationResult => {
73
98
  ])
74
99
  }
75
100
  if (e instanceof Database.ForeignKeyViolationError) {
76
- const relationInfo = e.constraint ? findForeignConstraint(schema, e.constraint) : null
101
+ const relationInfo = e.constraint && e.table ? findForeignConstraint(schema, databaseMetadata, e.table, e.constraint) : null
77
102
  const violationDetail: string = 'detail' in e.previous ? e.previous.detail : ''
78
103
  const matchResult = violationDetail.match(/^Key \(.+\)=\((.+)\) is still referenced from table/)
79
104
  const message = relationInfo
@@ -85,13 +110,13 @@ export const convertError = (schema: Model.Schema, e: any): MutationResult => {
85
110
  return new MutationConstraintViolationError([], ConstraintType.foreignKey, message, [MutationResultHint.sqlError])
86
111
  }
87
112
  if (e instanceof Database.UniqueViolationError) {
88
- const [entity, constraint] = e.constraint ? findUniqueConstraint(schema, e.constraint) : [null, null]
113
+ const constraint = e.constraint && e.table ? findUniqueConstraint(schema, databaseMetadata, e.table, e.constraint) : null
89
114
  const violationDetail: string = 'detail' in e.previous ? e.previous.detail : ''
90
115
  const matchResult = violationDetail.match(/^Key \(.+\)=\((.+)\) already exists\.$/)
91
116
  const values = matchResult?.[1]
92
- const message = constraint && entity
117
+ const message = constraint
93
118
  ? `${values ? `Value (${values})` : 'Unknown value'}` +
94
- ` already exists in unique columns (${constraint.fields.join(', ')}) on entity ${entity.name}`
119
+ ` already exists in unique columns (${constraint.fields.join(', ')}) on entity ${constraint.entity.name}`
95
120
  : e.originalMessage
96
121
  const paths = constraint ? constraint.fields.map(it => [{ field: it }]) : []
97
122
  return new MutationConstraintViolationError(paths, ConstraintType.uniqueKey, message, [MutationResultHint.sqlError])
@@ -115,10 +140,10 @@ export const convertError = (schema: Model.Schema, e: any): MutationResult => {
115
140
  throw e
116
141
  }
117
142
 
118
- export const tryMutation = async (schema: Model.Schema, cb: () => Promise<MutationResultList>): Promise<MutationResultList> => {
143
+ export const tryMutation = async (schema: Model.Schema, databaseMetadata: SchemaDatabaseMetadata, cb: () => Promise<MutationResultList>): Promise<MutationResultList> => {
119
144
  try {
120
145
  return await cb()
121
146
  } catch (e) {
122
- return [convertError(schema, e)]
147
+ return [convertError(schema, databaseMetadata, e)]
123
148
  }
124
149
  }
@@ -1,5 +1,5 @@
1
1
  import { Input, Model } from '@contember/schema'
2
- import { acceptFieldVisitor, getColumnName } from '@contember/schema-utils'
2
+ import { acceptFieldVisitor, getColumnName, SchemaDatabaseMetadata } from '@contember/schema-utils'
3
3
  import {
4
4
  OrderByHelper,
5
5
  PathFactory,
@@ -38,6 +38,7 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
38
38
  public readonly identityId: string,
39
39
  public readonly transactionId: string,
40
40
  private readonly schema: Model.Schema,
41
+ private readonly schemaDatabaseMetadata: SchemaDatabaseMetadata,
41
42
  private readonly predicatesInjector: PredicatesInjector,
42
43
  private readonly selectBuilderFactory: SelectBuilderFactory,
43
44
  private readonly uniqueWhereExpander: UniqueWhereExpander,
@@ -181,7 +182,7 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
181
182
  throw new ImplementationException()
182
183
  }
183
184
  await this.setupSystemVariables()
184
- return tryMutation(this.schema, () =>
185
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, () =>
185
186
  this.insertInternal(entity, data, builderCb),
186
187
  )
187
188
  }
@@ -196,7 +197,7 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
196
197
  throw new ImplementationException()
197
198
  }
198
199
  await this.setupSystemVariables()
199
- return tryMutation(this.schema, async () => {
200
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, async () => {
200
201
  const primaryValue = await this.getPrimaryValue(entity, by)
201
202
  if (primaryValue === undefined) {
202
203
  return [new MutationEntryNotFoundError([], by)]
@@ -214,7 +215,7 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
214
215
  throw new ImplementationException()
215
216
  }
216
217
  await this.setupSystemVariables()
217
- return tryMutation(this.schema, async () => {
218
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, async () => {
218
219
  const primaryValue = await this.getPrimaryValue(entity, by)
219
220
  if (primaryValue === undefined) {
220
221
  return [new MutationEntryNotFoundError([], by as Input.UniqueWhere)]
@@ -233,7 +234,7 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
233
234
  throw new ImplementationException()
234
235
  }
235
236
  await this.setupSystemVariables()
236
- return tryMutation(this.schema, async () => {
237
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, async () => {
237
238
  const primaryValue = await this.getPrimaryValue(entity, by)
238
239
  if (primaryValue === undefined) {
239
240
  return await this.insertInternal(entity, create)
@@ -258,7 +259,9 @@ export class Mapper<ConnectionType extends Connection.ConnectionLike = Connectio
258
259
  throw new ImplementationException()
259
260
  }
260
261
  await this.setupSystemVariables()
261
- return tryMutation(this.schema, () => this.deleteExecutor.execute(this, entity, by, filter))
262
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, () => {
263
+ return this.deleteExecutor.execute(this, entity, by, filter)
264
+ })
262
265
  }
263
266
 
264
267
  public async connectJunction(
@@ -8,7 +8,7 @@ import { Inserter } from './insert'
8
8
  import { Model } from '@contember/schema'
9
9
  import { Client, Connection } from '@contember/database'
10
10
  import { Mapper } from './Mapper'
11
- import { Providers } from '@contember/schema-utils'
11
+ import { Providers, SchemaDatabaseMetadata } from '@contember/schema-utils'
12
12
 
13
13
  export type MapperFactoryHook = (mapper: Mapper) => void
14
14
 
@@ -20,6 +20,7 @@ export class MapperFactory {
20
20
  private readonly db: Client,
21
21
  private readonly identityId: string,
22
22
  private readonly schema: Model.Schema,
23
+ private readonly schemaDatabaseMetadata: SchemaDatabaseMetadata,
23
24
  private readonly predicatesInjector: PredicatesInjector,
24
25
  private readonly selectBuilderFactory: SelectBuilderFactory,
25
26
  private readonly uniqueWhereExpander: UniqueWhereExpander,
@@ -51,6 +52,7 @@ export class MapperFactory {
51
52
  this.identityId,
52
53
  this.providers.uuid(),
53
54
  this.schema,
55
+ this.schemaDatabaseMetadata,
54
56
  this.predicatesInjector,
55
57
  this.selectBuilderFactory,
56
58
  this.uniqueWhereExpander,
@@ -3,6 +3,7 @@ import { convertError } from './ErrorUtils'
3
3
  import { getFulfilledValues, getRejections } from '../utils'
4
4
  import { SerializationFailureError } from '@contember/database'
5
5
  import { logger } from '@contember/logger'
6
+ import { SchemaDatabaseMetadata } from '@contember/schema-utils'
6
7
 
7
8
  export enum MutationResultType {
8
9
  ok = 'ok',
@@ -212,6 +213,7 @@ export type ResultListNotFlatten = MutationResultList | MutationResultList[]
212
213
 
213
214
  export const collectResults = async (
214
215
  schema: Model.Schema,
216
+ schemaDatabaseMetadata: SchemaDatabaseMetadata,
215
217
  mainPromise: Promise<ResultListNotFlatten | undefined> | undefined,
216
218
  otherPromises: (Promise<ResultListNotFlatten | undefined> | undefined)[],
217
219
  ): Promise<MutationResultList> => {
@@ -220,7 +222,7 @@ export const collectResults = async (
220
222
  .filter((it): it is Promise<ResultListNotFlatten> => !!it)
221
223
  .map(it =>
222
224
  it //
223
- .catch(e => [convertError(schema, e)])
225
+ .catch(e => [convertError(schema, schemaDatabaseMetadata, e)])
224
226
  .then(value => ({ value, index: index++ })),
225
227
  )
226
228
  const results = await Promise.allSettled(allPromises)
@@ -11,9 +11,8 @@ import { CreateInputVisitor } from '../../inputProcessing'
11
11
  import { SqlCreateInputProcessor } from './SqlCreateInputProcessor'
12
12
  import { Mapper } from '../Mapper'
13
13
  import { Input, Model, Value } from '@contember/schema'
14
- import { acceptEveryFieldVisitor, Providers } from '@contember/schema-utils'
14
+ import { acceptEveryFieldVisitor, Providers, SchemaDatabaseMetadata } from '@contember/schema-utils'
15
15
  import { InsertBuilderFactory } from './InsertBuilderFactory'
16
- import { Client } from '@contember/database'
17
16
  import { InsertBuilder } from './InsertBuilder'
18
17
  import { tryMutation } from '../ErrorUtils'
19
18
  import { rowDataToFieldValues } from '../ColumnValue'
@@ -21,6 +20,7 @@ import { rowDataToFieldValues } from '../ColumnValue'
21
20
  export class Inserter {
22
21
  constructor(
23
22
  private readonly schema: Model.Schema,
23
+ private readonly schemaDatabaseMetadata: SchemaDatabaseMetadata,
24
24
  private readonly insertBuilderFactory: InsertBuilderFactory,
25
25
  private readonly providers: Providers,
26
26
  ) {}
@@ -53,7 +53,7 @@ export class Inserter {
53
53
  new MutationCreateOk([], entity, primary, data, values)
54
54
  const insertPromise = this.executeInsert(insertBuilder, mapper, okResultFactory)
55
55
 
56
- return await collectResults(this.schema, insertPromise, Object.values(promises))
56
+ return await collectResults(this.schema, this.schemaDatabaseMetadata, insertPromise, Object.values(promises))
57
57
  }
58
58
 
59
59
  private async executeInsert(
@@ -61,7 +61,7 @@ export class Inserter {
61
61
  mapper: Mapper,
62
62
  okResultFactory: (primary: Value.PrimaryValue, values: RowValues) => MutationCreateOk,
63
63
  ): Promise<MutationResultList> {
64
- return tryMutation(this.schema, async () => {
64
+ return tryMutation(this.schema, this.schemaDatabaseMetadata, async () => {
65
65
  const result = await insertBuilder.execute(mapper)
66
66
  if (result.aborted) {
67
67
  return [new MutationNothingToDo([], NothingToDoReason.aborted)]
@@ -4,7 +4,7 @@ import { Input, Model } from '@contember/schema'
4
4
  import { PredicateFactory } from '../../acl'
5
5
  import { UpdateBuilderFactory } from './UpdateBuilderFactory'
6
6
  import { Mapper } from '../Mapper'
7
- import { acceptEveryFieldVisitor } from '@contember/schema-utils'
7
+ import { acceptEveryFieldVisitor, SchemaDatabaseMetadata } from '@contember/schema-utils'
8
8
  import {
9
9
  collectResults,
10
10
  MutationNoResultError,
@@ -21,6 +21,7 @@ import { rowDataToFieldValues } from '../ColumnValue'
21
21
  export class Updater {
22
22
  constructor(
23
23
  private readonly schema: Model.Schema,
24
+ private readonly schemaDatabaseMetadata: SchemaDatabaseMetadata,
24
25
  private readonly predicateFactory: PredicateFactory,
25
26
  private readonly updateBuilderFactory: UpdateBuilderFactory,
26
27
  ) {}
@@ -50,7 +51,7 @@ export class Updater {
50
51
  if (otherPromises.length === 0) {
51
52
  return await mutationResultPromise
52
53
  } else {
53
- return await collectResults(this.schema, mutationResultPromise, otherPromises)
54
+ return await collectResults(this.schema, this.schemaDatabaseMetadata, mutationResultPromise, otherPromises)
54
55
  }
55
56
  }
56
57
 
@@ -23,6 +23,7 @@ import { InputPreValidator } from '../input-validation'
23
23
  import { ObjectNode } from '../inputProcessing'
24
24
  import { executeReadOperations } from './ReadHelpers'
25
25
  import { logger } from '@contember/logger'
26
+ import { SchemaDatabaseMetadata } from '@contember/schema-utils'
26
27
 
27
28
  type WithoutNode<T extends { node: any }> = Pick<T, Exclude<keyof T, 'node'>>
28
29
 
@@ -34,6 +35,7 @@ export class MutationResolver {
34
35
  private readonly mapperFactory: MapperFactory,
35
36
  private readonly inputValidator: InputPreValidator,
36
37
  private readonly graphqlQueryAstFactory: GraphQlQueryAstFactory,
38
+ private readonly schemaDatabaseMetadata: SchemaDatabaseMetadata,
37
39
  ) {}
38
40
 
39
41
  public async resolveTransaction(info: GraphQLResolveInfo, options: TransactionOptions): Promise<Result.TransactionResult> {
@@ -196,7 +198,7 @@ export class MutationResolver {
196
198
  }
197
199
  if (options?.deferForeignKeyConstraints) {
198
200
  logger.debug('MutationResolver: validating fk constraints')
199
- const constraintsResult = await tryMutation(this.schema, async () => {
201
+ const constraintsResult = await tryMutation(this.schema, this.schemaDatabaseMetadata, async () => {
200
202
  await mapper.constraintHelper.setFkConstraintsImmediate()
201
203
  return []
202
204
  })
package/src/types.ts CHANGED
@@ -6,5 +6,5 @@ export interface Context {
6
6
  db: Client
7
7
  identityVariables: Acl.VariablesMap
8
8
  executionContainer: ExecutionContainer
9
- timer: <T>(event: string, cb?: () => T) => Promise<T>
9
+ timer: <T>(event: string, cb: () => T) => T
10
10
  }
@@ -1,5 +1,4 @@
1
1
  export * from './accessor'
2
- export * from './arrays'
3
2
  export * from './assertNever'
4
3
  export * from './Interface'
5
4
  export * from './isIt'
@@ -15,5 +15,5 @@ export const getFieldsForUniqueWhere = (schema: Model.Schema, entity: Model.Enti
15
15
  }),
16
16
  ).filter((it): it is [string] => !!it)
17
17
 
18
- return [[entity.primary], ...Object.values(entity.unique).map(it => it.fields), ...relations]
18
+ return [[entity.primary], ...entity.unique.map(it => it.fields), ...relations]
19
19
  }
@@ -0,0 +1,77 @@
1
+ import { AclDefinition as acl, createSchema, SchemaDefinition as def } from '@contember/schema-definition'
2
+ import { test } from 'vitest'
3
+ import { execute } from '../../../../../src/test'
4
+ import { GQL, SQL } from '../../../../../src/tags'
5
+ import { PermissionFactory } from '../../../../../../src'
6
+ import { testUuid } from '@contember/engine-api-tester'
7
+
8
+
9
+ namespace RowPredicateSubset {
10
+ export const ownerRole = acl.createRole('owner')
11
+ export const personIdVariable = acl.createPredefinedVariable('personId', 'personID', ownerRole)
12
+
13
+ @acl.allow(ownerRole, {
14
+ read: true,
15
+ when: { resource: acl.canRead('item') },
16
+ })
17
+ export class Item {
18
+ resource = def.oneHasOne(Resource, 'item')
19
+ }
20
+
21
+ @acl.allow(ownerRole, {
22
+ read: true,
23
+ when: { owner: acl.canRead('resources') },
24
+ })
25
+ export class Resource {
26
+ item = def.oneHasOneInverse(Item, 'resource')
27
+ owner = def.manyHasOne(Person, 'resources')
28
+ }
29
+
30
+ @acl.allow(ownerRole, {
31
+ read: true,
32
+ when: { personId: personIdVariable },
33
+ })
34
+ export class Person {
35
+ personId = def.uuidColumn().notNull()
36
+ resources = def.oneHasMany(Resource, 'owner')
37
+ }
38
+ }
39
+
40
+
41
+ test('owner predicate with isNull', async () => {
42
+ const schema = createSchema(RowPredicateSubset)
43
+
44
+ const permissions = new PermissionFactory().create(schema, ['owner'])
45
+
46
+ await execute({
47
+ schema: schema.model,
48
+ permissions: permissions,
49
+ variables: {
50
+ owner__personId: { eq: testUuid(0) },
51
+ },
52
+ query: GQL`
53
+ query {
54
+ listItem(filter: {resource: {id: {isNull: true}}}) {
55
+ id
56
+ }
57
+ }`,
58
+ executes: [
59
+ {
60
+ sql: SQL`select "root_"."id" as "root_id" from "public"."item" as "root_"
61
+ left join "public"."resource" as "root_resource" on "root_"."resource_id" = "root_resource"."id"
62
+ left join "public"."person" as "root_resource_owner" on "root_resource"."owner_id" = "root_resource_owner"."id"
63
+ where "root_resource"."id" is null and "root_resource_owner"."person_id" = ? and "root_resource_owner"."person_id" = ?`,
64
+ response: {
65
+ rows: [],
66
+ },
67
+ },
68
+ ],
69
+ return: {
70
+ data: {
71
+ listItem: [],
72
+ },
73
+ },
74
+ })
75
+ })
76
+
77
+
@@ -37,7 +37,7 @@ namespace RowPredicateSubset {
37
37
  test('row level predicate includes main predicate', async () => {
38
38
  const schema = createSchema(RowPredicateSubset)
39
39
 
40
- const permissions = new PermissionFactory(schema.model).create(schema.acl, ['reader'])
40
+ const permissions = new PermissionFactory().create(schema, ['reader'])
41
41
 
42
42
  await execute({
43
43
  schema: schema.model,