@subsquid/openreader 2.1.0 → 3.1.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 (96) hide show
  1. package/lib/ir/args.d.ts +1 -1
  2. package/lib/ir/args.d.ts.map +1 -1
  3. package/lib/ir/connection.d.ts +3 -4
  4. package/lib/ir/connection.d.ts.map +1 -1
  5. package/lib/ir/connection.js.map +1 -1
  6. package/lib/ir/fields.d.ts +6 -2
  7. package/lib/ir/fields.d.ts.map +1 -1
  8. package/lib/ir/fields.js +15 -0
  9. package/lib/ir/fields.js.map +1 -1
  10. package/lib/limit.size.d.ts +4 -4
  11. package/lib/limit.size.d.ts.map +1 -1
  12. package/lib/limit.size.js +51 -20
  13. package/lib/limit.size.js.map +1 -1
  14. package/lib/main.js +0 -9
  15. package/lib/main.js.map +1 -1
  16. package/lib/model.d.ts +3 -2
  17. package/lib/model.d.ts.map +1 -1
  18. package/lib/model.schema.d.ts +2 -2
  19. package/lib/model.schema.d.ts.map +1 -1
  20. package/lib/model.schema.js +8 -3
  21. package/lib/model.schema.js.map +1 -1
  22. package/lib/model.tools.d.ts +6 -1
  23. package/lib/model.tools.d.ts.map +1 -1
  24. package/lib/model.tools.js +111 -8
  25. package/lib/model.tools.js.map +1 -1
  26. package/lib/opencrud/orderBy.d.ts +2 -2
  27. package/lib/opencrud/orderBy.d.ts.map +1 -1
  28. package/lib/opencrud/orderBy.js +13 -17
  29. package/lib/opencrud/orderBy.js.map +1 -1
  30. package/lib/opencrud/schema.d.ts +4 -4
  31. package/lib/opencrud/schema.d.ts.map +1 -1
  32. package/lib/opencrud/schema.js +56 -62
  33. package/lib/opencrud/schema.js.map +1 -1
  34. package/lib/opencrud/tree.d.ts +9 -7
  35. package/lib/opencrud/tree.d.ts.map +1 -1
  36. package/lib/opencrud/tree.js +32 -14
  37. package/lib/opencrud/tree.js.map +1 -1
  38. package/lib/scalars/BigDecimal.d.ts +3 -0
  39. package/lib/scalars/BigDecimal.d.ts.map +1 -0
  40. package/lib/scalars/BigDecimal.js +38 -0
  41. package/lib/scalars/BigDecimal.js.map +1 -0
  42. package/lib/scalars/index.d.ts +1 -0
  43. package/lib/scalars/index.d.ts.map +1 -1
  44. package/lib/scalars/index.js +2 -0
  45. package/lib/scalars/index.js.map +1 -1
  46. package/lib/server.d.ts +4 -2
  47. package/lib/server.d.ts.map +1 -1
  48. package/lib/server.js +4 -2
  49. package/lib/server.js.map +1 -1
  50. package/lib/sql/cursor.d.ts.map +1 -1
  51. package/lib/sql/cursor.js +4 -2
  52. package/lib/sql/cursor.js.map +1 -1
  53. package/lib/sql/mapping.d.ts +3 -1
  54. package/lib/sql/mapping.d.ts.map +1 -1
  55. package/lib/sql/mapping.js +16 -1
  56. package/lib/sql/mapping.js.map +1 -1
  57. package/lib/sql/printer.d.ts +29 -11
  58. package/lib/sql/printer.d.ts.map +1 -1
  59. package/lib/sql/printer.js +112 -11
  60. package/lib/sql/printer.js.map +1 -1
  61. package/lib/sql/query.d.ts +11 -11
  62. package/lib/sql/query.d.ts.map +1 -1
  63. package/lib/sql/query.js +41 -19
  64. package/lib/sql/query.js.map +1 -1
  65. package/lib/test/queryable.test.d.ts +2 -0
  66. package/lib/test/queryable.test.d.ts.map +1 -0
  67. package/lib/test/queryable.test.js +255 -0
  68. package/lib/test/queryable.test.js.map +1 -0
  69. package/lib/test/scalars.test.js +75 -1
  70. package/lib/test/scalars.test.js.map +1 -1
  71. package/lib/util/big-decimal.d.ts +5 -0
  72. package/lib/util/big-decimal.d.ts.map +1 -0
  73. package/lib/util/big-decimal.js +15 -0
  74. package/lib/util/big-decimal.js.map +1 -0
  75. package/package.json +10 -2
  76. package/src/ir/args.ts +1 -1
  77. package/src/ir/connection.ts +3 -4
  78. package/src/ir/fields.ts +22 -2
  79. package/src/limit.size.ts +55 -30
  80. package/src/main.ts +0 -11
  81. package/src/model.schema.ts +16 -9
  82. package/src/model.tools.ts +121 -8
  83. package/src/model.ts +3 -2
  84. package/src/opencrud/orderBy.ts +13 -17
  85. package/src/opencrud/schema.ts +81 -84
  86. package/src/opencrud/tree.ts +55 -26
  87. package/src/scalars/BigDecimal.ts +37 -0
  88. package/src/scalars/index.ts +2 -0
  89. package/src/server.ts +10 -7
  90. package/src/sql/cursor.ts +4 -2
  91. package/src/sql/mapping.ts +18 -1
  92. package/src/sql/printer.ts +142 -22
  93. package/src/sql/query.ts +50 -30
  94. package/src/test/queryable.test.ts +258 -0
  95. package/src/test/scalars.test.ts +78 -1
  96. package/src/util/big-decimal.ts +15 -0
@@ -1,5 +1,5 @@
1
1
  import {unexpectedCase} from "@subsquid/util-internal"
2
- import {FieldRequest} from "../ir/fields"
2
+ import {FieldRequest, FieldsByEntity} from '../ir/fields'
3
3
 
4
4
 
5
5
  export function mapRows(rows: any[][], fields: FieldRequest[]): any[] {
@@ -64,3 +64,20 @@ export function mapRow(row: any[], fields: FieldRequest[], ifType?: string): any
64
64
  }
65
65
  return rec
66
66
  }
67
+
68
+
69
+ export function mapQueryableRows(rows: any[][], fields: FieldsByEntity): any[] {
70
+ let result = new Array(rows.length)
71
+ for (let i = 0; i < rows.length; i++) {
72
+ result[i] = mapQueryableRow(rows[i], fields)
73
+ }
74
+ return result
75
+ }
76
+
77
+
78
+ export function mapQueryableRow(row: any[], fields: FieldsByEntity): any {
79
+ let entity = row[0]
80
+ let rec = mapRow(row[1], fields[entity])
81
+ rec._isTypeOf = entity
82
+ return rec
83
+ }
@@ -1,14 +1,15 @@
1
- import {unexpectedCase} from "@subsquid/util-internal"
2
- import assert from "assert"
3
- import {Dialect} from "../dialect"
4
- import {EntityListArguments, OrderBy, Where} from "../ir/args"
5
- import {FieldRequest} from "../ir/fields"
6
- import {Model} from "../model"
7
- import {Cursor, EntityCursor} from "./cursor"
8
- import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from "./util"
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 EntityListQueryPrinter {
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
- private entityName: string,
23
+ public readonly entityName: string,
23
24
  private params: unknown[],
24
- private args: EntityListArguments = {},
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.populateOrderBy(this.root, args.orderBy)
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?: EntityListArguments, fields?: FieldRequest[]): EntityListQueryPrinter {
51
- return new EntityListQueryPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
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.ref(where.field)
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,33 +266,45 @@ export class EntityListQueryPrinter {
261
266
  return printClause("AND", exps)
262
267
  }
263
268
 
264
- private populateOrderBy(cursor: Cursor, orderBy: OrderBy): void {
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
- this.orderBy.push(`${cursor.native(field)} ${spec}`)
277
+ cb(field, cursor, spec)
269
278
  } else {
270
- this.populateOrderBy(cursor.child(field), spec)
279
+ this.visitOrderBy(cursor.child(field), spec, cb)
271
280
  }
272
281
  }
273
282
  }
274
283
 
275
284
  private param(value: any): string {
276
- return "$" + this.params.push(value)
285
+ if (value && value.__is_squid_big_decimal) {
286
+ return "$" + this.params.push(value.toString()) + '::numeric'
287
+ } else {
288
+ return "$" + this.params.push(value)
289
+ }
277
290
  }
278
291
 
279
292
  private ident(name: string): string {
280
293
  return escapeIdentifier(this.dialect, name)
281
294
  }
282
295
 
283
- addWhereDerivedFrom(field: string, parentIdExp: string): this {
296
+ private addWhereDerivedFrom(field: string, parentIdExp: string): this {
284
297
  this.where.push(`${this.root.native(field)} = ${parentIdExp}`)
285
298
  return this
286
299
  }
287
300
 
301
+ hasColumns(): boolean {
302
+ return this.columns.size() > 0
303
+ }
304
+
288
305
  printColumnList(options?: {withAliases?: boolean}): string {
306
+ assert(this.hasColumns())
289
307
  let names = this.columns.names()
290
- assert(names.length > 0)
291
308
  if (options?.withAliases) {
292
309
  names = names.map((name, idx) => `${name} AS _c${idx}`)
293
310
  }
@@ -322,7 +339,110 @@ export class EntityListQueryPrinter {
322
339
  return `SELECT ${this.printColumnList({withAliases: true})} ${this.printFrom()}`
323
340
  }
324
341
 
325
- printAsJsonRows(): string {
342
+ printAsCount(): string {
343
+ if (this.args.offset || this.args.limit) {
344
+ return `SELECT count(*) FROM (SELECT true ${this.printFrom()}) AS rows`
345
+ } else {
346
+ return `SELECT count(*) ${this.printFrom()}`
347
+ }
348
+ }
349
+
350
+ private printAsJsonRows(): string {
326
351
  return `SELECT ${this.printColumnListAsJsonArray()} AS row ${this.printFrom()}`
327
352
  }
328
353
  }
354
+
355
+
356
+ export class QueryableSqlPrinter {
357
+ private printers: EntitySqlPrinter[] = []
358
+ private orders: SortOrder[] = []
359
+ private orderColumns: string[][] = []
360
+
361
+ constructor(
362
+ private model: Model,
363
+ private dialect: Dialect,
364
+ private queryableName: string,
365
+ private params: unknown[],
366
+ private args: SqlArguments = {},
367
+ fields?: FieldsByEntity
368
+ ) {
369
+ for (let entityName of getQueryableEntities(this.model, this.queryableName)) {
370
+ let entityFields = fields?.[entityName]
371
+
372
+ let printer = new EntitySqlPrinter(
373
+ model,
374
+ dialect,
375
+ entityName,
376
+ this.params,
377
+ {where: args.where},
378
+ entityFields
379
+ )
380
+
381
+ if (this.args.orderBy) {
382
+ let cols: string[] = []
383
+ this.orders.length = 0
384
+ printer.traverseOrderBy(this.args.orderBy, (field, cursor, order) => {
385
+ let col = field == '_type' ? `'${entityName}'` : cursor.native(field)
386
+ this.orders.push(order)
387
+ cols.push(`${col} AS o${this.orders.length}`)
388
+ })
389
+ this.orderColumns.push(cols)
390
+ }
391
+
392
+ this.printers.push(printer)
393
+ }
394
+ }
395
+
396
+ print(): string {
397
+ let from = this.printers.map((printer, idx) => {
398
+ let cols: string[] = []
399
+ cols.push(`'${printer.entityName}' AS e`)
400
+ if (printer.hasColumns()) {
401
+ cols.push(printer.printColumnListAsJsonArray() + ' AS d')
402
+ } else {
403
+ cols.push('null AS d')
404
+ }
405
+ cols.push(...this.orderColumns[idx])
406
+ return `SELECT ${cols.join(', ')} ${printer.printFrom()}`
407
+ }).join('\nUNION ALL\n')
408
+
409
+ let args = this.printArgs()
410
+ if (args) {
411
+ return `SELECT e, d FROM (\n${from}\n) AS rows` + args
412
+ } else {
413
+ return from
414
+ }
415
+ }
416
+
417
+ printAsCount(): string {
418
+ let union = this.orders.length
419
+ ? this.printers.map((printer, idx) => {
420
+ return `SELECT ${this.orderColumns[idx].join(', ')} ${printer.printFrom()}`
421
+ })
422
+ : this.printers.map(printer => {
423
+ return `SELECT true ${printer.printFrom()}`
424
+ })
425
+
426
+ let from = union.join('\nUNION ALL\n')
427
+ let args = this.printArgs()
428
+ if (args) {
429
+ from = `SELECT true FROM (\n${from}\n) AS src` + args
430
+ }
431
+
432
+ return `SELECT count(*) FROM (\n${from}\n) AS rows`
433
+ }
434
+
435
+ private printArgs(): string {
436
+ let sql = ''
437
+ if (this.orders.length) {
438
+ sql += '\nORDER BY ' + this.orders.map((o, idx) => `o${idx + 1} ${o}`).join(', ')
439
+ }
440
+ if (this.args.offset) {
441
+ sql += `\nOFFSET ${this.args.offset}`
442
+ }
443
+ if (this.args.limit) {
444
+ sql += `\nLIMIT ${this.args.limit}`
445
+ }
446
+ return sql
447
+ }
448
+ }
package/src/sql/query.ts CHANGED
@@ -1,7 +1,7 @@
1
- import {assertNotNull} from "@subsquid/util-internal"
2
- import assert from "assert"
3
- import type {Dialect} from "../dialect"
4
- import type {EntityListArguments, Where} from "../ir/args"
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 "../ir/connection"
13
- import type {FieldRequest} from "../ir/fields"
14
- import type {Model} from "../model"
15
- import {toSafeInteger} from "../util/util"
16
- import {mapRows} from "./mapping"
17
- import {EntityListQueryPrinter} from "./printer"
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 EntityListQuery implements Query<any[]> {
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
- entityName: string,
35
- private fields: FieldRequest[],
36
- args: EntityListArguments
34
+ typeName: string,
35
+ private fields: AnyFields,
36
+ args: SqlArguments
37
37
  ) {
38
- this.sql = new EntityListQueryPrinter(model, dialect, entityName, this.params, args, fields).print()
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
- return mapRows(rows, this.fields)
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 EntityListQueryPrinter(
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 EntityCountQuery implements Query<number> {
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
- entityName: string,
92
+ typeName: string,
83
93
  where?: Where
84
94
  ) {
85
- this.sql = 'SELECT count(*) ' + new EntityListQueryPrinter(model, dialect, entityName, this.params, {where}).printFrom()
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 EntityConnectionQuery implements Query<RelayConnectionResponse> {
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?: FieldRequest[]
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
- entityName: string,
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 printer = new EntityListQueryPrinter(model, dialect, entityName, this.params, {
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
- }, req.edgeNode)
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?.length) {
142
+ if (req.edgeNode) {
123
143
  this.edgeNode = req.edgeNode
124
144
  this.sql = printer.print()
125
145
  } else {
126
- this.sql = `SELECT count(*) FROM (SELECT true ${printer.printFrom()}) AS rows`
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
+ })