@contember/engine-content-api 1.1.0-alpha.6 → 1.1.0-beta.0

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 (123) hide show
  1. package/dist/src/acl/Authorizator.d.ts +2 -1
  2. package/dist/src/acl/Authorizator.d.ts.map +1 -1
  3. package/dist/src/acl/Authorizator.js +4 -3
  4. package/dist/src/acl/Authorizator.js.map +1 -1
  5. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.d.ts.map +1 -1
  6. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.js +2 -0
  7. package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.js.map +1 -1
  8. package/dist/src/mapper/Mapper.d.ts.map +1 -1
  9. package/dist/src/mapper/Mapper.js +10 -12
  10. package/dist/src/mapper/Mapper.js.map +1 -1
  11. package/dist/src/mapper/select/ConditionBuilder.d.ts +1 -1
  12. package/dist/src/mapper/select/ConditionBuilder.d.ts.map +1 -1
  13. package/dist/src/mapper/select/ConditionBuilder.js +5 -5
  14. package/dist/src/mapper/select/ConditionBuilder.js.map +1 -1
  15. package/dist/src/mapper/select/JoinBuilder.js +2 -2
  16. package/dist/src/mapper/select/JoinBuilder.js.map +1 -1
  17. package/dist/src/mapper/select/JoinVisitor.js +4 -4
  18. package/dist/src/mapper/select/JoinVisitor.js.map +1 -1
  19. package/dist/src/mapper/select/OrderByBuilder.js +2 -2
  20. package/dist/src/mapper/select/OrderByBuilder.js.map +1 -1
  21. package/dist/src/mapper/select/Path.d.ts +2 -1
  22. package/dist/src/mapper/select/Path.d.ts.map +1 -1
  23. package/dist/src/mapper/select/Path.js +3 -2
  24. package/dist/src/mapper/select/Path.js.map +1 -1
  25. package/dist/src/mapper/select/RelationFetcher.js +2 -2
  26. package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
  27. package/dist/src/mapper/select/SelectBuilder.d.ts +1 -1
  28. package/dist/src/mapper/select/SelectBuilder.d.ts.map +1 -1
  29. package/dist/src/mapper/select/SelectBuilder.js +7 -10
  30. package/dist/src/mapper/select/SelectBuilder.js.map +1 -1
  31. package/dist/src/mapper/select/SelectHydrator.d.ts +2 -1
  32. package/dist/src/mapper/select/SelectHydrator.d.ts.map +1 -1
  33. package/dist/src/mapper/select/SelectHydrator.js +34 -11
  34. package/dist/src/mapper/select/SelectHydrator.js.map +1 -1
  35. package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
  36. package/dist/src/mapper/select/WhereBuilder.js +5 -4
  37. package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
  38. package/dist/src/mapper/select/handlers/FieldsVisitor.js +2 -2
  39. package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
  40. package/dist/src/mapper/select/handlers/MetaHandler.js +1 -1
  41. package/dist/src/mapper/select/handlers/MetaHandler.js.map +1 -1
  42. package/dist/src/resolvers/MutationResolver.d.ts.map +1 -1
  43. package/dist/src/resolvers/MutationResolver.js +4 -3
  44. package/dist/src/resolvers/MutationResolver.js.map +1 -1
  45. package/dist/src/schema/CustomTypesProvider.d.ts.map +1 -1
  46. package/dist/src/schema/CustomTypesProvider.js +18 -3
  47. package/dist/src/schema/CustomTypesProvider.js.map +1 -1
  48. package/dist/src/schema/OrderByTypeProvider.d.ts.map +1 -1
  49. package/dist/src/schema/OrderByTypeProvider.js +2 -0
  50. package/dist/src/schema/OrderByTypeProvider.js.map +1 -1
  51. package/dist/src/tsconfig.tsbuildinfo +1 -1
  52. package/dist/tests/cases/integration/graphQlDb/update/invalidInput.test.js +0 -23
  53. package/dist/tests/cases/integration/graphQlDb/update/invalidInput.test.js.map +1 -1
  54. package/dist/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.js +87 -0
  55. package/dist/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.js.map +1 -1
  56. package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.d.ts +2 -0
  57. package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.d.ts.map +1 -0
  58. package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.js +35 -0
  59. package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.js.map +1 -0
  60. package/dist/tests/cases/integration/graphQlRequests/upsert/basicUpsert.test.js +45 -2
  61. package/dist/tests/cases/integration/graphQlRequests/upsert/basicUpsert.test.js.map +1 -1
  62. package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js +1 -1
  63. package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js.map +1 -1
  64. package/dist/tests/cases/integration/graphQlSchema/schema-acl-allowed.gql +2 -0
  65. package/dist/tests/cases/integration/graphQlSchema/schema-acl-predicate.gql +2 -0
  66. package/dist/tests/cases/integration/graphQlSchema/schema-acl-predicate2.gql +2 -0
  67. package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-create.gql +2 -0
  68. package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-delete.gql +2 -0
  69. package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-update.gql +2 -0
  70. package/dist/tests/cases/integration/graphQlSchema/schema-acl.gql +2 -0
  71. package/dist/tests/cases/integration/graphQlSchema/schema-aliased-type.gql +2 -0
  72. package/dist/tests/cases/integration/graphQlSchema/schema-basic.gql +2 -0
  73. package/dist/tests/cases/integration/graphQlSchema/schema-bug-66.gql +2 -0
  74. package/dist/tests/cases/integration/graphQlSchema/schema-custom-primary.gql +2 -0
  75. package/dist/tests/cases/integration/graphQlSchema/schema-has-many-reduction.gql +2 -0
  76. package/dist/tests/cases/integration/graphQlSchema/schema-new-builder.gql +2 -0
  77. package/dist/tests/cases/integration/graphQlSchema/schema-view-entity.gql +2 -0
  78. package/dist/tests/cases/unit/uuidType.test.d.ts +2 -0
  79. package/dist/tests/cases/unit/uuidType.test.d.ts.map +1 -0
  80. package/dist/tests/cases/unit/uuidType.test.js +23 -0
  81. package/dist/tests/cases/unit/uuidType.test.js.map +1 -0
  82. package/dist/tests/src/test.js +1 -1
  83. package/dist/tests/src/test.js.map +1 -1
  84. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +10 -10
  86. package/src/acl/Authorizator.ts +5 -2
  87. package/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.ts +2 -0
  88. package/src/mapper/Mapper.ts +10 -13
  89. package/src/mapper/select/ConditionBuilder.ts +5 -4
  90. package/src/mapper/select/JoinBuilder.ts +2 -2
  91. package/src/mapper/select/JoinVisitor.ts +4 -4
  92. package/src/mapper/select/OrderByBuilder.ts +2 -2
  93. package/src/mapper/select/Path.ts +4 -2
  94. package/src/mapper/select/RelationFetcher.ts +2 -2
  95. package/src/mapper/select/SelectBuilder.ts +7 -11
  96. package/src/mapper/select/SelectHydrator.ts +52 -19
  97. package/src/mapper/select/WhereBuilder.ts +6 -3
  98. package/src/mapper/select/handlers/FieldsVisitor.ts +2 -2
  99. package/src/mapper/select/handlers/MetaHandler.ts +1 -1
  100. package/src/resolvers/MutationResolver.ts +4 -3
  101. package/src/schema/CustomTypesProvider.ts +21 -4
  102. package/src/schema/OrderByTypeProvider.ts +2 -0
  103. package/tests/cases/integration/graphQlDb/update/invalidInput.test.ts +0 -26
  104. package/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.ts +87 -0
  105. package/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.ts +34 -0
  106. package/tests/cases/integration/graphQlRequests/upsert/basicUpsert.test.ts +49 -2
  107. package/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.ts +1 -1
  108. package/tests/cases/integration/graphQlSchema/schema-acl-allowed.gql +2 -0
  109. package/tests/cases/integration/graphQlSchema/schema-acl-predicate.gql +2 -0
  110. package/tests/cases/integration/graphQlSchema/schema-acl-predicate2.gql +2 -0
  111. package/tests/cases/integration/graphQlSchema/schema-acl-restricted-create.gql +2 -0
  112. package/tests/cases/integration/graphQlSchema/schema-acl-restricted-delete.gql +2 -0
  113. package/tests/cases/integration/graphQlSchema/schema-acl-restricted-update.gql +2 -0
  114. package/tests/cases/integration/graphQlSchema/schema-acl.gql +2 -0
  115. package/tests/cases/integration/graphQlSchema/schema-aliased-type.gql +2 -0
  116. package/tests/cases/integration/graphQlSchema/schema-basic.gql +2 -0
  117. package/tests/cases/integration/graphQlSchema/schema-bug-66.gql +2 -0
  118. package/tests/cases/integration/graphQlSchema/schema-custom-primary.gql +2 -0
  119. package/tests/cases/integration/graphQlSchema/schema-has-many-reduction.gql +2 -0
  120. package/tests/cases/integration/graphQlSchema/schema-new-builder.gql +2 -0
  121. package/tests/cases/integration/graphQlSchema/schema-view-entity.gql +2 -0
  122. package/tests/cases/unit/uuidType.test.ts +25 -0
  123. package/tests/src/test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-content-api",
3
- "version": "1.1.0-alpha.6",
3
+ "version": "1.1.0-beta.0",
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.1.0-alpha.6",
14
- "@contember/dic": "^1.1.0-alpha.6",
15
- "@contember/engine-common": "^1.1.0-alpha.6",
16
- "@contember/graphql-utils": "^1.1.0-alpha.6",
17
- "@contember/schema": "^1.1.0-alpha.6",
18
- "@contember/schema-utils": "^1.1.0-alpha.6",
13
+ "@contember/database": "^1.1.0-beta.0",
14
+ "@contember/dic": "^1.1.0-beta.0",
15
+ "@contember/engine-common": "^1.1.0-beta.0",
16
+ "@contember/graphql-utils": "^1.1.0-beta.0",
17
+ "@contember/schema": "^1.1.0-beta.0",
18
+ "@contember/schema-utils": "^1.1.0-beta.0",
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.1.0-alpha.6",
24
- "@contember/engine-api-tester": "^1.1.0-alpha.6",
25
- "@contember/schema-definition": "^1.1.0-alpha.6",
23
+ "@contember/database-tester": "^1.1.0-beta.0",
24
+ "@contember/engine-api-tester": "^1.1.0-beta.0",
25
+ "@contember/schema-definition": "^1.1.0-beta.0",
26
26
  "@graphql-codegen/cli": "^1.15.2",
27
27
  "@graphql-codegen/typescript": "^1.15.2",
28
28
  "@graphql-codegen/typescript-operations": "^1.15.2",
@@ -4,7 +4,10 @@ import { Acl } from '@contember/schema'
4
4
  export type AuthorizationResult = 'yes' | 'no' | 'maybe'
5
5
 
6
6
  export class Authorizator {
7
- constructor(private readonly permissions: Acl.Permissions) {
7
+ constructor(
8
+ private readonly permissions: Acl.Permissions,
9
+ private readonly defaultCustomPrimary: boolean,
10
+ ) {
8
11
  }
9
12
 
10
13
  getEntityPermission(operation: Acl.Operation, entity: string): AuthorizationResult {
@@ -47,6 +50,6 @@ export class Authorizator {
47
50
 
48
51
 
49
52
  isCustomPrimaryAllowed(entity: string): boolean {
50
- return this.permissions?.[entity]?.operations?.customPrimary || false
53
+ return this.permissions?.[entity]?.operations?.customPrimary ?? this.defaultCustomPrimary
51
54
  }
52
55
  }
@@ -3,6 +3,7 @@ import { FieldMap } from '../EntityFieldsProvider'
3
3
  import { PaginatedHasManyFieldProviderExtension } from './PaginatedHasManyFieldProvider'
4
4
  import { capitalizeFirstLetter } from '../../utils'
5
5
  import { PaginatedFieldConfigFactory } from '../../schema/PaginatedFieldConfigFactory'
6
+ import { aliasAwareResolver } from '../../schema'
6
7
 
7
8
  export class PaginatedHasManyFieldProviderVisitor implements
8
9
  Model.ColumnVisitor<FieldMap<PaginatedHasManyFieldProviderExtension>>,
@@ -47,6 +48,7 @@ export class PaginatedHasManyFieldProviderVisitor implements
47
48
  extensions: {
48
49
  relationName: relation.name,
49
50
  },
51
+ resolve: aliasAwareResolver,
50
52
  },
51
53
  }
52
54
  }
@@ -72,8 +72,8 @@ export class Mapper {
72
72
  let indexByAlias: string | null = null
73
73
  if (indexBy) {
74
74
  const path = this.pathFactory.create([])
75
- indexByAlias = path.for(indexBy).getAlias()
76
- qb = qb.select([path.getAlias(), getColumnName(this.schema, entity, indexBy)], indexByAlias)
75
+ indexByAlias = path.for(indexBy).alias
76
+ qb = qb.select([path.alias, getColumnName(this.schema, entity, indexBy)], indexByAlias)
77
77
  }
78
78
  const rows = await this.selectRows(hydrator, qb, entity, input)
79
79
 
@@ -100,7 +100,7 @@ export class Mapper {
100
100
  let qb: SelectBuilder<SelectBuilder.Result> = SelectBuilder.create()
101
101
  const path = this.pathFactory.create([])
102
102
  const groupingKey = '__grouping_key'
103
- qb = qb.select([path.getAlias(), relation.joiningColumn.columnName], groupingKey)
103
+ qb = qb.select([path.alias, relation.joiningColumn.columnName], groupingKey)
104
104
 
105
105
  const rows = await this.selectRows(hydrator, qb, entity, input, relation.name)
106
106
  return await hydrator.hydrateGroups(rows, groupingKey)
@@ -115,22 +115,19 @@ export class Mapper {
115
115
  ) {
116
116
  const inputWithOrder = OrderByHelper.appendDefaultOrderBy(entity, input, [])
117
117
  const path = this.pathFactory.create([])
118
- const augmentedBuilder = qb.from(entity.tableName, path.getAlias()).meta('path', [...input.path, input.alias])
118
+ const augmentedBuilder = qb.from(entity.tableName, path.alias).meta('path', [...input.path, input.alias])
119
119
 
120
120
  const selector = this.selectBuilderFactory.create(augmentedBuilder, hydrator)
121
121
  const filterWithPredicates = this.predicatesInjector.inject(entity, inputWithOrder.args.filter || {})
122
122
  const inputWithPredicates = inputWithOrder.withArg('filter', filterWithPredicates)
123
- const selectPromise = selector.select(this, entity, inputWithPredicates, path, groupBy)
124
- const rows = await selector.execute(this.db)
125
- await selectPromise
126
-
127
- return rows
123
+ selector.select(this, entity, inputWithPredicates, path, groupBy)
124
+ return await selector.execute(this.db)
128
125
  }
129
126
 
130
127
  public async count(entity: Model.Entity, filter: Input.OptionalWhere) {
131
128
  const path = this.pathFactory.create([])
132
129
  const qb = SelectBuilder.create()
133
- .from(entity.tableName, path.getAlias())
130
+ .from(entity.tableName, path.alias)
134
131
  .select(expr => expr.raw('count(*)'), 'row_count')
135
132
  const withPredicates = this.predicatesInjector.inject(entity, filter)
136
133
  const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates)
@@ -145,10 +142,10 @@ export class Mapper {
145
142
  ): Promise<Record<string, number>> {
146
143
  const path = this.pathFactory.create([])
147
144
  const qb = SelectBuilder.create()
148
- .from(entity.tableName, path.getAlias())
145
+ .from(entity.tableName, path.alias)
149
146
  .select(expr => expr.raw('count(*)'), 'row_count')
150
- .select([path.getAlias(), relation.joiningColumn.columnName])
151
- .groupBy([path.getAlias(), relation.joiningColumn.columnName])
147
+ .select([path.alias, relation.joiningColumn.columnName])
148
+ .groupBy([path.alias, relation.joiningColumn.columnName])
152
149
  const withPredicates = this.predicatesInjector.inject(entity, filter)
153
150
  const qbWithWhere = this.whereBuilder.build(qb, entity, path, withPredicates)
154
151
  const rows = await qbWithWhere.getResult(this.db)
@@ -7,6 +7,7 @@ export class ConditionBuilder {
7
7
  builder: SqlConditionBuilder,
8
8
  tableName: string,
9
9
  columnName: string,
10
+ columnType: string,
10
11
  condition: Input.Condition<any>,
11
12
  ): SqlConditionBuilder {
12
13
  const keys = Object.keys(condition) as (keyof Required<Input.Condition<any>>)[]
@@ -27,13 +28,13 @@ export class ConditionBuilder {
27
28
  param: Exclude<Input.Condition<any>[K], undefined>,
28
29
  ) => SqlConditionBuilder
29
30
  } = {
30
- and: (builder, expressions) => builder.and(builder2 => expressions.reduce((builder3, expr) => this.build(builder3, tableName, columnName, expr), builder2)),
31
- or: (builder, expressions) => builder.or(builder2 => expressions.reduce((builder3, expr) => this.build(builder3, tableName, columnName, expr), builder2)),
32
- not: (builder, expression) => builder.not(builder2 => this.build(builder2, tableName, columnName, expression)),
31
+ and: (builder, expressions) => builder.and(builder2 => expressions.reduce((builder3, expr) => this.build(builder3, tableName, columnName, columnType, expr), builder2)),
32
+ or: (builder, expressions) => builder.or(builder2 => expressions.reduce((builder3, expr) => this.build(builder3, tableName, columnName, columnType, expr), builder2)),
33
+ not: (builder, expression) => builder.not(builder2 => this.build(builder2, tableName, columnName, columnType, expression)),
33
34
  eq: (builder, value) => builder.compare(columnIdentifier, Operator.eq, value),
34
35
  notEq: (builder, value) => builder.compare(columnIdentifier, Operator.notEq, value),
35
36
  isNull: (builder, value) => value ? builder.isNull(columnIdentifier) : builder.not(clause => clause.isNull(columnIdentifier)),
36
- in: (builder, values) => builder.in(columnIdentifier, values),
37
+ in: (builder, values) => builder.in(columnIdentifier, values, columnType),
37
38
  notIn: (builder, values) => builder.not(builder2 => builder2.in(columnIdentifier, values)),
38
39
  lt: (builder, value) => builder.compare(columnIdentifier, Operator.lt, value),
39
40
  lte: (builder, value) => builder.compare(columnIdentifier, Operator.lte, value),
@@ -22,11 +22,11 @@ export class JoinBuilder {
22
22
  const joins = acceptRelationTypeVisitor(this.schema, entity, relationName, new JoinVisitor(path))
23
23
 
24
24
  return joins.reduce<SelectBuilder<SelectBuilder.Result>>((qb, join) => {
25
- const targetAlias = join.targetAlias || path.getAlias()
25
+ const targetAlias = join.targetAlias || path.alias
26
26
  if (qb.options.join.find(it => it.alias === targetAlias)) {
27
27
  return qb
28
28
  }
29
- const sourceAlias = join.sourceAlias || path.back().getAlias()
29
+ const sourceAlias = join.sourceAlias || path.back().alias
30
30
 
31
31
  return qb.leftJoin(join.tableName, targetAlias, clause =>
32
32
  clause.compareColumns([sourceAlias, join.sourceColumn], Operator.eq, [targetAlias, join.targetColumn]),
@@ -68,8 +68,8 @@ export class JoinVisitor implements Model.RelationByTypeVisitor<JoinDefinition[]
68
68
  relation: Model.ManyHasManyOwningRelation,
69
69
  targetEntity: Model.Entity,
70
70
  ): JoinDefinition[] {
71
- const sourceAlias = this.path.back().getAlias()
72
- const targetAlias = this.path.getAlias()
71
+ const sourceAlias = this.path.back().alias
72
+ const targetAlias = this.path.alias
73
73
  const joiningAlias = `${sourceAlias}_x_${targetAlias}`
74
74
  return [
75
75
  {
@@ -96,8 +96,8 @@ export class JoinVisitor implements Model.RelationByTypeVisitor<JoinDefinition[]
96
96
  targetEntity: Model.Entity,
97
97
  targetRelation: Model.ManyHasManyOwningRelation,
98
98
  ): JoinDefinition[] {
99
- const sourceAlias = this.path.back().getAlias()
100
- const targetAlias = this.path.getAlias()
99
+ const sourceAlias = this.path.back().alias
100
+ const targetAlias = this.path.alias
101
101
  const joiningAlias = `${sourceAlias}_x_${targetAlias}`
102
102
  return [
103
103
  {
@@ -30,7 +30,7 @@ export class OrderByBuilder {
30
30
  ): [SelectBuilder<SelectBuilder.Result>, Orderable] {
31
31
  const entries = Object.entries(orderBy)
32
32
  if (entries.length !== 1) {
33
- const fields = entries.join(', ')
33
+ const fields = entries.map(it => it[0]).join(', ')
34
34
  throw new UserError('Order by: only one field is expected in each item of order by clause, got: ' + fields)
35
35
  }
36
36
  if (orderBy._random || orderBy._randomSeeded) {
@@ -56,7 +56,7 @@ export class OrderByBuilder {
56
56
  if (typeof value === 'string') {
57
57
  const columnName = getColumnName(this.schema, entity, fieldName)
58
58
  const applyOrder = <Orderable extends QueryBuilder.Orderable<any>>(orderable: Orderable) =>
59
- orderable.orderBy([path.getAlias(), columnName], value as Input.OrderDirection)
59
+ orderable.orderBy([path.alias, columnName], value)
60
60
 
61
61
  qb = applyOrder(qb)
62
62
  if (orderable !== null) {
@@ -25,6 +25,8 @@ export class PathFactory {
25
25
  }
26
26
 
27
27
  export class Path {
28
+ public readonly alias = this.createAlias()
29
+
28
30
  constructor(
29
31
  public readonly path: string[],
30
32
  private readonly aliasContext: AliasContext,
@@ -41,7 +43,7 @@ export class Path {
41
43
  return new Path([...this.path, path], this.aliasContext, this.rootAlias)
42
44
  }
43
45
 
44
- public getAlias(): string {
46
+ private createAlias(): string {
45
47
  const alias = this.rootAlias + this.path.join('_')
46
48
 
47
49
  // intentionally not allowing == MAX_IDENTIFIER_LENGTH
@@ -49,6 +51,6 @@ export class Path {
49
51
  return alias
50
52
  }
51
53
  const index = this.aliasContext.getAliasIndex(alias).toString()
52
- return alias.substr(0, MAX_IDENTIFIER_LENGTH - index.length - 1) + '_' + index
54
+ return alias.substring(0, MAX_IDENTIFIER_LENGTH - index.length - 1) + '_' + index
53
55
  }
54
56
  }
@@ -209,9 +209,9 @@ export class RelationFetcher {
209
209
 
210
210
  if (hasWhere || hasFieldOrderBy) {
211
211
  const path = this.pathFactory.create([])
212
- qb = qb.join(targetEntity.tableName, path.getAlias(), condition =>
212
+ qb = qb.join(targetEntity.tableName, path.alias, condition =>
213
213
  condition.compareColumns(['junction_', column.targetColumn.columnName], Operator.eq, [
214
- path.getAlias(),
214
+ path.alias,
215
215
  targetEntity.primaryColumn,
216
216
  ]),
217
217
  )
@@ -43,14 +43,14 @@ export class SelectBuilder {
43
43
  return result
44
44
  }
45
45
 
46
- public async select(
46
+ public select(
47
47
  mapper: Mapper,
48
48
  entity: Model.Entity,
49
49
  input: ObjectNode<Input.ListQueryInput>,
50
50
  path: Path,
51
51
  groupBy?: string,
52
52
  ) {
53
- const promise = this.selectInternal(mapper, entity, path, input)
53
+ this.selectInternal(mapper, entity, path, input)
54
54
  const where = input.args.filter
55
55
  if (where) {
56
56
  this.qb = this.whereBuilder.build(this.qb, entity, path, where)
@@ -60,7 +60,7 @@ export class SelectBuilder {
60
60
  if (groupBy) {
61
61
  const groupByColumn = getColumnName(this.schema, entity, groupBy)
62
62
  this.queryWrapper = new LimitByGroupWrapper(
63
- [path.getAlias(), groupByColumn],
63
+ [path.alias, groupByColumn],
64
64
  (orderable, qb) => {
65
65
  if (orderBy.length > 0) {
66
66
  [qb, orderable] = this.orderByBuilder.build(qb, orderable, entity, this.pathFactory.create([]), orderBy)
@@ -76,11 +76,9 @@ export class SelectBuilder {
76
76
  }
77
77
  this.qb = this.qb.limit(input.args.limit, input.args.offset)
78
78
  }
79
-
80
- await promise
81
79
  }
82
80
 
83
- private async selectInternal(mapper: Mapper, entity: Model.Entity, path: Path, input: ObjectNode) {
81
+ private selectInternal(mapper: Mapper, entity: Model.Entity, path: Path, input: ObjectNode) {
84
82
  if (!input.fields.find(it => it.name === entity.primary && it.alias === entity.primary)) {
85
83
  input = input.withField(new FieldNode(entity.primary, entity.primary, {}))
86
84
  }
@@ -139,11 +137,9 @@ export class SelectBuilder {
139
137
  }
140
138
 
141
139
  private async getColumnValues(columnPath: Path, columnName: string): Promise<Input.PrimaryValue[]> {
142
- this.qb = this.qb.select([columnPath.back().getAlias(), columnName], columnPath.getAlias())
140
+ this.qb = this.qb.select([columnPath.back().alias, columnName], columnPath.alias)
143
141
  const rows = await this.rows
144
- const columnAlias = columnPath.getAlias()
145
- return rows
146
- .map((it): Input.PrimaryValue => it[columnAlias] as Input.PrimaryValue)
147
- .filter((val, index, all) => all.indexOf(val) === index)
142
+ const columnAlias = columnPath.alias
143
+ return Array.from(new Set(rows.map((it): Input.PrimaryValue => it[columnAlias] as Input.PrimaryValue)))
148
144
  }
149
145
  }
@@ -1,14 +1,24 @@
1
1
  import { Path } from './Path'
2
2
  import { Value } from '@contember/schema'
3
+ import { getFulfilledValues, getRejections } from '../../utils'
4
+
5
+ type DataPromises = {
6
+ path: Path
7
+ parentKeyPath: Path
8
+ data: Promise<SelectNestedData>
9
+ defaultValue: SelectNestedDefaultValue
10
+ }
11
+
12
+ type ResolvedData = {
13
+ path: Path
14
+ parentKeyPath: Path
15
+ data: SelectNestedData
16
+ defaultValue: SelectNestedDefaultValue
17
+ }
3
18
 
4
19
  export class SelectHydrator {
5
20
  private columns: Path[] = []
6
- private promises: {
7
- path: Path
8
- parentKeyPath: Path
9
- data: PromiseLike<SelectNestedData>
10
- defaultValue: SelectNestedDefaultValue
11
- }[] = []
21
+ private promises: DataPromises[] = []
12
22
 
13
23
  public addColumn(path: Path) {
14
24
  this.columns.push(path)
@@ -17,21 +27,21 @@ export class SelectHydrator {
17
27
  public addPromise(
18
28
  path: Path,
19
29
  parentKeyPath: Path,
20
- data: PromiseLike<SelectNestedData>,
30
+ data: Promise<SelectNestedData>,
21
31
  defaultValue: SelectNestedDefaultValue,
22
32
  ) {
23
33
  this.promises.push({ path, parentKeyPath, data, defaultValue })
24
34
  }
25
35
 
26
36
  public async hydrateGroups(rows: SelectRow[], groupBy: string): Promise<SelectGroupedObjects> {
27
- await Promise.all(this.promises.map(it => it.data))
37
+ const resolved = await this.resolveDataPromises()
28
38
  const result: SelectGroupedObjects = {}
29
39
  for (let row of rows) {
30
40
  const key = row[groupBy] as Value.PrimaryValue
31
41
  if (!result[key]) {
32
42
  result[key] = []
33
43
  }
34
- result[key].push(await this.hydrateRow(row))
44
+ result[key].push(this.hydrateRow(row, resolved))
35
45
  }
36
46
  return result
37
47
  }
@@ -43,19 +53,22 @@ export class SelectHydrator {
43
53
  rows: SelectRow[],
44
54
  indexBy?: string,
45
55
  ): Promise<SelectResultObject[] | SelectIndexedResultObjects> {
46
- await Promise.all(this.promises.map(it => it.data))
56
+ const resolved = await this.resolveDataPromises()
47
57
  if (indexBy) {
48
58
  const result: SelectIndexedResultObjects = {}
49
59
  for (let row of rows) {
50
- result[row[indexBy] as Value.PrimaryValue] = await this.hydrateRow(row)
60
+ result[row[indexBy] as Value.PrimaryValue] = this.hydrateRow(row, resolved)
51
61
  }
52
62
  return result
53
63
  }
54
-
55
- return Promise.all(rows.map(row => this.hydrateRow(row)))
64
+ const result: SelectResultObject[] = []
65
+ for (const row of rows) {
66
+ result.push(this.hydrateRow(row, resolved))
67
+ }
68
+ return result
56
69
  }
57
70
 
58
- private async hydrateRow(row: SelectRow): Promise<SelectResultObject> {
71
+ private hydrateRow(row: SelectRow, resolvedData: ResolvedData[]): SelectResultObject {
59
72
  const result: SelectResultObject = {}
60
73
 
61
74
  for (let columnPath of this.columns) {
@@ -63,24 +76,44 @@ export class SelectHydrator {
63
76
  const last: string = path.pop() as string
64
77
  const currentObject = path.reduce<any>((obj, part) => (obj[part] = obj[part] || {}), result)
65
78
 
66
- const field = row[columnPath.getAlias()]
79
+ const field = row[columnPath.alias]
67
80
  currentObject[last] = this.formatValue(field)
68
81
  }
69
82
 
70
- for (let { path, parentKeyPath, data, defaultValue } of this.promises) {
71
- const awaitedData = await data
83
+ for (let { path, parentKeyPath, data, defaultValue } of resolvedData) {
72
84
  const pathTmp = [...path.path]
73
85
  const last = pathTmp.pop() as string
74
86
  const currentObject = pathTmp.reduce<any>((obj, part) => (obj && obj[part]) || undefined, result)
75
- const parentValue = row[parentKeyPath.getAlias()] as Value.PrimaryValue
87
+ const parentValue = row[parentKeyPath.alias] as Value.PrimaryValue
76
88
  if (currentObject) {
77
- currentObject[last] = (parentValue ? awaitedData[parentValue] : undefined) || defaultValue
89
+ currentObject[last] = (parentValue ? data[parentValue] : undefined) || defaultValue
78
90
  }
79
91
  }
80
92
 
81
93
  return result
82
94
  }
83
95
 
96
+ private async resolveDataPromises(): Promise<ResolvedData[]> {
97
+ const results = await Promise.allSettled(this.promises.map(async (it): Promise<ResolvedData> => ({
98
+ defaultValue: it.defaultValue,
99
+ parentKeyPath: it.parentKeyPath,
100
+ path: it.path,
101
+ data: await it.data,
102
+ })))
103
+ const failures = getRejections(results)
104
+ if (failures.length > 0) {
105
+ if (failures.length > 1) {
106
+ // eslint-disable-next-line no-console
107
+ console.error('Multiple error has occurred, printing them & rethrowing the first one')
108
+ // eslint-disable-next-line no-console
109
+ failures.slice(1).map(e => console.error(e))
110
+ }
111
+ throw failures[0]
112
+ }
113
+
114
+ return getFulfilledValues(results)
115
+ }
116
+
84
117
  private formatValue(value: any) {
85
118
  if (value instanceof Date) {
86
119
  return value.toISOString()
@@ -48,7 +48,7 @@ export class WhereBuilder {
48
48
  allowManyJoin: boolean,
49
49
  joinList: WhereJoinDefinition[],
50
50
  ): SqlConditionBuilder {
51
- const tableName = path.getAlias()
51
+ const tableName = path.alias
52
52
 
53
53
  if (where.and !== undefined && where.and.length > 0) {
54
54
  const expr = where.and
@@ -101,6 +101,7 @@ export class WhereBuilder {
101
101
  conditionBuilder,
102
102
  tableName,
103
103
  relation.joiningColumn.columnName,
104
+ (targetEntity.fields[targetEntity.primary] as Model.AnyColumn).columnType,
104
105
  primaryCondition,
105
106
  )
106
107
  }
@@ -114,7 +115,7 @@ export class WhereBuilder {
114
115
  conditionBuilder = acceptFieldVisitor<SqlConditionBuilder>(this.schema, entity, fieldName, {
115
116
  visitColumn: (entity, column) => {
116
117
  const subWhere: Input.Condition<Input.ColumnValue> = where[column.name] as Input.Condition<Input.ColumnValue>
117
- return this.conditionBuilder.build(conditionBuilder, tableName, column.columnName, subWhere)
118
+ return this.conditionBuilder.build(conditionBuilder, tableName, column.columnName, column.columnType, subWhere)
118
119
  },
119
120
  visitOneHasOneInverse: joinedWhere,
120
121
  visitOneHasOneOwning: joinedWhere,
@@ -194,8 +195,10 @@ export class WhereBuilder {
194
195
  const primaryCondition = this.transformWhereToPrimaryCondition(relationWhere, targetEntity.primary)
195
196
  if (primaryCondition !== null) {
196
197
 
198
+ const columnType = (targetEntity.fields[targetEntity.primary] as Model.AnyColumn).columnType
199
+
197
200
  return qb.where(condition =>
198
- this.conditionBuilder.build(condition, 'junction_', toColumn, primaryCondition),
201
+ this.conditionBuilder.build(condition, 'junction_', toColumn, columnType, primaryCondition),
199
202
  )
200
203
  }
201
204
 
@@ -19,8 +19,8 @@ export class FieldsVisitor implements Model.RelationByTypeVisitor<void>, Model.C
19
19
  visitColumn(entity: Model.Entity, column: Model.AnyColumn): void {
20
20
  const columnPath = this.executionContext.path
21
21
  this.executionContext.addColumn(qb => {
22
- const tableAlias = columnPath.back().getAlias()
23
- const columnAlias = columnPath.getAlias()
22
+ const tableAlias = columnPath.back().alias
23
+ const columnAlias = columnPath.alias
24
24
 
25
25
  const fieldPredicate = this.predicateFactory.shouldApplyCellLevelPredicate(
26
26
  entity,
@@ -48,7 +48,7 @@ export class MetaHandler implements SelectExecutionHandler<{}> {
48
48
  }
49
49
  return condition
50
50
  }),
51
- metaPath.getAlias(),
51
+ metaPath.alias,
52
52
  ),
53
53
  )
54
54
  }, metaPath)
@@ -388,12 +388,13 @@ export class MutationResolver {
388
388
  if (errorResponse) {
389
389
  return errorResponse
390
390
  }
391
- const primary = getInsertPrimary(result) || getUpdatePrimary(result)
392
- if (!primary) {
391
+ const primary = getInsertPrimary(result)
392
+ const byResolved = primary ? { [entity.primary]: primary } : input.by
393
+ if (!byResolved) {
393
394
  throw new ImplementationException('MutationResolver::resolveUpsertInternal does not handle result properly')
394
395
  }
395
396
 
396
- const nodes = await this.resolveResultNodes(mapper, entity, { [entity.primary]: primary }, queryAst)
397
+ const nodes = await this.resolveResultNodes(mapper, entity, byResolved, queryAst)
397
398
  return {
398
399
  ok: true,
399
400
  validation: {
@@ -1,19 +1,36 @@
1
1
  import { GraphQLScalarType, Kind } from 'graphql'
2
2
  import { JSONType } from '@contember/graphql-utils'
3
- import { singletonFactory } from '../utils'
4
3
 
5
- //todo: implement serialize, parseValue and parseLiteral properly
4
+
5
+ const uuidPattern = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[1-5][0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$/i
6
+ /** dashes are optional */
7
+ const isUuid = (value: string): boolean => {
8
+ return (
9
+ value.length <= 36 &&
10
+ value.length >= 32 &&
11
+ value.match(uuidPattern) !== null
12
+ )
13
+ }
6
14
 
7
15
  export class CustomTypesProvider {
8
16
  public readonly uuidType: GraphQLScalarType = new GraphQLScalarType({
9
17
  name: 'UUID',
10
18
  serialize: str => String(str),
11
- parseValue: str => String(str),
19
+ parseValue: str => {
20
+ if (typeof str !== 'string' || !isUuid(str)) {
21
+ return undefined
22
+ }
23
+ return str
24
+ },
12
25
  parseLiteral: function parseLiteral(ast) {
13
- return ast.kind === Kind.STRING ? ast.value : undefined
26
+ if (ast.kind !== Kind.STRING || !isUuid(ast.value)) {
27
+ return undefined
28
+ }
29
+ return ast.value
14
30
  },
15
31
  })
16
32
 
33
+ //todo: implement serialize, parseValue and parseLiteral properly
17
34
  public readonly dateType: GraphQLScalarType = new GraphQLScalarType({
18
35
  name: 'Date',
19
36
  serialize: str => String(str),
@@ -15,6 +15,8 @@ export class OrderByTypeProvider {
15
15
  values: {
16
16
  asc: { value: 'asc' },
17
17
  desc: { value: 'desc' },
18
+ ascNullsFirst: { value: 'asc nulls first' },
19
+ descNullsLast: { value: 'desc nulls last' },
18
20
  },
19
21
  })
20
22
 
@@ -41,29 +41,3 @@ test('returns error for invalid date input', async () => {
41
41
  expectDatabase: {},
42
42
  })
43
43
  })
44
-
45
- test('returns error for invalid uuid', async () => {
46
- await executeDbTest({
47
- schema,
48
- seed: [],
49
- query: GQL`mutation {
50
- updatePost(by: { id: "abc" }, data: { createdAt: "2020-13-01 20:00" }) {
51
- ok
52
- errors {
53
- type
54
- message
55
- }
56
- }
57
- }`,
58
- return: response => {
59
- assert.ok(response.updatePost)
60
- assert.notOk(response.updatePost.ok)
61
- assert.lengthOf(response.updatePost.errors, 1)
62
- assert.equal(response.updatePost.errors[0].type, 'InvalidDataInput')
63
- assert.match(response.updatePost.errors[0].message, /invalid input syntax for (type )?uuid: "abc"/)
64
- },
65
- expectDatabase: {},
66
- })
67
- })
68
-
69
-