@subsquid/openreader 0.5.1 → 0.6.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 (102) hide show
  1. package/dist/dialect.d.ts +2 -0
  2. package/dist/dialect.d.ts.map +1 -0
  3. package/dist/dialect.js +3 -0
  4. package/dist/dialect.js.map +1 -0
  5. package/dist/gql/opencrud.d.ts +4 -3
  6. package/dist/gql/opencrud.d.ts.map +1 -1
  7. package/dist/gql/opencrud.js +8 -5
  8. package/dist/gql/opencrud.js.map +1 -1
  9. package/dist/gql/scalars/BigInt.d.ts +3 -0
  10. package/dist/gql/scalars/BigInt.d.ts.map +1 -0
  11. package/dist/gql/scalars/BigInt.js +36 -0
  12. package/dist/gql/scalars/BigInt.js.map +1 -0
  13. package/dist/gql/scalars/Bytes.d.ts +3 -0
  14. package/dist/gql/scalars/Bytes.d.ts.map +1 -0
  15. package/dist/gql/scalars/Bytes.js +32 -0
  16. package/dist/gql/scalars/Bytes.js.map +1 -0
  17. package/dist/gql/scalars/DateTime.d.ts +3 -0
  18. package/dist/gql/scalars/DateTime.d.ts.map +1 -0
  19. package/dist/gql/scalars/DateTime.js +44 -0
  20. package/dist/gql/scalars/DateTime.js.map +1 -0
  21. package/dist/gql/scalars/index.d.ts +6 -0
  22. package/dist/gql/scalars/index.d.ts.map +1 -0
  23. package/dist/gql/scalars/index.js +12 -0
  24. package/dist/gql/scalars/index.js.map +1 -0
  25. package/dist/gql/schema.d.ts.map +1 -1
  26. package/dist/gql/schema.js +3 -2
  27. package/dist/gql/schema.js.map +1 -1
  28. package/dist/queryBuilder.d.ts +3 -1
  29. package/dist/queryBuilder.d.ts.map +1 -1
  30. package/dist/queryBuilder.js +104 -22
  31. package/dist/queryBuilder.js.map +1 -1
  32. package/dist/resolver.d.ts +2 -1
  33. package/dist/resolver.d.ts.map +1 -1
  34. package/dist/resolver.js +12 -12
  35. package/dist/resolver.js.map +1 -1
  36. package/dist/server.d.ts +2 -0
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +3 -2
  39. package/dist/server.js.map +1 -1
  40. package/dist/test/basic.test.js +3 -3
  41. package/dist/test/basic.test.js.map +1 -1
  42. package/dist/test/connection.test.js +1 -1
  43. package/dist/test/connection.test.js.map +1 -1
  44. package/dist/test/fts.test.js +2 -2
  45. package/dist/test/fts.test.js.map +1 -1
  46. package/dist/test/isNull.test.js +1 -1
  47. package/dist/test/isNull.test.js.map +1 -1
  48. package/dist/test/lists.test.js +3 -3
  49. package/dist/test/lists.test.js.map +1 -1
  50. package/dist/test/lookup.test.js +13 -7
  51. package/dist/test/lookup.test.js.map +1 -1
  52. package/dist/test/scalars.test.js +12 -9
  53. package/dist/test/scalars.test.js.map +1 -1
  54. package/dist/test/{util/setup.d.ts → setup.d.ts} +8 -1
  55. package/dist/test/setup.d.ts.map +1 -0
  56. package/dist/test/{util/setup.js → setup.js} +19 -8
  57. package/dist/test/setup.js.map +1 -0
  58. package/dist/test/typed-json.test.js +1 -1
  59. package/dist/test/typed-json.test.js.map +1 -1
  60. package/dist/test/unions.test.js +1 -1
  61. package/dist/test/unions.test.js.map +1 -1
  62. package/dist/test/where.test.js +1 -1
  63. package/dist/test/where.test.js.map +1 -1
  64. package/dist/util.d.ts +1 -0
  65. package/dist/util.d.ts.map +1 -1
  66. package/dist/util.js +5 -1
  67. package/dist/util.js.map +1 -1
  68. package/dist/where.d.ts +1 -1
  69. package/dist/where.d.ts.map +1 -1
  70. package/dist/where.js +2 -0
  71. package/dist/where.js.map +1 -1
  72. package/package.json +4 -4
  73. package/src/dialect.ts +2 -0
  74. package/src/gql/opencrud.ts +10 -6
  75. package/src/gql/scalars/BigInt.ts +34 -0
  76. package/src/gql/scalars/Bytes.ts +28 -0
  77. package/src/gql/scalars/DateTime.ts +45 -0
  78. package/src/gql/scalars/index.ts +10 -0
  79. package/src/gql/schema.ts +3 -2
  80. package/src/queryBuilder.ts +98 -18
  81. package/src/resolver.ts +13 -11
  82. package/src/server.ts +5 -2
  83. package/src/test/basic.test.ts +3 -3
  84. package/src/test/connection.test.ts +1 -1
  85. package/src/test/fts.test.ts +4 -2
  86. package/src/test/isNull.test.ts +1 -1
  87. package/src/test/lists.test.ts +3 -3
  88. package/src/test/lookup.test.ts +13 -7
  89. package/src/test/scalars.test.ts +12 -9
  90. package/src/test/{util/setup.ts → setup.ts} +21 -7
  91. package/src/test/typed-json.test.ts +1 -1
  92. package/src/test/unions.test.ts +1 -1
  93. package/src/test/where.test.ts +1 -1
  94. package/src/util.ts +5 -0
  95. package/src/where.ts +3 -0
  96. package/dist/scalars.d.ts +0 -36
  97. package/dist/scalars.d.ts.map +0 -1
  98. package/dist/scalars.js +0 -229
  99. package/dist/scalars.js.map +0 -1
  100. package/dist/test/util/setup.d.ts.map +0 -1
  101. package/dist/test/util/setup.js.map +0 -1
  102. package/src/scalars.ts +0 -247
@@ -0,0 +1,45 @@
1
+ import {GraphQLScalarType} from "graphql"
2
+ import {invalidFormat} from "../../util"
3
+
4
+
5
+ export const DateTimeScalar = new GraphQLScalarType({
6
+ name: 'DateTime',
7
+ description:
8
+ 'A date-time string in simplified extended ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ)',
9
+ serialize(value: Date | string) {
10
+ if (value instanceof Date) {
11
+ return value.toISOString()
12
+ } else {
13
+ if (!isIsoDateTimeString(value)) throw invalidFormat('DateTime', value)
14
+ return value
15
+ }
16
+ },
17
+ parseValue(value: string) {
18
+ return parseDateTime(value)
19
+ },
20
+ parseLiteral(ast) {
21
+ switch(ast.kind) {
22
+ case "StringValue":
23
+ return parseDateTime(ast.value)
24
+ default:
25
+ return null
26
+ }
27
+ }
28
+ })
29
+
30
+
31
+ // credit - https://github.com/Urigo/graphql-scalars/blob/91b4ea8df891be8af7904cf84751930cc0c6613d/src/scalars/iso-date/validator.ts#L122
32
+ const RFC_3339_REGEX = /^(\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60))(\.\d{1,})?([Z])$/
33
+
34
+
35
+ function isIsoDateTimeString(s: string): boolean {
36
+ return RFC_3339_REGEX.test(s)
37
+ }
38
+
39
+
40
+ function parseDateTime(s: string): Date {
41
+ if (!isIsoDateTimeString(s)) throw invalidFormat('DateTime', s)
42
+ let timestamp = Date.parse(s)
43
+ if (isNaN(timestamp)) throw invalidFormat('DateTime', s)
44
+ return new Date(timestamp)
45
+ }
@@ -0,0 +1,10 @@
1
+ import {BigIntScalar} from "./BigInt"
2
+ import {BytesScalar} from "./Bytes"
3
+ import {DateTimeScalar} from "./DateTime"
4
+
5
+
6
+ export const customScalars = {
7
+ BigInt: BigIntScalar,
8
+ Bytes: BytesScalar,
9
+ DateTime: DateTimeScalar
10
+ }
package/src/gql/schema.ts CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  } from "graphql"
21
21
  import {Index, Model, Prop, PropType} from "../model"
22
22
  import {validateModel} from "../model.tools"
23
- import {scalars_list} from "../scalars"
23
+ import {customScalars} from "./scalars"
24
24
 
25
25
 
26
26
  const baseSchema = buildASTSchema(parse(`
@@ -31,7 +31,8 @@ const baseSchema = buildASTSchema(parse(`
31
31
  directive @fulltext(query: String!) on FIELD_DEFINITION
32
32
  directive @variant on OBJECT # legacy
33
33
  directive @jsonField on OBJECT # legacy
34
- ${scalars_list.map(name => 'scalar ' + name).join('\n')}
34
+ scalar ID
35
+ ${Object.keys(customScalars).map(name => 'scalar ' + name).join('\n')}
35
36
  `))
36
37
 
37
38
 
@@ -1,11 +1,11 @@
1
1
  import {toSnakeCase} from "@subsquid/util"
2
2
  import assert from "assert"
3
3
  import {Database} from "./db"
4
+ import type {Dialect} from "./dialect"
4
5
  import type {Entity, JsonObject, Model} from "./model"
5
6
  import {getEntity, getFtsQuery, getObject, getUnionProps} from "./model.tools"
6
7
  import {OpenCrudOrderByValue, OrderBy, parseOrderBy} from "./orderBy"
7
8
  import type {FtsRequestedFields, RequestedFields} from "./requestedFields"
8
- import {fromJsonCast, fromJsonToOutputCast, toOutputArrayCast, toOutputCast} from "./scalars"
9
9
  import {ensureArray, toColumn, toFkColumn, toInt, toTable, unsupportedCase} from "./util"
10
10
  import {hasConditions, parseWhereField, WhereOp, whereOpToSqlOperator} from "./where"
11
11
 
@@ -24,6 +24,7 @@ export class QueryBuilder {
24
24
 
25
25
  constructor(
26
26
  private model: Model,
27
+ private dialect: Dialect,
27
28
  private db: Database
28
29
  ) {}
29
30
 
@@ -43,6 +44,7 @@ export class QueryBuilder {
43
44
 
44
45
  let cursor = new Cursor(
45
46
  this.model,
47
+ this.dialect,
46
48
  this.ident.bind(this),
47
49
  this.aliases,
48
50
  join,
@@ -75,7 +77,7 @@ export class QueryBuilder {
75
77
  break
76
78
  case 'list-subquery':
77
79
  if (columns.size()) {
78
- out += `SELECT json_build_array(${columns.render()}) `
80
+ out += `SELECT json_build_array(${columns.render()}) AS row `
79
81
  }
80
82
  break
81
83
  default:
@@ -177,7 +179,7 @@ export class QueryBuilder {
177
179
  case 'scalar':
178
180
  case 'enum':
179
181
  case 'list':
180
- req.index = columns.add(cursor.transport(fieldName))
182
+ req.index = columns.add(cursor.output(fieldName))
181
183
  break
182
184
  case 'object':
183
185
  req.index = columns.add(cursor.field(fieldName) + ' IS NULL')
@@ -189,7 +191,7 @@ export class QueryBuilder {
189
191
  break
190
192
  case 'union':
191
193
  let cu = cursor.child(fieldName)
192
- req.index = columns.add(cu.transport('isTypeOf'))
194
+ req.index = columns.add(cu.output('isTypeOf'))
193
195
  this.populateColumns(
194
196
  columns,
195
197
  cu,
@@ -199,7 +201,7 @@ export class QueryBuilder {
199
201
  case 'fk':
200
202
  case 'lookup': {
201
203
  let cu = cursor.child(fieldName)
202
- req.index = columns.add(cu.transport('id'))
204
+ req.index = columns.add(cu.output('id'))
203
205
  this.populateColumns(
204
206
  columns,
205
207
  cu,
@@ -209,11 +211,11 @@ export class QueryBuilder {
209
211
  }
210
212
  case 'list-lookup':
211
213
  req.index = columns.add(
212
- 'array(' + this.select(field.propType.entity, req.args, req.children, {
214
+ '(SELECT jsonb_agg(row) FROM (' + this.select(field.propType.entity, req.args, req.children, {
213
215
  kind: 'list-subquery',
214
216
  field: field.propType.field,
215
217
  parent: cursor.native('id')
216
- }) + ')'
218
+ }) + ') as rows)'
217
219
  )
218
220
  break
219
221
  default:
@@ -329,18 +331,29 @@ export class QueryBuilder {
329
331
  break
330
332
  }
331
333
  case 'startsWith':
332
- exps.push(`starts_with(${lhs}, ${this.param(arg)})`)
334
+ if (this.dialect == 'cockroach') {
335
+ let p = this.param(arg) + '::text'
336
+ exps.push(`${lhs} >= ${p}`)
337
+ exps.push(`left(${lhs}, length(${p})) = ${p}`)
338
+ } else {
339
+ exps.push(`starts_with(${lhs}, ${this.param(arg)})`)
340
+ }
333
341
  break
334
342
  case 'not_startsWith':
335
- exps.push(`NOT starts_with(${lhs}, ${this.param(arg)})`)
343
+ if (this.dialect == 'cockroach') {
344
+ let p = this.param(arg) + '::text'
345
+ exps.push(`(${lhs} < ${p} OR left(${lhs}, length(${p})) != ${p})`)
346
+ } else {
347
+ exps.push(`NOT starts_with(${lhs}, ${this.param(arg)})`)
348
+ }
336
349
  break
337
350
  case 'endsWith': {
338
- let param = this.param(arg)
351
+ let param = this.param(arg) + '::text'
339
352
  exps.push(`right(${lhs}, length(${param})) = ${param}`)
340
353
  break
341
354
  }
342
355
  case 'not_endsWith': {
343
- let param = this.param(arg)
356
+ let param = this.param(arg) + '::text'
344
357
  exps.push(`right(${lhs}, length(${param})) != ${param}`)
345
358
  break
346
359
  }
@@ -350,6 +363,12 @@ export class QueryBuilder {
350
363
  case 'not_contains':
351
364
  exps.push(`position(${this.param(arg)} in ${lhs}) = 0`)
352
365
  break
366
+ case 'containsInsensitive':
367
+ exps.push(`position(lower(${this.param(arg)}) in lower(${lhs})) > 0`)
368
+ break
369
+ case 'not_containsInsensitive':
370
+ exps.push(`position(lower(${this.param(arg)}) in lower(${lhs})) = 0`)
371
+ break
353
372
  default: {
354
373
  exps.push(`${lhs} ${whereOpToSqlOperator(op)} ${this.param(arg)}`)
355
374
  }
@@ -578,11 +597,12 @@ interface ListSubquery {
578
597
  * A pointer to an entity or nested json object within SQL query.
579
598
  *
580
599
  * It has convenience methods for building various SQL expressions
581
- * related to individual properties of an entity or an object it points to.
600
+ * related to individual properties of an entity or of an object it points to.
582
601
  */
583
602
  class Cursor {
584
603
  constructor(
585
604
  private model: Model,
605
+ private dialect: Dialect,
586
606
  private ident: (name: string) => string,
587
607
  private aliases: AliasSet,
588
608
  private join: JoinSet,
@@ -592,15 +612,43 @@ class Cursor {
592
612
  private prefix: string
593
613
  ) {}
594
614
 
595
- transport(propName: string): string {
615
+ output(propName: string): string {
596
616
  let prop = this.object.properties[propName]
597
617
  switch(prop.type.kind) {
598
618
  case 'scalar':
619
+ if (this.object.kind == 'object') {
620
+ switch(prop.type.name) {
621
+ case 'Int':
622
+ return `(${this.prefix}->'${propName}')::integer`
623
+ case 'Float':
624
+ return `(${this.prefix}->'${propName}')::numeric`
625
+ case 'Boolean':
626
+ return `(${this.prefix}->>'${propName}')::bool`
627
+ default:
628
+ return `${this.prefix}->>'${propName}'`
629
+ }
630
+ } else {
631
+ let exp = this.column(propName)
632
+ switch(prop.type.name) {
633
+ case 'BigInt':
634
+ return `(${exp})::text`
635
+ case 'Bytes':
636
+ return `'0x' || encode(${exp}, 'hex')`
637
+ case 'DateTime':
638
+ if (this.dialect == 'cockroach') {
639
+ return `experimental_strftime((${exp}) at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ')`
640
+ } else {
641
+ return `to_char((${exp}) at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`
642
+ }
643
+ default:
644
+ return exp
645
+ }
646
+ }
599
647
  case 'enum':
600
648
  if (this.object.kind == 'object') {
601
- return fromJsonToOutputCast(prop.type.name, this.prefix, propName)
649
+ return `${this.prefix}->>'${propName}'`
602
650
  } else {
603
- return toOutputCast(prop.type.name, this.column(propName))
651
+ return this.column(propName)
604
652
  }
605
653
  case 'list':
606
654
  let itemType = prop.type.item.type
@@ -608,7 +656,21 @@ class Cursor {
608
656
  // this is json
609
657
  return this.field(propName)
610
658
  } else {
611
- return toOutputArrayCast(itemType.name, this.column(propName))
659
+ let exp = this.column(propName)
660
+ switch(itemType.name) {
661
+ case 'BigInt':
662
+ return `(${exp})::text[]`
663
+ case 'Bytes':
664
+ return `array(select '0x' || encode(i, 'hex') from unnest(${exp}) as i)`
665
+ case 'DateTime':
666
+ if (this.dialect == 'cockroach') {
667
+ return `array(select experimental_strftime(i at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ') from unnest(${exp}) as i)`
668
+ } else {
669
+ return `array(select to_char(i at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') from unnest(${exp}) as i)`
670
+ }
671
+ default:
672
+ return exp
673
+ }
612
674
  }
613
675
  default:
614
676
  throw unsupportedCase(prop.type.kind)
@@ -624,7 +686,24 @@ class Cursor {
624
686
  }
625
687
  assert(prop.type.kind == 'scalar' || prop.type.kind == 'enum')
626
688
  if (this.object.kind == 'object') {
627
- return fromJsonCast(prop.type.name, this.prefix, propName)
689
+ let js = `${this.prefix}->'${propName}'`
690
+ let str = `${this.prefix}->>'${propName}'`
691
+ switch(prop.type.name) {
692
+ case 'Int':
693
+ return `(${js})::integer`
694
+ case 'Float':
695
+ return `(${js})::numeric`
696
+ case 'Boolean':
697
+ return `(${str})::bool`
698
+ case 'BigInt':
699
+ return `(${str})::numeric`
700
+ case 'Bytes':
701
+ return `decode(substr(${str}, 3), 'hex')`
702
+ case 'DateTime':
703
+ return `(${str})::timestamptz`
704
+ default:
705
+ return str
706
+ }
628
707
  } else {
629
708
  return this.column(propName)
630
709
  }
@@ -676,6 +755,7 @@ class Cursor {
676
755
 
677
756
  return new Cursor(
678
757
  this.model,
758
+ this.dialect,
679
759
  this.ident,
680
760
  this.aliases,
681
761
  this.join,
@@ -702,7 +782,7 @@ class Cursor {
702
782
  fk(propName: string): string {
703
783
  return this.object.kind == 'entity'
704
784
  ? this.ident(this.alias) + '.' + this.ident(toFkColumn(propName))
705
- : fromJsonCast('ID', this.prefix, propName)
785
+ : `${this.prefix}->>'${propName}'`
706
786
  }
707
787
 
708
788
  tsv(queryName: string): string {
package/src/resolver.ts CHANGED
@@ -4,6 +4,8 @@ import {UserInputError} from "apollo-server-core"
4
4
  import assert from "assert"
5
5
  import type {GraphQLResolveInfo} from "graphql"
6
6
  import type {Database, Transaction} from "./db"
7
+ import type {Dialect} from "./dialect"
8
+ import {customScalars} from "./gql/scalars"
7
9
  import type {Entity, JsonObject, Model} from "./model"
8
10
  import {QueryBuilder} from "./queryBuilder"
9
11
  import {
@@ -15,7 +17,6 @@ import {
15
17
  PageInfo
16
18
  } from "./relayConnection"
17
19
  import {connectionRequestedFields, ftsRequestedFields, requestedFields} from "./requestedFields"
18
- import {getScalarResolvers} from "./scalars"
19
20
  import {ensureArray, toQueryListField, unsupportedCase} from "./util"
20
21
 
21
22
 
@@ -24,9 +25,9 @@ export interface ResolverContext {
24
25
  }
25
26
 
26
27
 
27
- export function buildResolvers(model: Model): IResolvers<unknown, ResolverContext> {
28
+ export function buildResolvers(model: Model, dialect: Dialect): IResolvers<unknown, ResolverContext> {
28
29
  let Query: Record<string, IFieldResolver<unknown, ResolverContext>> = {}
29
- let resolvers: IResolvers = {Query, ...getScalarResolvers()}
30
+ let resolvers: IResolvers = {Query, ...customScalars}
30
31
 
31
32
  for (let name in model) {
32
33
  let item = model[name]
@@ -35,16 +36,16 @@ export function buildResolvers(model: Model): IResolvers<unknown, ResolverContex
35
36
  Query[toQueryListField(name)] = async (source, args, context, info) => {
36
37
  let fields = requestedFields(model, name, info)
37
38
  let db = await context.openReaderTransaction.get()
38
- return new QueryBuilder(model, db).executeSelect(name, args, fields)
39
+ return new QueryBuilder(model, dialect, db).executeSelect(name, args, fields)
39
40
  }
40
41
  Query[toQueryListField(name) + 'Connection'] = async (source, args, context, info) => {
41
42
  let db = await context.openReaderTransaction.get()
42
- return resolveEntityConnection(model, name, args, info, db)
43
+ return resolveEntityConnection(model, dialect, name, args, info, db)
43
44
  }
44
45
  Query[`${toCamelCase(name)}ById`] = async (source, args, context, info) => {
45
46
  let fields = requestedFields(model, name, info)
46
47
  let db = await context.openReaderTransaction.get()
47
- let result = await new QueryBuilder(model, db)
48
+ let result = await new QueryBuilder(model, dialect, db)
48
49
  .executeSelect(name, {where: {id_eq: args.id}}, fields)
49
50
  assert(result.length < 2)
50
51
  return result[0]
@@ -52,7 +53,7 @@ export function buildResolvers(model: Model): IResolvers<unknown, ResolverContex
52
53
  Query[`${toCamelCase(name)}ByUniqueInput`] = async (source, args, context, info) => {
53
54
  let fields = requestedFields(model, name, info)
54
55
  let db = await context.openReaderTransaction.get()
55
- let result = await new QueryBuilder(model, db)
56
+ let result = await new QueryBuilder(model, dialect, db)
56
57
  .executeSelect(name, {where: {id_eq: args.where.id}}, fields)
57
58
  assert(result.length < 2)
58
59
  return result[0]
@@ -71,7 +72,7 @@ export function buildResolvers(model: Model): IResolvers<unknown, ResolverContex
71
72
  Query[name] = async (source, args, context, info) => {
72
73
  let fields = ftsRequestedFields(model, name, info)
73
74
  let db = await context.openReaderTransaction.get()
74
- return new QueryBuilder(model, db).executeFulltextSearch(name, args, fields)
75
+ return new QueryBuilder(model, dialect, db).executeFulltextSearch(name, args, fields)
75
76
  }
76
77
  resolvers[`${name}_Item`] = {
77
78
  __resolveType: resolveUnionType
@@ -130,6 +131,7 @@ interface ConnectionResponse extends RelayConnectionResponse<any> {
130
131
 
131
132
  async function resolveEntityConnection(
132
133
  model: Model,
134
+ dialect: Dialect,
133
135
  entityName: string,
134
136
  args: ConnectionArgs,
135
137
  info: GraphQLResolveInfo,
@@ -162,7 +164,7 @@ async function resolveEntityConnection(
162
164
 
163
165
  let fields = connectionRequestedFields(model, entityName, info)
164
166
  if (fields.edges?.node) {
165
- let nodes = await new QueryBuilder(model, db).executeSelect(entityName, listArgs, fields.edges.node)
167
+ let nodes = await new QueryBuilder(model, dialect, db).executeSelect(entityName, listArgs, fields.edges.node)
166
168
  let edges: ConnectionEdge<any>[] = new Array(Math.min(limit, nodes.length))
167
169
  for (let i = 0; i < edges.length; i++) {
168
170
  edges[i] = {
@@ -176,7 +178,7 @@ async function resolveEntityConnection(
176
178
  response.totalCount = offset + nodes.length
177
179
  }
178
180
  } else if (fields.edges?.cursor || fields.pageInfo) {
179
- let listLength = await new QueryBuilder(model, db).executeListCount(entityName, listArgs)
181
+ let listLength = await new QueryBuilder(model, dialect, db).executeListCount(entityName, listArgs)
180
182
  response.pageInfo = pageInfo(listLength)
181
183
  if (fields.edges?.cursor) {
182
184
  response.edges = []
@@ -192,7 +194,7 @@ async function resolveEntityConnection(
192
194
  }
193
195
 
194
196
  if (fields.totalCount && response.totalCount == null) {
195
- response.totalCount = await new QueryBuilder(model, db).executeSelectCount(entityName, listArgs.where)
197
+ response.totalCount = await new QueryBuilder(model, dialect, db).executeSelectCount(entityName, listArgs.where)
196
198
  }
197
199
 
198
200
  return response
package/src/server.ts CHANGED
@@ -7,6 +7,7 @@ import http from "http"
7
7
  import path from "path"
8
8
  import type {Pool} from "pg"
9
9
  import {PoolTransaction} from "./db"
10
+ import {Dialect} from "./dialect"
10
11
  import {buildServerSchema} from "./gql/opencrud"
11
12
  import type {Model} from "./model"
12
13
  import {buildResolvers} from "./resolver"
@@ -22,14 +23,16 @@ export interface ServerOptions {
22
23
  model: Model
23
24
  db: Pool
24
25
  port: number | string
26
+ dialect?: Dialect
25
27
  graphiqlConsole?: boolean
26
28
  }
27
29
 
28
30
 
29
31
  export async function serve(options: ServerOptions): Promise<ListeningServer> {
30
32
  let {model, db} = options
31
- let resolvers = buildResolvers(model)
32
- let typeDefs = buildServerSchema(model)
33
+ let dialect = options.dialect ?? 'postgres'
34
+ let resolvers = buildResolvers(model, dialect)
35
+ let typeDefs = buildServerSchema(model, dialect)
33
36
  let app = express()
34
37
  let server = http.createServer(app)
35
38
 
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
 
4
4
  describe('basic tests', function() {
@@ -44,11 +44,11 @@ describe('basic tests', function() {
44
44
  it('can fetch all accounts', function() {
45
45
  return client.test(
46
46
  `query {
47
- accounts {
47
+ accounts(orderBy: id_ASC) {
48
48
  id
49
49
  wallet
50
50
  balance
51
- history { balance }
51
+ history(orderBy: id_ASC) { balance }
52
52
  }
53
53
  }`,
54
54
  {
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
  describe('relay connections', function () {
4
4
  useDatabase([
@@ -1,10 +1,12 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {isCockroach, useDatabase, useServer} from "./setup"
2
+
2
3
 
3
4
  function tsvector(columns: string[]) {
4
5
  return columns.map(col => `setweight(to_tsvector('english', coalesce(${col}, '')), 'A')`).join(' || ')
5
6
  }
6
7
 
7
- describe('full text search', function () {
8
+
9
+ isCockroach() || describe('full text search', function () {
8
10
  useDatabase([
9
11
  `create table foo (
10
12
  id text primary key,
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
 
4
4
  describe('isNull operator', function() {
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
 
4
4
  describe('lists', function () {
@@ -172,8 +172,8 @@ describe('lists', function () {
172
172
  `, {
173
173
  lists: [{
174
174
  datetimeArray: [
175
- '2020-01-01T00:00:00.000Z',
176
- '2021-01-01T00:00:00.000Z'
175
+ '2020-01-01T00:00:00.000000Z',
176
+ '2021-01-01T00:00:00.000000Z'
177
177
  ]
178
178
  }]
179
179
  })
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {isCockroach, useDatabase, useServer} from "./setup"
2
2
 
3
3
  describe('lookup test', function () {
4
4
  useDatabase([
@@ -82,16 +82,22 @@ describe('lookup test', function () {
82
82
  it('supports sorting on lookup fields', function () {
83
83
  return client.test(`
84
84
  query {
85
- issues(orderBy: [payment_amount_DESC]) {
85
+ issues(orderBy: [payment_amount_ASC]) {
86
86
  id
87
87
  }
88
88
  }
89
89
  `, {
90
- issues: [
91
- {id: '3'},
92
- {id: '1'},
93
- {id: '2'}
94
- ]
90
+ issues: isCockroach()
91
+ ? [
92
+ {id: '3'},
93
+ {id: '2'},
94
+ {id: '1'}
95
+ ]
96
+ : [
97
+ {id: '2'},
98
+ {id: '1'},
99
+ {id: '3'}
100
+ ]
95
101
  })
96
102
  })
97
103
 
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
  describe('scalars', function() {
4
4
  useDatabase([
@@ -12,6 +12,7 @@ describe('scalars', function() {
12
12
  `insert into scalar (id, "string") values ('7', 'bar baz foo')`,
13
13
  `insert into scalar (id, "string") values ('8', 'baz foo bar')`,
14
14
  `insert into scalar (id, "string") values ('9', 'hello')`,
15
+ `insert into scalar (id, "string") values ('9-1', 'A fOo B')`,
15
16
  `insert into scalar (id, "date_time", deep) values ('10', '2021-09-24T15:43:13.400Z', '{"dateTime": "2021-09-24T00:00:00.120Z"}'::jsonb)`,
16
17
  `insert into scalar (id, "date_time", deep) values ('11', '2021-09-24T00:00:00.000Z', '{"dateTime": "2021-09-24T00:00:00Z"}'::jsonb)`,
17
18
  `insert into scalar (id, "date_time", deep) values ('12', '2021-09-24 02:00:00.001 +01:00', '{"dateTime": "2021-09-24T00:00:00.1Z"}'::jsonb)`,
@@ -106,16 +107,18 @@ describe('scalars', function() {
106
107
  not_ends_with: scalars(where: {string_not_endsWith: "foo"} orderBy: id_ASC) { id }
107
108
  contains: scalars(where: {string_contains: "foo"} orderBy: id_ASC) { id }
108
109
  not_contains: scalars(where: {string_not_contains: "foo"} orderBy: id_ASC) { id }
109
- case_sensitive: scalars(where: {string_contains: "Foo"} orderBy: id_ASC) { id }
110
+ contains_insensitive: scalars(where: {string_containsInsensitive: "FoO"} orderBy: id_ASC) { id }
111
+ not_contains_insensitive: scalars(where: {string_not_containsInsensitive: "FoO"} orderBy: id_ASC) { id }
110
112
  }
111
113
  `, {
112
114
  starts_with: [{id: '6'}],
113
- not_starts_with: [{id: '7'}, {id: '8'}, {id: '9'}],
115
+ not_starts_with: [{id: '7'}, {id: '8'}, {id: '9'}, {id: '9-1'}],
114
116
  ends_with: [{id: '7'}],
115
- not_ends_with: [{id: '6'}, {id: '8'}, {id: '9'}],
117
+ not_ends_with: [{id: '6'}, {id: '8'}, {id: '9'}, {id: '9-1'}],
116
118
  contains: [{id: '6'}, {id: '7'}, {id: '8'}],
117
- not_contains: [{id: '9'}],
118
- case_sensitive: []
119
+ not_contains: [{id: '9'}, {id: '9-1'}],
120
+ contains_insensitive: [{id: '6'}, {id: '7'}, {id: '8'}, {id: '9-1'}],
121
+ not_contains_insensitive: [{id: '9'}]
119
122
  })
120
123
  })
121
124
  })
@@ -239,9 +242,9 @@ describe('scalars', function() {
239
242
  }
240
243
  `, {
241
244
  scalars: [
242
- {id: '10', dateTime: '2021-09-24T15:43:13.400Z', deep: {dateTime: '2021-09-24T00:00:00.120Z'}},
243
- {id: '11', dateTime: '2021-09-24T00:00:00.000Z', deep: {dateTime: '2021-09-24T00:00:00Z'}},
244
- {id: '12', dateTime: '2021-09-24T01:00:00.001Z', deep: {dateTime: '2021-09-24T00:00:00.1Z'}}
245
+ {id: '10', dateTime: '2021-09-24T15:43:13.400000Z', deep: {dateTime: '2021-09-24T00:00:00.120Z'}},
246
+ {id: '11', dateTime: '2021-09-24T00:00:00.000000Z', deep: {dateTime: '2021-09-24T00:00:00Z'}},
247
+ {id: '12', dateTime: '2021-09-24T01:00:00.001000Z', deep: {dateTime: '2021-09-24T00:00:00.1Z'}}
245
248
  ]
246
249
  })
247
250
  })
@@ -1,12 +1,25 @@
1
+ import {assertNotNull} from "@subsquid/util"
1
2
  import {Client} from "gql-test-client"
2
3
  import {parse} from "graphql"
3
4
  import {Client as PgClient, ClientBase, Pool} from "pg"
4
- import {createPoolConfig} from "../../db"
5
- import {buildModel, buildSchema} from "../../gql/schema"
6
- import {ListeningServer, serve} from "../../server"
5
+ import {buildModel, buildSchema} from "../gql/schema"
6
+ import {ListeningServer, serve} from "../server"
7
7
 
8
8
 
9
- export const db_config = createPoolConfig()
9
+ export function isCockroach(): boolean {
10
+ return process.env.DB_TYPE == 'cockroach'
11
+ }
12
+
13
+
14
+ export const db_config = {
15
+ host: 'localhost',
16
+ port: parseInt(assertNotNull(
17
+ isCockroach() ? process.env.DB_PORT_COCKROACH : process.env.DB_PORT_PG
18
+ )),
19
+ user: 'root',
20
+ password: 'root',
21
+ database: 'defaultdb'
22
+ }
10
23
 
11
24
 
12
25
  async function withClient(block: (client: ClientBase) => Promise<void>): Promise<void> {
@@ -31,8 +44,8 @@ export function databaseInit(sql: string[]): Promise<void> {
31
44
 
32
45
  export function databaseDelete(): Promise<void> {
33
46
  return withClient(async client => {
34
- await client.query(`DROP SCHEMA IF EXISTS public CASCADE`)
35
- await client.query(`CREATE SCHEMA public`)
47
+ await client.query(`DROP SCHEMA IF EXISTS root CASCADE`)
48
+ await client.query(`CREATE SCHEMA root`)
36
49
  })
37
50
  }
38
51
 
@@ -53,7 +66,8 @@ export function useServer(schema: string): Client {
53
66
  info = await serve({
54
67
  db,
55
68
  model: buildModel(buildSchema(parse(schema))),
56
- port: 0
69
+ port: 0,
70
+ dialect: isCockroach() ? 'cockroach' : 'postgres'
57
71
  })
58
72
  client.endpoint = `http://localhost:${info.port}/graphql`
59
73
  })
@@ -1,4 +1,4 @@
1
- import {useDatabase, useServer} from "./util/setup"
1
+ import {useDatabase, useServer} from "./setup"
2
2
 
3
3
  describe('typed json fields', function () {
4
4
  useDatabase([