@contember/engine-content-api 1.1.0-alpha.7 → 1.1.0-beta.1
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/Authorizator.d.ts +2 -1
- package/dist/src/acl/Authorizator.d.ts.map +1 -1
- package/dist/src/acl/Authorizator.js +4 -3
- package/dist/src/acl/Authorizator.js.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.d.ts.map +1 -1
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.js +2 -0
- package/dist/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.js.map +1 -1
- package/dist/src/mapper/Mapper.d.ts.map +1 -1
- package/dist/src/mapper/Mapper.js +10 -12
- package/dist/src/mapper/Mapper.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 +5 -5
- package/dist/src/mapper/select/ConditionBuilder.js.map +1 -1
- package/dist/src/mapper/select/JoinBuilder.js +2 -2
- package/dist/src/mapper/select/JoinBuilder.js.map +1 -1
- package/dist/src/mapper/select/JoinVisitor.js +4 -4
- package/dist/src/mapper/select/JoinVisitor.js.map +1 -1
- package/dist/src/mapper/select/OrderByBuilder.js +2 -2
- package/dist/src/mapper/select/OrderByBuilder.js.map +1 -1
- package/dist/src/mapper/select/Path.d.ts +2 -1
- package/dist/src/mapper/select/Path.d.ts.map +1 -1
- package/dist/src/mapper/select/Path.js +3 -2
- package/dist/src/mapper/select/Path.js.map +1 -1
- package/dist/src/mapper/select/RelationFetcher.js +2 -2
- package/dist/src/mapper/select/RelationFetcher.js.map +1 -1
- package/dist/src/mapper/select/SelectBuilder.d.ts +1 -1
- package/dist/src/mapper/select/SelectBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectBuilder.js +7 -10
- package/dist/src/mapper/select/SelectBuilder.js.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.d.ts +2 -1
- package/dist/src/mapper/select/SelectHydrator.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.js +34 -11
- package/dist/src/mapper/select/SelectHydrator.js.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/WhereBuilder.js +5 -4
- package/dist/src/mapper/select/WhereBuilder.js.map +1 -1
- package/dist/src/mapper/select/handlers/FieldsVisitor.js +2 -2
- package/dist/src/mapper/select/handlers/FieldsVisitor.js.map +1 -1
- package/dist/src/mapper/select/handlers/MetaHandler.js +1 -1
- package/dist/src/mapper/select/handlers/MetaHandler.js.map +1 -1
- package/dist/src/schema/CustomTypesProvider.d.ts.map +1 -1
- package/dist/src/schema/CustomTypesProvider.js +18 -3
- package/dist/src/schema/CustomTypesProvider.js.map +1 -1
- package/dist/src/schema/OrderByTypeProvider.d.ts.map +1 -1
- package/dist/src/schema/OrderByTypeProvider.js +2 -0
- package/dist/src/schema/OrderByTypeProvider.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/graphQlDb/update/invalidInput.test.js +0 -23
- package/dist/tests/cases/integration/graphQlDb/update/invalidInput.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.js +87 -0
- package/dist/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.d.ts +2 -0
- package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.js +35 -0
- package/dist/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.js.map +1 -0
- package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js +1 -1
- package/dist/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.js.map +1 -1
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-allowed.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-predicate.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-predicate2.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-create.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-delete.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl-restricted-update.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-acl.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-aliased-type.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-basic.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-bug-66.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-custom-primary.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-has-many-reduction.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-new-builder.gql +2 -0
- package/dist/tests/cases/integration/graphQlSchema/schema-view-entity.gql +2 -0
- package/dist/tests/cases/unit/uuidType.test.d.ts +2 -0
- package/dist/tests/cases/unit/uuidType.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/uuidType.test.js +23 -0
- package/dist/tests/cases/unit/uuidType.test.js.map +1 -0
- package/dist/tests/src/test.js +1 -1
- package/dist/tests/src/test.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/acl/Authorizator.ts +5 -2
- package/src/extensions/paginatedHasMany/PaginatedHasManyFieldProviderVisitor.ts +2 -0
- package/src/mapper/Mapper.ts +10 -13
- package/src/mapper/select/ConditionBuilder.ts +5 -4
- package/src/mapper/select/JoinBuilder.ts +2 -2
- package/src/mapper/select/JoinVisitor.ts +4 -4
- package/src/mapper/select/OrderByBuilder.ts +2 -2
- package/src/mapper/select/Path.ts +4 -2
- package/src/mapper/select/RelationFetcher.ts +2 -2
- package/src/mapper/select/SelectBuilder.ts +7 -11
- package/src/mapper/select/SelectHydrator.ts +52 -19
- package/src/mapper/select/WhereBuilder.ts +6 -3
- package/src/mapper/select/handlers/FieldsVisitor.ts +2 -2
- package/src/mapper/select/handlers/MetaHandler.ts +1 -1
- package/src/schema/CustomTypesProvider.ts +21 -4
- package/src/schema/OrderByTypeProvider.ts +2 -0
- package/tests/cases/integration/graphQlDb/update/invalidInput.test.ts +0 -26
- package/tests/cases/integration/graphQlRequests/query/relations/oneHasMany.test.ts +87 -0
- package/tests/cases/integration/graphQlRequests/query/sorting/sortNulls.test.ts +34 -0
- package/tests/cases/integration/graphQlSchema/graphQlSchemaBuilder.test.ts +1 -1
- package/tests/cases/integration/graphQlSchema/schema-acl-allowed.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl-predicate.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl-predicate2.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl-restricted-create.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl-restricted-delete.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl-restricted-update.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-acl.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-aliased-type.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-basic.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-bug-66.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-custom-primary.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-has-many-reduction.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-new-builder.gql +2 -0
- package/tests/cases/integration/graphQlSchema/schema-view-entity.gql +2 -0
- package/tests/cases/unit/uuidType.test.ts +25 -0
- 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-
|
|
3
|
+
"version": "1.1.0-beta.1",
|
|
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-
|
|
14
|
-
"@contember/dic": "^1.1.0-
|
|
15
|
-
"@contember/engine-common": "^1.1.0-
|
|
16
|
-
"@contember/graphql-utils": "^1.1.0-
|
|
17
|
-
"@contember/schema": "^1.1.0-
|
|
18
|
-
"@contember/schema-utils": "^1.1.0-
|
|
13
|
+
"@contember/database": "^1.1.0-beta.1",
|
|
14
|
+
"@contember/dic": "^1.1.0-beta.1",
|
|
15
|
+
"@contember/engine-common": "^1.1.0-beta.1",
|
|
16
|
+
"@contember/graphql-utils": "^1.1.0-beta.1",
|
|
17
|
+
"@contember/schema": "^1.1.0-beta.1",
|
|
18
|
+
"@contember/schema-utils": "^1.1.0-beta.1",
|
|
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-
|
|
24
|
-
"@contember/engine-api-tester": "^1.1.0-
|
|
25
|
-
"@contember/schema-definition": "^1.1.0-
|
|
23
|
+
"@contember/database-tester": "^1.1.0-beta.1",
|
|
24
|
+
"@contember/engine-api-tester": "^1.1.0-beta.1",
|
|
25
|
+
"@contember/schema-definition": "^1.1.0-beta.1",
|
|
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",
|
package/src/acl/Authorizator.ts
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
}
|
package/src/mapper/Mapper.ts
CHANGED
|
@@ -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).
|
|
76
|
-
qb = qb.select([path.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
124
|
-
|
|
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.
|
|
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.
|
|
145
|
+
.from(entity.tableName, path.alias)
|
|
149
146
|
.select(expr => expr.raw('count(*)'), 'row_count')
|
|
150
|
-
.select([path.
|
|
151
|
-
.groupBy([path.
|
|
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.
|
|
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().
|
|
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().
|
|
72
|
-
const targetAlias = this.path.
|
|
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().
|
|
100
|
-
const targetAlias = this.path.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
212
|
+
qb = qb.join(targetEntity.tableName, path.alias, condition =>
|
|
213
213
|
condition.compareColumns(['junction_', column.targetColumn.columnName], Operator.eq, [
|
|
214
|
-
path.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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().
|
|
140
|
+
this.qb = this.qb.select([columnPath.back().alias, columnName], columnPath.alias)
|
|
143
141
|
const rows = await this.rows
|
|
144
|
-
const columnAlias = columnPath.
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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] =
|
|
60
|
+
result[row[indexBy] as Value.PrimaryValue] = this.hydrateRow(row, resolved)
|
|
51
61
|
}
|
|
52
62
|
return result
|
|
53
63
|
}
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
87
|
+
const parentValue = row[parentKeyPath.alias] as Value.PrimaryValue
|
|
76
88
|
if (currentObject) {
|
|
77
|
-
currentObject[last] = (parentValue ?
|
|
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.
|
|
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().
|
|
23
|
-
const columnAlias = columnPath.
|
|
22
|
+
const tableAlias = columnPath.back().alias
|
|
23
|
+
const columnAlias = columnPath.alias
|
|
24
24
|
|
|
25
25
|
const fieldPredicate = this.predicateFactory.shouldApplyCellLevelPredicate(
|
|
26
26
|
entity,
|
|
@@ -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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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),
|
|
@@ -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
|
-
|