@subsquid/openreader 2.1.0 → 3.0.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.
- package/lib/ir/args.d.ts +1 -1
- package/lib/ir/args.d.ts.map +1 -1
- package/lib/ir/connection.d.ts +3 -4
- package/lib/ir/connection.d.ts.map +1 -1
- package/lib/ir/connection.js.map +1 -1
- package/lib/ir/fields.d.ts +6 -2
- package/lib/ir/fields.d.ts.map +1 -1
- package/lib/ir/fields.js +15 -0
- package/lib/ir/fields.js.map +1 -1
- package/lib/limit.size.d.ts +4 -4
- package/lib/limit.size.d.ts.map +1 -1
- package/lib/limit.size.js +51 -20
- package/lib/limit.size.js.map +1 -1
- package/lib/main.js +0 -9
- package/lib/main.js.map +1 -1
- package/lib/model.d.ts +2 -1
- package/lib/model.d.ts.map +1 -1
- package/lib/model.schema.d.ts +2 -2
- package/lib/model.schema.d.ts.map +1 -1
- package/lib/model.schema.js +8 -3
- package/lib/model.schema.js.map +1 -1
- package/lib/model.tools.d.ts +6 -1
- package/lib/model.tools.d.ts.map +1 -1
- package/lib/model.tools.js +111 -8
- package/lib/model.tools.js.map +1 -1
- package/lib/opencrud/orderBy.d.ts +2 -2
- package/lib/opencrud/orderBy.d.ts.map +1 -1
- package/lib/opencrud/orderBy.js +13 -17
- package/lib/opencrud/orderBy.js.map +1 -1
- package/lib/opencrud/schema.d.ts +4 -4
- package/lib/opencrud/schema.d.ts.map +1 -1
- package/lib/opencrud/schema.js +53 -62
- package/lib/opencrud/schema.js.map +1 -1
- package/lib/opencrud/tree.d.ts +9 -7
- package/lib/opencrud/tree.d.ts.map +1 -1
- package/lib/opencrud/tree.js +32 -14
- package/lib/opencrud/tree.js.map +1 -1
- package/lib/sql/cursor.js +2 -2
- package/lib/sql/cursor.js.map +1 -1
- package/lib/sql/mapping.d.ts +3 -1
- package/lib/sql/mapping.d.ts.map +1 -1
- package/lib/sql/mapping.js +16 -1
- package/lib/sql/mapping.js.map +1 -1
- package/lib/sql/printer.d.ts +29 -11
- package/lib/sql/printer.d.ts.map +1 -1
- package/lib/sql/printer.js +106 -10
- package/lib/sql/printer.js.map +1 -1
- package/lib/sql/query.d.ts +11 -11
- package/lib/sql/query.d.ts.map +1 -1
- package/lib/sql/query.js +41 -19
- package/lib/sql/query.js.map +1 -1
- package/lib/test/queryable.test.d.ts +2 -0
- package/lib/test/queryable.test.d.ts.map +1 -0
- package/lib/test/queryable.test.js +255 -0
- package/lib/test/queryable.test.js.map +1 -0
- package/package.json +2 -2
- package/src/ir/args.ts +1 -1
- package/src/ir/connection.ts +3 -4
- package/src/ir/fields.ts +22 -2
- package/src/limit.size.ts +55 -30
- package/src/main.ts +0 -11
- package/src/model.schema.ts +16 -9
- package/src/model.tools.ts +121 -8
- package/src/model.ts +2 -1
- package/src/opencrud/orderBy.ts +13 -17
- package/src/opencrud/schema.ts +78 -84
- package/src/opencrud/tree.ts +55 -26
- package/src/sql/cursor.ts +2 -2
- package/src/sql/mapping.ts +18 -1
- package/src/sql/printer.ts +137 -21
- package/src/sql/query.ts +50 -30
- package/src/test/queryable.test.ts +258 -0
package/src/sql/printer.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {unexpectedCase} from
|
|
2
|
-
import assert from
|
|
3
|
-
import {Dialect} from
|
|
4
|
-
import {
|
|
5
|
-
import {FieldRequest} from
|
|
6
|
-
import {Model} from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import {unexpectedCase} from '@subsquid/util-internal'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import {Dialect} from '../dialect'
|
|
4
|
+
import {OrderBy, SortOrder, SqlArguments, Where} from '../ir/args'
|
|
5
|
+
import {FieldRequest, FieldsByEntity} from '../ir/fields'
|
|
6
|
+
import {Model} from '../model'
|
|
7
|
+
import {getQueryableEntities} from '../model.tools'
|
|
8
|
+
import {Cursor, EntityCursor} from './cursor'
|
|
9
|
+
import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from './util'
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
export class
|
|
12
|
+
export class EntitySqlPrinter {
|
|
12
13
|
private aliases: AliasSet
|
|
13
14
|
private join: JoinSet
|
|
14
15
|
private root: EntityCursor
|
|
@@ -19,9 +20,9 @@ export class EntityListQueryPrinter {
|
|
|
19
20
|
constructor(
|
|
20
21
|
private model: Model,
|
|
21
22
|
private dialect: Dialect,
|
|
22
|
-
|
|
23
|
+
public readonly entityName: string,
|
|
23
24
|
private params: unknown[],
|
|
24
|
-
private args:
|
|
25
|
+
private args: SqlArguments = {},
|
|
25
26
|
fields?: FieldRequest[],
|
|
26
27
|
aliases?: AliasSet
|
|
27
28
|
) {
|
|
@@ -43,12 +44,14 @@ export class EntityListQueryPrinter {
|
|
|
43
44
|
this.populateWhere(this.root, args.where, this.where)
|
|
44
45
|
}
|
|
45
46
|
if (args.orderBy) {
|
|
46
|
-
this.
|
|
47
|
+
this.traverseOrderBy(args.orderBy, (field, cursor, order) => {
|
|
48
|
+
this.orderBy.push(cursor.native(field) + ' ' + order)
|
|
49
|
+
})
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
private sub(entityName: string, args?:
|
|
51
|
-
return new
|
|
53
|
+
private sub(entityName: string, args?: SqlArguments, fields?: FieldRequest[]): EntitySqlPrinter {
|
|
54
|
+
return new EntitySqlPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
private populateColumns(cursor: Cursor, fields: FieldRequest[]): void {
|
|
@@ -128,7 +131,9 @@ export class EntityListQueryPrinter {
|
|
|
128
131
|
break
|
|
129
132
|
}
|
|
130
133
|
case "isNull": {
|
|
131
|
-
let f = cursor.
|
|
134
|
+
let f = cursor.prop(where.field).type.kind == 'lookup'
|
|
135
|
+
? cursor.child(where.field).ref('id')
|
|
136
|
+
: cursor.ref(where.field)
|
|
132
137
|
if (where.yes) {
|
|
133
138
|
exps.push(`${f} IS NULL`)
|
|
134
139
|
} else {
|
|
@@ -261,13 +266,17 @@ export class EntityListQueryPrinter {
|
|
|
261
266
|
return printClause("AND", exps)
|
|
262
267
|
}
|
|
263
268
|
|
|
264
|
-
|
|
269
|
+
traverseOrderBy(orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
|
|
270
|
+
this.visitOrderBy(this.root, orderBy, cb)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private visitOrderBy(cursor: Cursor, orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
|
|
265
274
|
for (let field in orderBy) {
|
|
266
275
|
let spec = orderBy[field]
|
|
267
276
|
if (typeof spec == "string") {
|
|
268
|
-
|
|
277
|
+
cb(field, cursor, spec)
|
|
269
278
|
} else {
|
|
270
|
-
this.
|
|
279
|
+
this.visitOrderBy(cursor.child(field), spec, cb)
|
|
271
280
|
}
|
|
272
281
|
}
|
|
273
282
|
}
|
|
@@ -280,14 +289,18 @@ export class EntityListQueryPrinter {
|
|
|
280
289
|
return escapeIdentifier(this.dialect, name)
|
|
281
290
|
}
|
|
282
291
|
|
|
283
|
-
addWhereDerivedFrom(field: string, parentIdExp: string): this {
|
|
292
|
+
private addWhereDerivedFrom(field: string, parentIdExp: string): this {
|
|
284
293
|
this.where.push(`${this.root.native(field)} = ${parentIdExp}`)
|
|
285
294
|
return this
|
|
286
295
|
}
|
|
287
296
|
|
|
297
|
+
hasColumns(): boolean {
|
|
298
|
+
return this.columns.size() > 0
|
|
299
|
+
}
|
|
300
|
+
|
|
288
301
|
printColumnList(options?: {withAliases?: boolean}): string {
|
|
302
|
+
assert(this.hasColumns())
|
|
289
303
|
let names = this.columns.names()
|
|
290
|
-
assert(names.length > 0)
|
|
291
304
|
if (options?.withAliases) {
|
|
292
305
|
names = names.map((name, idx) => `${name} AS _c${idx}`)
|
|
293
306
|
}
|
|
@@ -322,7 +335,110 @@ export class EntityListQueryPrinter {
|
|
|
322
335
|
return `SELECT ${this.printColumnList({withAliases: true})} ${this.printFrom()}`
|
|
323
336
|
}
|
|
324
337
|
|
|
325
|
-
|
|
338
|
+
printAsCount(): string {
|
|
339
|
+
if (this.args.offset || this.args.limit) {
|
|
340
|
+
return `SELECT count(*) FROM (SELECT true ${this.printFrom()}) AS rows`
|
|
341
|
+
} else {
|
|
342
|
+
return `SELECT count(*) ${this.printFrom()}`
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private printAsJsonRows(): string {
|
|
326
347
|
return `SELECT ${this.printColumnListAsJsonArray()} AS row ${this.printFrom()}`
|
|
327
348
|
}
|
|
328
349
|
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
export class QueryableSqlPrinter {
|
|
353
|
+
private printers: EntitySqlPrinter[] = []
|
|
354
|
+
private orders: SortOrder[] = []
|
|
355
|
+
private orderColumns: string[][] = []
|
|
356
|
+
|
|
357
|
+
constructor(
|
|
358
|
+
private model: Model,
|
|
359
|
+
private dialect: Dialect,
|
|
360
|
+
private queryableName: string,
|
|
361
|
+
private params: unknown[],
|
|
362
|
+
private args: SqlArguments = {},
|
|
363
|
+
fields?: FieldsByEntity
|
|
364
|
+
) {
|
|
365
|
+
for (let entityName of getQueryableEntities(this.model, this.queryableName)) {
|
|
366
|
+
let entityFields = fields?.[entityName]
|
|
367
|
+
|
|
368
|
+
let printer = new EntitySqlPrinter(
|
|
369
|
+
model,
|
|
370
|
+
dialect,
|
|
371
|
+
entityName,
|
|
372
|
+
this.params,
|
|
373
|
+
{where: args.where},
|
|
374
|
+
entityFields
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if (this.args.orderBy) {
|
|
378
|
+
let cols: string[] = []
|
|
379
|
+
this.orders.length = 0
|
|
380
|
+
printer.traverseOrderBy(this.args.orderBy, (field, cursor, order) => {
|
|
381
|
+
let col = field == '_type' ? `'${entityName}'` : cursor.native(field)
|
|
382
|
+
this.orders.push(order)
|
|
383
|
+
cols.push(`${col} AS o${this.orders.length}`)
|
|
384
|
+
})
|
|
385
|
+
this.orderColumns.push(cols)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.printers.push(printer)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
print(): string {
|
|
393
|
+
let from = this.printers.map((printer, idx) => {
|
|
394
|
+
let cols: string[] = []
|
|
395
|
+
cols.push(`'${printer.entityName}' AS e`)
|
|
396
|
+
if (printer.hasColumns()) {
|
|
397
|
+
cols.push(printer.printColumnListAsJsonArray() + ' AS d')
|
|
398
|
+
} else {
|
|
399
|
+
cols.push('null AS d')
|
|
400
|
+
}
|
|
401
|
+
cols.push(...this.orderColumns[idx])
|
|
402
|
+
return `SELECT ${cols.join(', ')} ${printer.printFrom()}`
|
|
403
|
+
}).join('\nUNION ALL\n')
|
|
404
|
+
|
|
405
|
+
let args = this.printArgs()
|
|
406
|
+
if (args) {
|
|
407
|
+
return `SELECT e, d FROM (\n${from}\n) AS rows` + args
|
|
408
|
+
} else {
|
|
409
|
+
return from
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
printAsCount(): string {
|
|
414
|
+
let union = this.orders.length
|
|
415
|
+
? this.printers.map((printer, idx) => {
|
|
416
|
+
return `SELECT ${this.orderColumns[idx].join(', ')} ${printer.printFrom()}`
|
|
417
|
+
})
|
|
418
|
+
: this.printers.map(printer => {
|
|
419
|
+
return `SELECT true ${printer.printFrom()}`
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
let from = union.join('\nUNION ALL\n')
|
|
423
|
+
let args = this.printArgs()
|
|
424
|
+
if (args) {
|
|
425
|
+
from = `SELECT true FROM (\n${from}\n) AS src` + args
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return `SELECT count(*) FROM (\n${from}\n) AS rows`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private printArgs(): string {
|
|
432
|
+
let sql = ''
|
|
433
|
+
if (this.orders.length) {
|
|
434
|
+
sql += '\nORDER BY ' + this.orders.map((o, idx) => `o${idx + 1} ${o}`).join(', ')
|
|
435
|
+
}
|
|
436
|
+
if (this.args.offset) {
|
|
437
|
+
sql += `\nOFFSET ${this.args.offset}`
|
|
438
|
+
}
|
|
439
|
+
if (this.args.limit) {
|
|
440
|
+
sql += `\nLIMIT ${this.args.limit}`
|
|
441
|
+
}
|
|
442
|
+
return sql
|
|
443
|
+
}
|
|
444
|
+
}
|
package/src/sql/query.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {assertNotNull} from
|
|
2
|
-
import assert from
|
|
3
|
-
import type {Dialect} from
|
|
4
|
-
import type {
|
|
1
|
+
import {assertNotNull} from '@subsquid/util-internal'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import type {Dialect} from '../dialect'
|
|
4
|
+
import type {SqlArguments, Where} from '../ir/args'
|
|
5
5
|
import {
|
|
6
6
|
decodeRelayConnectionCursor,
|
|
7
7
|
encodeRelayConnectionCursor,
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
RelayConnectionPageInfo,
|
|
10
10
|
RelayConnectionRequest,
|
|
11
11
|
RelayConnectionResponse
|
|
12
|
-
} from
|
|
13
|
-
import type {FieldRequest} from
|
|
14
|
-
import type {Model} from
|
|
15
|
-
import {toSafeInteger} from
|
|
16
|
-
import {mapRows} from
|
|
17
|
-
import {
|
|
12
|
+
} from '../ir/connection'
|
|
13
|
+
import type {AnyFields, FieldRequest} from '../ir/fields'
|
|
14
|
+
import type {Model} from '../model'
|
|
15
|
+
import {toSafeInteger} from '../util/util'
|
|
16
|
+
import {mapQueryableRows, mapRows} from './mapping'
|
|
17
|
+
import {EntitySqlPrinter, QueryableSqlPrinter} from './printer'
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
export interface Query<T> {
|
|
@@ -24,22 +24,32 @@ export interface Query<T> {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
export class
|
|
27
|
+
export class ListQuery implements Query<any[]> {
|
|
28
28
|
public readonly sql: string
|
|
29
29
|
public readonly params: unknown[] = []
|
|
30
30
|
|
|
31
31
|
constructor(
|
|
32
32
|
model: Model,
|
|
33
33
|
dialect: Dialect,
|
|
34
|
-
|
|
35
|
-
private fields:
|
|
36
|
-
args:
|
|
34
|
+
typeName: string,
|
|
35
|
+
private fields: AnyFields,
|
|
36
|
+
args: SqlArguments
|
|
37
37
|
) {
|
|
38
|
-
|
|
38
|
+
if (model[typeName].kind == 'entity') {
|
|
39
|
+
assert(Array.isArray(fields))
|
|
40
|
+
this.sql = new EntitySqlPrinter(model, dialect, typeName, this.params, args, fields).print()
|
|
41
|
+
} else {
|
|
42
|
+
assert(!Array.isArray(fields))
|
|
43
|
+
this.sql = new QueryableSqlPrinter(model, dialect, typeName, this.params, args, fields).print()
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
map(rows: any[][]): any[] {
|
|
42
|
-
|
|
48
|
+
if (Array.isArray(this.fields)) {
|
|
49
|
+
return mapRows(rows, this.fields)
|
|
50
|
+
} else {
|
|
51
|
+
return mapQueryableRows(rows, this.fields)
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -55,7 +65,7 @@ export class EntityByIdQuery {
|
|
|
55
65
|
private fields: FieldRequest[],
|
|
56
66
|
id: string
|
|
57
67
|
) {
|
|
58
|
-
this.sql = new
|
|
68
|
+
this.sql = new EntitySqlPrinter(
|
|
59
69
|
model,
|
|
60
70
|
dialect,
|
|
61
71
|
entityName,
|
|
@@ -72,17 +82,18 @@ export class EntityByIdQuery {
|
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
|
|
75
|
-
export class
|
|
85
|
+
export class CountQuery implements Query<number> {
|
|
76
86
|
public readonly sql: string
|
|
77
87
|
public readonly params: unknown[] = []
|
|
78
88
|
|
|
79
89
|
constructor(
|
|
80
90
|
model: Model,
|
|
81
91
|
dialect: Dialect,
|
|
82
|
-
|
|
92
|
+
typeName: string,
|
|
83
93
|
where?: Where
|
|
84
94
|
) {
|
|
85
|
-
|
|
95
|
+
let Printer = model[typeName].kind == 'entity' ? EntitySqlPrinter : QueryableSqlPrinter
|
|
96
|
+
this.sql = new Printer(model, dialect, typeName, this.params, {where}).printAsCount()
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
map(rows: any[][]): number {
|
|
@@ -91,12 +102,12 @@ export class EntityCountQuery implements Query<number> {
|
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
|
|
94
|
-
export class
|
|
105
|
+
export class ConnectionQuery implements Query<RelayConnectionResponse> {
|
|
95
106
|
public readonly sql: string
|
|
96
107
|
public readonly params: unknown[] = []
|
|
97
108
|
private offset = 0
|
|
98
109
|
private limit = 100
|
|
99
|
-
private edgeNode?:
|
|
110
|
+
private edgeNode?: AnyFields
|
|
100
111
|
private edgeCursor?: boolean
|
|
101
112
|
private pageInfo?: boolean
|
|
102
113
|
private totalCount?: boolean
|
|
@@ -104,30 +115,39 @@ export class EntityConnectionQuery implements Query<RelayConnectionResponse> {
|
|
|
104
115
|
constructor(
|
|
105
116
|
model: Model,
|
|
106
117
|
dialect: Dialect,
|
|
107
|
-
|
|
108
|
-
req: RelayConnectionRequest
|
|
118
|
+
typeName: string,
|
|
119
|
+
req: RelayConnectionRequest<AnyFields>
|
|
109
120
|
) {
|
|
110
121
|
this.setOffsetAndLimit(req)
|
|
111
122
|
this.edgeCursor = req.edgeCursor
|
|
112
123
|
this.pageInfo = req.pageInfo
|
|
113
124
|
this.totalCount = req.totalCount
|
|
114
125
|
|
|
115
|
-
let
|
|
126
|
+
let args = {
|
|
116
127
|
orderBy: req.orderBy,
|
|
117
128
|
where: req.where,
|
|
118
129
|
offset: this.offset,
|
|
119
130
|
limit: this.limit + 1
|
|
120
|
-
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let printer
|
|
134
|
+
if (model[typeName].kind == 'entity') {
|
|
135
|
+
assert(req.edgeNode == null || Array.isArray(req.edgeNode))
|
|
136
|
+
printer = new EntitySqlPrinter(model, dialect, typeName, this.params, args, req.edgeNode)
|
|
137
|
+
} else {
|
|
138
|
+
assert(req.edgeNode == null || !Array.isArray(req.edgeNode))
|
|
139
|
+
printer = new QueryableSqlPrinter(model, dialect, typeName, this.params, args, req.edgeNode)
|
|
140
|
+
}
|
|
121
141
|
|
|
122
|
-
if (req.edgeNode
|
|
142
|
+
if (req.edgeNode) {
|
|
123
143
|
this.edgeNode = req.edgeNode
|
|
124
144
|
this.sql = printer.print()
|
|
125
145
|
} else {
|
|
126
|
-
this.sql =
|
|
146
|
+
this.sql = printer.printAsCount()
|
|
127
147
|
}
|
|
128
148
|
}
|
|
129
149
|
|
|
130
|
-
private setOffsetAndLimit(req: RelayConnectionRequest): void {
|
|
150
|
+
private setOffsetAndLimit(req: RelayConnectionRequest<unknown>): void {
|
|
131
151
|
if (req.after != null) {
|
|
132
152
|
this.offset = assertNotNull(decodeRelayConnectionCursor(req.after))
|
|
133
153
|
}
|
|
@@ -140,7 +160,7 @@ export class EntityConnectionQuery implements Query<RelayConnectionResponse> {
|
|
|
140
160
|
map(rows: any[][]): RelayConnectionResponse {
|
|
141
161
|
let res: RelayConnectionResponse = {}
|
|
142
162
|
if (this.edgeNode) {
|
|
143
|
-
let nodes = mapRows(rows, this.edgeNode)
|
|
163
|
+
let nodes = Array.isArray(this.edgeNode) ? mapRows(rows, this.edgeNode) : mapQueryableRows(rows, this.edgeNode)
|
|
144
164
|
let edges: RelayConnectionEdge[] = new Array(Math.min(this.limit, nodes.length))
|
|
145
165
|
for (let i = 0; i < edges.length; i++) {
|
|
146
166
|
edges[i] = {
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {useDatabase, useServer} from './setup'
|
|
2
|
+
|
|
3
|
+
describe('queryable interfaces', function() {
|
|
4
|
+
useDatabase([
|
|
5
|
+
`create table foo (id text primary key, name text, foo int)`,
|
|
6
|
+
`create table bar (id text primary key, name text, bar int)`,
|
|
7
|
+
`create table ref (id text primary key, name text, foo_id text not null unique references foo, bar_id text not null unique references bar)`,
|
|
8
|
+
`create table baz (id text primary key, name text, ref_id text references ref, baz int)`,
|
|
9
|
+
`insert into foo (id, name, foo) values ('foo-1', 'hello-foo-1', 1)`,
|
|
10
|
+
`insert into foo (id, name, foo) values ('foo-2', 'hello-foo-2', 2)`,
|
|
11
|
+
`insert into bar (id, name, bar) values ('bar-1', 'hello-bar-1', 10)`,
|
|
12
|
+
`insert into bar (id, name, bar) values ('bar-2', 'hello-bar-2', 20)`,
|
|
13
|
+
`insert into ref (id, name, foo_id, bar_id) values ('1', 'ref-1', 'foo-1', 'bar-2')`,
|
|
14
|
+
`insert into ref (id, name, foo_id, bar_id) values ('2', 'ref-2', 'foo-2', 'bar-1')`,
|
|
15
|
+
`insert into baz (id, name, baz, ref_id) values ('baz-1', 'hello-baz-1', 100, '1')`,
|
|
16
|
+
`create table one (id text primary key)`,
|
|
17
|
+
`create table two (id text primary key)`,
|
|
18
|
+
`create table relation (id text primary key, one_id text references one, two_id text references two)`,
|
|
19
|
+
`insert into one (id) values ('1-1')`,
|
|
20
|
+
`insert into one (id) values ('1-2')`,
|
|
21
|
+
`insert into two (id) values ('2-1')`,
|
|
22
|
+
`insert into two (id) values ('2-2')`,
|
|
23
|
+
`insert into relation (id, one_id, two_id) values ('r-1', '1-1', '2-1')`,
|
|
24
|
+
`insert into relation (id, one_id, two_id) values ('r-2', '1-2', '2-1')`,
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const client = useServer(`
|
|
28
|
+
interface Entity @query {
|
|
29
|
+
id: ID!
|
|
30
|
+
name: String
|
|
31
|
+
ref: Ref
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Ref @entity {
|
|
35
|
+
id: ID!
|
|
36
|
+
name: String
|
|
37
|
+
foo: Foo! @unique
|
|
38
|
+
bar: Bar! @unique
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Foo implements Entity @entity {
|
|
42
|
+
id: ID!
|
|
43
|
+
name: String
|
|
44
|
+
ref: Ref @derivedFrom(field: "foo")
|
|
45
|
+
foo: Int
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Bar implements Entity @entity {
|
|
49
|
+
id: ID!
|
|
50
|
+
name: String
|
|
51
|
+
ref: Ref @derivedFrom(field: "bar")
|
|
52
|
+
bar: Int
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type Baz implements Entity @entity {
|
|
56
|
+
id: ID!
|
|
57
|
+
name: String
|
|
58
|
+
ref: Ref
|
|
59
|
+
baz: Int
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface Number @query {
|
|
63
|
+
id: ID!
|
|
64
|
+
relations: [Relation!]!
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type One implements Number @entity {
|
|
68
|
+
id: ID!
|
|
69
|
+
relations: [Relation!]! @derivedFrom(field: "one")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type Two implements Number @entity {
|
|
73
|
+
id: ID!
|
|
74
|
+
relations: [Relation!]! @derivedFrom(field: "two")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type Relation @entity {
|
|
78
|
+
id: ID!
|
|
79
|
+
one: One
|
|
80
|
+
two: Two
|
|
81
|
+
}
|
|
82
|
+
`)
|
|
83
|
+
|
|
84
|
+
it('fetching', function() {
|
|
85
|
+
return client.test(`
|
|
86
|
+
query {
|
|
87
|
+
entities(orderBy: id_ASC) {
|
|
88
|
+
id
|
|
89
|
+
name
|
|
90
|
+
ref {
|
|
91
|
+
id
|
|
92
|
+
name
|
|
93
|
+
}
|
|
94
|
+
... on Foo { foo }
|
|
95
|
+
... on Bar { bar }
|
|
96
|
+
... on Baz { baz }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
`, {
|
|
100
|
+
entities: [
|
|
101
|
+
{
|
|
102
|
+
id: 'bar-1',
|
|
103
|
+
name: 'hello-bar-1',
|
|
104
|
+
ref: {
|
|
105
|
+
id: '2',
|
|
106
|
+
name: 'ref-2'
|
|
107
|
+
},
|
|
108
|
+
bar: 10
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'bar-2',
|
|
112
|
+
name: 'hello-bar-2',
|
|
113
|
+
ref: {
|
|
114
|
+
id: '1',
|
|
115
|
+
name: 'ref-1'
|
|
116
|
+
},
|
|
117
|
+
bar: 20
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'baz-1',
|
|
121
|
+
name: 'hello-baz-1',
|
|
122
|
+
ref: {
|
|
123
|
+
id: '1',
|
|
124
|
+
name: 'ref-1'
|
|
125
|
+
},
|
|
126
|
+
baz: 100
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'foo-1',
|
|
130
|
+
name: 'hello-foo-1',
|
|
131
|
+
ref: {
|
|
132
|
+
id: '1',
|
|
133
|
+
name: 'ref-1'
|
|
134
|
+
},
|
|
135
|
+
foo: 1
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'foo-2',
|
|
139
|
+
name: 'hello-foo-2',
|
|
140
|
+
ref: {
|
|
141
|
+
id: '2',
|
|
142
|
+
name: 'ref-2'
|
|
143
|
+
},
|
|
144
|
+
foo: 2
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('sorting by entity type', function() {
|
|
151
|
+
return client.test(`
|
|
152
|
+
query {
|
|
153
|
+
entities(orderBy: [_type_DESC, id_ASC]) {
|
|
154
|
+
id
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
`, {
|
|
158
|
+
entities: [
|
|
159
|
+
{id: 'foo-1'},
|
|
160
|
+
{id: 'foo-2'},
|
|
161
|
+
{id: 'baz-1'},
|
|
162
|
+
{id: 'bar-1'},
|
|
163
|
+
{id: 'bar-2'}
|
|
164
|
+
]
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('pagination', function() {
|
|
169
|
+
return client.test(`
|
|
170
|
+
query {
|
|
171
|
+
page1: entitiesConnection(orderBy: id_ASC, first: 2) {
|
|
172
|
+
...fields
|
|
173
|
+
}
|
|
174
|
+
page2: entitiesConnection(orderBy: id_ASC, first: 2, after: "2") {
|
|
175
|
+
...fields
|
|
176
|
+
}
|
|
177
|
+
page3: entitiesConnection(orderBy: id_ASC, first: 2, after: "4") {
|
|
178
|
+
...fields
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fragment fields on EntitiesConnection {
|
|
183
|
+
edges {
|
|
184
|
+
cursor
|
|
185
|
+
node {
|
|
186
|
+
... on Foo { foo }
|
|
187
|
+
... on Bar { bar }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
pageInfo {
|
|
191
|
+
hasNextPage
|
|
192
|
+
hasPreviousPage
|
|
193
|
+
startCursor
|
|
194
|
+
endCursor
|
|
195
|
+
}
|
|
196
|
+
totalCount
|
|
197
|
+
}
|
|
198
|
+
`, {
|
|
199
|
+
page1: {
|
|
200
|
+
edges: [
|
|
201
|
+
{cursor: '1', node: {bar: 10}},
|
|
202
|
+
{cursor: '2', node: {bar: 20}},
|
|
203
|
+
],
|
|
204
|
+
pageInfo: {
|
|
205
|
+
hasNextPage: true,
|
|
206
|
+
hasPreviousPage: false,
|
|
207
|
+
startCursor: '1',
|
|
208
|
+
endCursor: '2'
|
|
209
|
+
},
|
|
210
|
+
totalCount: 5
|
|
211
|
+
},
|
|
212
|
+
page2: {
|
|
213
|
+
edges: [
|
|
214
|
+
{cursor: '3', node: {}},
|
|
215
|
+
{cursor: '4', node: {foo: 1}},
|
|
216
|
+
],
|
|
217
|
+
pageInfo: {
|
|
218
|
+
hasNextPage: true,
|
|
219
|
+
hasPreviousPage: true,
|
|
220
|
+
startCursor: '3',
|
|
221
|
+
endCursor: '4'
|
|
222
|
+
},
|
|
223
|
+
totalCount: 5
|
|
224
|
+
},
|
|
225
|
+
page3: {
|
|
226
|
+
edges: [
|
|
227
|
+
{cursor: '5', node: {foo: 2}},
|
|
228
|
+
],
|
|
229
|
+
pageInfo: {
|
|
230
|
+
hasNextPage: false,
|
|
231
|
+
hasPreviousPage: true,
|
|
232
|
+
startCursor: '5',
|
|
233
|
+
endCursor: '5'
|
|
234
|
+
},
|
|
235
|
+
totalCount: 5
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('list lookup fields in interfaces', function() {
|
|
241
|
+
return client.test(`
|
|
242
|
+
query {
|
|
243
|
+
numbers(orderBy: id_ASC) {
|
|
244
|
+
id
|
|
245
|
+
relations { id }
|
|
246
|
+
__typename
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
`, {
|
|
250
|
+
numbers: [
|
|
251
|
+
{__typename: 'One', id: '1-1', relations: [{id: 'r-1'}]},
|
|
252
|
+
{__typename: 'One', id: '1-2', relations: [{id: 'r-2'}]},
|
|
253
|
+
{__typename: 'Two', id: '2-1', relations: [{id: 'r-1'}, {id: 'r-2'}]},
|
|
254
|
+
{__typename: 'Two', id: '2-2', relations: []}
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
})
|