@subsquid/openreader 0.5.1 → 0.7.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.
Files changed (117) hide show
  1. package/README.md +1 -1
  2. package/dist/dialect.d.ts +2 -0
  3. package/dist/dialect.d.ts.map +1 -0
  4. package/dist/dialect.js +3 -0
  5. package/dist/dialect.js.map +1 -0
  6. package/dist/gql/opencrud.d.ts +4 -3
  7. package/dist/gql/opencrud.d.ts.map +1 -1
  8. package/dist/gql/opencrud.js +12 -5
  9. package/dist/gql/opencrud.js.map +1 -1
  10. package/dist/gql/scalars/BigInt.d.ts +3 -0
  11. package/dist/gql/scalars/BigInt.d.ts.map +1 -0
  12. package/dist/gql/scalars/BigInt.js +36 -0
  13. package/dist/gql/scalars/BigInt.js.map +1 -0
  14. package/dist/gql/scalars/Bytes.d.ts +3 -0
  15. package/dist/gql/scalars/Bytes.d.ts.map +1 -0
  16. package/dist/gql/scalars/Bytes.js +32 -0
  17. package/dist/gql/scalars/Bytes.js.map +1 -0
  18. package/dist/gql/scalars/DateTime.d.ts +3 -0
  19. package/dist/gql/scalars/DateTime.d.ts.map +1 -0
  20. package/dist/gql/scalars/DateTime.js +44 -0
  21. package/dist/gql/scalars/DateTime.js.map +1 -0
  22. package/dist/gql/scalars/JSON.d.ts +3 -0
  23. package/dist/gql/scalars/JSON.d.ts.map +1 -0
  24. package/dist/gql/scalars/JSON.js +9 -0
  25. package/dist/gql/scalars/JSON.js.map +1 -0
  26. package/dist/gql/scalars/index.d.ts +7 -0
  27. package/dist/gql/scalars/index.d.ts.map +1 -0
  28. package/dist/gql/scalars/index.js +14 -0
  29. package/dist/gql/scalars/index.js.map +1 -0
  30. package/dist/gql/schema.d.ts.map +1 -1
  31. package/dist/gql/schema.js +3 -2
  32. package/dist/gql/schema.js.map +1 -1
  33. package/dist/orderBy.d.ts.map +1 -1
  34. package/dist/orderBy.js +4 -2
  35. package/dist/orderBy.js.map +1 -1
  36. package/dist/queryBuilder.d.ts +3 -1
  37. package/dist/queryBuilder.d.ts.map +1 -1
  38. package/dist/queryBuilder.js +121 -24
  39. package/dist/queryBuilder.js.map +1 -1
  40. package/dist/resolver.d.ts +2 -1
  41. package/dist/resolver.d.ts.map +1 -1
  42. package/dist/resolver.js +12 -12
  43. package/dist/resolver.js.map +1 -1
  44. package/dist/server.d.ts +2 -0
  45. package/dist/server.d.ts.map +1 -1
  46. package/dist/server.js +3 -2
  47. package/dist/server.js.map +1 -1
  48. package/dist/test/basic.test.js +3 -3
  49. package/dist/test/basic.test.js.map +1 -1
  50. package/dist/test/connection.test.js +1 -1
  51. package/dist/test/connection.test.js.map +1 -1
  52. package/dist/test/fts.test.js +2 -2
  53. package/dist/test/fts.test.js.map +1 -1
  54. package/dist/test/isNull.test.js +1 -1
  55. package/dist/test/isNull.test.js.map +1 -1
  56. package/dist/test/lists.test.js +3 -3
  57. package/dist/test/lists.test.js.map +1 -1
  58. package/dist/test/lookup.test.js +13 -7
  59. package/dist/test/lookup.test.js.map +1 -1
  60. package/dist/test/regressions.test.d.ts +2 -0
  61. package/dist/test/regressions.test.d.ts.map +1 -0
  62. package/dist/test/regressions.test.js +39 -0
  63. package/dist/test/regressions.test.js.map +1 -0
  64. package/dist/test/scalars.test.js +47 -10
  65. package/dist/test/scalars.test.js.map +1 -1
  66. package/dist/test/{util/setup.d.ts → setup.d.ts} +8 -1
  67. package/dist/test/setup.d.ts.map +1 -0
  68. package/dist/test/{util/setup.js → setup.js} +19 -8
  69. package/dist/test/setup.js.map +1 -0
  70. package/dist/test/typed-json.test.js +16 -2
  71. package/dist/test/typed-json.test.js.map +1 -1
  72. package/dist/test/unions.test.js +1 -1
  73. package/dist/test/unions.test.js.map +1 -1
  74. package/dist/test/where.test.js +1 -1
  75. package/dist/test/where.test.js.map +1 -1
  76. package/dist/util.d.ts +1 -0
  77. package/dist/util.d.ts.map +1 -1
  78. package/dist/util.js +5 -1
  79. package/dist/util.js.map +1 -1
  80. package/dist/where.d.ts +1 -1
  81. package/dist/where.d.ts.map +1 -1
  82. package/dist/where.js +5 -1
  83. package/dist/where.js.map +1 -1
  84. package/package.json +5 -5
  85. package/src/dialect.ts +2 -0
  86. package/src/gql/opencrud.ts +15 -6
  87. package/src/gql/scalars/BigInt.ts +34 -0
  88. package/src/gql/scalars/Bytes.ts +28 -0
  89. package/src/gql/scalars/DateTime.ts +45 -0
  90. package/src/gql/scalars/JSON.ts +7 -0
  91. package/src/gql/scalars/index.ts +12 -0
  92. package/src/gql/schema.ts +3 -2
  93. package/src/orderBy.ts +4 -2
  94. package/src/queryBuilder.ts +114 -20
  95. package/src/resolver.ts +13 -11
  96. package/src/server.ts +5 -2
  97. package/src/test/basic.test.ts +3 -3
  98. package/src/test/connection.test.ts +1 -1
  99. package/src/test/fts.test.ts +4 -2
  100. package/src/test/isNull.test.ts +1 -1
  101. package/src/test/lists.test.ts +3 -3
  102. package/src/test/lookup.test.ts +13 -7
  103. package/src/test/regressions.test.ts +39 -0
  104. package/src/test/scalars.test.ts +49 -10
  105. package/src/test/{util/setup.ts → setup.ts} +21 -7
  106. package/src/test/typed-json.test.ts +17 -2
  107. package/src/test/unions.test.ts +1 -1
  108. package/src/test/where.test.ts +1 -1
  109. package/src/util.ts +5 -0
  110. package/src/where.ts +9 -2
  111. package/dist/scalars.d.ts +0 -36
  112. package/dist/scalars.d.ts.map +0 -1
  113. package/dist/scalars.js +0 -229
  114. package/dist/scalars.js.map +0 -1
  115. package/dist/test/util/setup.d.ts.map +0 -1
  116. package/dist/test/util/setup.js.map +0 -1
  117. package/src/scalars.ts +0 -247
package/dist/where.js CHANGED
@@ -13,6 +13,8 @@ const ENDINGS = [
13
13
  'not_in',
14
14
  'contains',
15
15
  'not_contains',
16
+ 'containsInsensitive',
17
+ 'not_containsInsensitive',
16
18
  'startsWith',
17
19
  'not_startsWith',
18
20
  'endsWith',
@@ -22,7 +24,9 @@ const ENDINGS = [
22
24
  'containsNone',
23
25
  'some',
24
26
  'every',
25
- 'none'
27
+ 'none',
28
+ 'jsonContains',
29
+ 'jsonHasKey',
26
30
  ].sort((a, b) => b.length - a.length).map(e => '_' + e);
27
31
  function parseEnding(field) {
28
32
  for (let i = 0; i < ENDINGS.length; i++) {
package/dist/where.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"where.js","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"where.js","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subsquid/openreader",
3
- "version": "0.5.1",
3
+ "version": "0.7.1",
4
4
  "description": "GraphQL server for squid framework",
5
5
  "keywords": [
6
6
  "graphql",
@@ -23,13 +23,13 @@
23
23
  "@graphql-tools/merge": "^8",
24
24
  "@graphql-tools/utils": "^8",
25
25
  "@subsquid/graphiql-console": "^0.2.0",
26
- "@subsquid/util": "^0.0.4",
26
+ "@subsquid/util": "^0.0.5",
27
27
  "apollo-server-core": "^3.6.2",
28
28
  "apollo-server-express": "^3.6.2",
29
29
  "express": "^4.17.2",
30
30
  "graphql": "^15.8.0",
31
31
  "graphql-parse-resolve-info": "^4.12.0",
32
- "pg": "^8.7.1"
32
+ "pg": "^8.7.3"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/express": "^4.17.13",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "scripts": {
46
46
  "build": "rm -rf dist && tsc",
47
- "test": "make up && sleep 1 && make test && make down || (make down && exit 1)"
47
+ "test": "make up && sleep 1 && make test test-cockroach && make down || (make down && exit 1)"
48
48
  },
49
- "readme": "# OpenReader\n\nGraphQL server for squid framework. Given [data schema](https://docs.subsquid.io/schema-spec) \nand compatible Postgres database it serves \"read part\" of [OpenCRUD spec](https://www.opencrud.org).\n\n## Usage\n\n```bash\nopenreader schema.graphql\n```\n\nDatabase connection and server port are configured via environment variables:\n\n```\nDB_NAME\nDB_USER\nDB_PASS\nDB_HOST\nDB_PORT\nGRAPHQL_SERVER_PORT\n```\n"
49
+ "readme": "# OpenReader\n\nGraphQL server for squid framework. Given [data schema](https://docs.subsquid.io/reference/openreader-schema) \nand compatible Postgres database it serves \"read part\" of [OpenCRUD spec](https://www.opencrud.org).\n\n## Usage\n\n```bash\nopenreader schema.graphql\n```\n\nDatabase connection and server port are configured via environment variables:\n\n```\nDB_NAME\nDB_USER\nDB_PASS\nDB_HOST\nDB_PORT\nGRAPHQL_SERVER_PORT\n```\n"
50
50
  }
package/src/dialect.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export type Dialect = 'postgres' | 'cockroach'
@@ -1,13 +1,14 @@
1
1
  import {Output, toCamelCase, toPlural} from "@subsquid/util"
2
2
  import assert from "assert"
3
3
  import {DocumentNode, parse, print} from "graphql"
4
- import {Entity, Enum, FTS_Query, Interface, JsonObject, Model, Prop, Union} from "../model"
4
+ import type {Dialect} from "../dialect"
5
+ import type {Entity, Enum, FTS_Query, Interface, JsonObject, Model, Prop, Union} from "../model"
5
6
  import {getOrderByMapping} from "../orderBy"
6
- import {scalars_list} from "../scalars"
7
7
  import {toQueryListField} from "../util"
8
+ import {customScalars} from "./scalars"
8
9
 
9
10
 
10
- export function generateOpenCrudQueries(model: Model): string {
11
+ export function generateOpenCrudQueries(model: Model, dialect: Dialect): string {
11
12
  let out = new Output()
12
13
 
13
14
  generatePageInfoType()
@@ -39,6 +40,7 @@ export function generateOpenCrudQueries(model: Model): string {
39
40
  generateEnumType(name, item)
40
41
  break
41
42
  case 'fts':
43
+ assert(dialect == 'postgres', `Full-text search queries are not supported by ${dialect}`)
42
44
  generateFtsTypes(name, item)
43
45
  break
44
46
  }
@@ -250,6 +252,8 @@ export function generateOpenCrudQueries(model: Model): string {
250
252
  if (graphqlType == 'String' || graphqlType == 'ID') {
251
253
  out.line(`${fieldName}_contains: ${graphqlType}`)
252
254
  out.line(`${fieldName}_not_contains: ${graphqlType}`)
255
+ out.line(`${fieldName}_containsInsensitive: ${graphqlType}`)
256
+ out.line(`${fieldName}_not_containsInsensitive: ${graphqlType}`)
253
257
  out.line(`${fieldName}_startsWith: ${graphqlType}`)
254
258
  out.line(`${fieldName}_not_startsWith: ${graphqlType}`)
255
259
  out.line(`${fieldName}_endsWith: ${graphqlType}`)
@@ -260,6 +264,11 @@ export function generateOpenCrudQueries(model: Model): string {
260
264
  out.line(`${fieldName}_in: [${graphqlType}!]`)
261
265
  out.line(`${fieldName}_not_in: [${graphqlType}!]`)
262
266
  }
267
+
268
+ if (graphqlType == 'JSON') {
269
+ out.line(`${fieldName}_jsonContains: ${graphqlType}`)
270
+ out.line(`${fieldName}_jsonHasKey: ${graphqlType}`)
271
+ }
263
272
  }
264
273
 
265
274
  function generateUnionType(name: string, union: Union) {
@@ -333,8 +342,8 @@ export function generateOpenCrudQueries(model: Model): string {
333
342
  }
334
343
 
335
344
 
336
- export function buildServerSchema(model: Model): DocumentNode {
337
- let scalars = scalars_list.map(name => 'scalar ' + name).join('\n')
338
- let queries = generateOpenCrudQueries(model)
345
+ export function buildServerSchema(model: Model, dialect: Dialect): DocumentNode {
346
+ let scalars = ['ID'].concat(Object.keys(customScalars)).map(name => 'scalar ' + name).join('\n')
347
+ let queries = generateOpenCrudQueries(model, dialect)
339
348
  return parse(scalars + '\n\n' + queries)
340
349
  }
@@ -0,0 +1,34 @@
1
+ import {GraphQLScalarType} from "graphql"
2
+ import {invalidFormat} from "../../util"
3
+
4
+
5
+ export const BigIntScalar = new GraphQLScalarType({
6
+ name: 'BigInt',
7
+ description: 'Big number integer',
8
+ serialize(value: number | string | bigint) {
9
+ return ''+value
10
+ },
11
+ parseValue(value: string) {
12
+ if (!isBigInt(value)) throw invalidFormat('BigInt', value)
13
+ return BigInt(value)
14
+ },
15
+ parseLiteral(ast) {
16
+ switch(ast.kind) {
17
+ case "StringValue":
18
+ if (isBigInt(ast.value)) {
19
+ return BigInt(ast.value)
20
+ } else {
21
+ throw invalidFormat('BigInt', ast.value)
22
+ }
23
+ case "IntValue":
24
+ return BigInt(ast.value)
25
+ default:
26
+ return null
27
+ }
28
+ }
29
+ })
30
+
31
+
32
+ function isBigInt(s: string): boolean {
33
+ return /^[+\-]?\d+$/.test(s)
34
+ }
@@ -0,0 +1,28 @@
1
+ import {decodeHex, isHex} from "@subsquid/util"
2
+ import {GraphQLScalarType} from "graphql"
3
+ import {invalidFormat} from "../../util"
4
+
5
+
6
+ export const BytesScalar = new GraphQLScalarType({
7
+ name: 'Bytes',
8
+ description: 'Binary data encoded as a hex string always prefixed with 0x',
9
+ serialize(value: string | Buffer) {
10
+ if (typeof value == 'string') {
11
+ if (!isHex(value)) throw invalidFormat('Bytes', value)
12
+ return value.toLowerCase()
13
+ } else {
14
+ return '0x' + value.toString('hex')
15
+ }
16
+ },
17
+ parseValue(value: string) {
18
+ return decodeHex(value)
19
+ },
20
+ parseLiteral(ast) {
21
+ switch(ast.kind) {
22
+ case "StringValue":
23
+ return decodeHex(ast.value)
24
+ default:
25
+ return null
26
+ }
27
+ }
28
+ })
@@ -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,7 @@
1
+ import {GraphQLScalarType} from "graphql"
2
+
3
+
4
+ export const JSONScalar = new GraphQLScalarType({
5
+ name: 'JSON',
6
+ description: 'A scalar that can represent any JSON value',
7
+ })
@@ -0,0 +1,12 @@
1
+ import {BigIntScalar} from "./BigInt"
2
+ import {BytesScalar} from "./Bytes"
3
+ import {DateTimeScalar} from "./DateTime"
4
+ import {JSONScalar} from "./JSON"
5
+
6
+
7
+ export const customScalars = {
8
+ BigInt: BigIntScalar,
9
+ Bytes: BytesScalar,
10
+ DateTime: DateTimeScalar,
11
+ JSON: JSONScalar,
12
+ }
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
 
package/src/orderBy.ts CHANGED
@@ -50,8 +50,10 @@ function buildOrderByMapping(model: Model, typeName: string, depth: number): Ope
50
50
  switch(propType.kind) {
51
51
  case 'scalar':
52
52
  case 'enum':
53
- m.set(key + '_ASC', {[key]: 'ASC'})
54
- m.set(key + '_DESC', {[key]: 'DESC'})
53
+ if (propType.name != 'JSON') {
54
+ m.set(key + '_ASC', {[key]: 'ASC'})
55
+ m.set(key + '_DESC', {[key]: 'DESC'})
56
+ }
55
57
  break
56
58
  case 'object':
57
59
  case 'union':
@@ -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,18 @@ 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
372
+ case 'jsonContains':
373
+ exps.push(`${lhs} @> ${this.param(arg)}`)
374
+ break
375
+ case 'jsonHasKey':
376
+ exps.push(`${lhs} ? ${this.param(arg)}`)
377
+ break
353
378
  default: {
354
379
  exps.push(`${lhs} ${whereOpToSqlOperator(op)} ${this.param(arg)}`)
355
380
  }
@@ -446,9 +471,15 @@ export class QueryBuilder {
446
471
  }
447
472
  break
448
473
  }
449
- case 'list-lookup':
450
- rec[req.alias] = this.toResult(row[req.index], req.children)
474
+ case 'list-lookup': {
475
+ let rows = row[req.index]
476
+ if (rows == null) {
477
+ rec[req.alias] = []
478
+ } else {
479
+ rec[req.alias] = this.toResult(row[req.index], req.children)
480
+ }
451
481
  break
482
+ }
452
483
  default:
453
484
  throw unsupportedCase((f as any).propType.kind)
454
485
  }
@@ -578,11 +609,12 @@ interface ListSubquery {
578
609
  * A pointer to an entity or nested json object within SQL query.
579
610
  *
580
611
  * It has convenience methods for building various SQL expressions
581
- * related to individual properties of an entity or an object it points to.
612
+ * related to individual properties of an entity or of an object it points to.
582
613
  */
583
614
  class Cursor {
584
615
  constructor(
585
616
  private model: Model,
617
+ private dialect: Dialect,
586
618
  private ident: (name: string) => string,
587
619
  private aliases: AliasSet,
588
620
  private join: JoinSet,
@@ -592,15 +624,45 @@ class Cursor {
592
624
  private prefix: string
593
625
  ) {}
594
626
 
595
- transport(propName: string): string {
627
+ output(propName: string): string {
596
628
  let prop = this.object.properties[propName]
597
629
  switch(prop.type.kind) {
598
630
  case 'scalar':
631
+ if (this.object.kind == 'object') {
632
+ switch(prop.type.name) {
633
+ case 'Int':
634
+ return `(${this.prefix}->'${propName}')::integer`
635
+ case 'Float':
636
+ return `(${this.prefix}->'${propName}')::numeric`
637
+ case 'Boolean':
638
+ return `(${this.prefix}->>'${propName}')::bool`
639
+ case 'JSON':
640
+ return `${this.prefix}->'${propName}'`
641
+ default:
642
+ return `${this.prefix}->>'${propName}'`
643
+ }
644
+ } else {
645
+ let exp = this.column(propName)
646
+ switch(prop.type.name) {
647
+ case 'BigInt':
648
+ return `(${exp})::text`
649
+ case 'Bytes':
650
+ return `'0x' || encode(${exp}, 'hex')`
651
+ case 'DateTime':
652
+ if (this.dialect == 'cockroach') {
653
+ return `experimental_strftime((${exp}) at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ')`
654
+ } else {
655
+ return `to_char((${exp}) at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`
656
+ }
657
+ default:
658
+ return exp
659
+ }
660
+ }
599
661
  case 'enum':
600
662
  if (this.object.kind == 'object') {
601
- return fromJsonToOutputCast(prop.type.name, this.prefix, propName)
663
+ return `${this.prefix}->>'${propName}'`
602
664
  } else {
603
- return toOutputCast(prop.type.name, this.column(propName))
665
+ return this.column(propName)
604
666
  }
605
667
  case 'list':
606
668
  let itemType = prop.type.item.type
@@ -608,7 +670,21 @@ class Cursor {
608
670
  // this is json
609
671
  return this.field(propName)
610
672
  } else {
611
- return toOutputArrayCast(itemType.name, this.column(propName))
673
+ let exp = this.column(propName)
674
+ switch(itemType.name) {
675
+ case 'BigInt':
676
+ return `(${exp})::text[]`
677
+ case 'Bytes':
678
+ return `array(select '0x' || encode(i, 'hex') from unnest(${exp}) as i)`
679
+ case 'DateTime':
680
+ if (this.dialect == 'cockroach') {
681
+ return `array(select experimental_strftime(i at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ') from unnest(${exp}) as i)`
682
+ } else {
683
+ return `array(select to_char(i at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') from unnest(${exp}) as i)`
684
+ }
685
+ default:
686
+ return exp
687
+ }
612
688
  }
613
689
  default:
614
690
  throw unsupportedCase(prop.type.kind)
@@ -624,7 +700,24 @@ class Cursor {
624
700
  }
625
701
  assert(prop.type.kind == 'scalar' || prop.type.kind == 'enum')
626
702
  if (this.object.kind == 'object') {
627
- return fromJsonCast(prop.type.name, this.prefix, propName)
703
+ let js = `${this.prefix}->'${propName}'`
704
+ let str = `${this.prefix}->>'${propName}'`
705
+ switch(prop.type.name) {
706
+ case 'Int':
707
+ return `(${js})::integer`
708
+ case 'Float':
709
+ return `(${js})::numeric`
710
+ case 'Boolean':
711
+ return `(${str})::bool`
712
+ case 'BigInt':
713
+ return `(${str})::numeric`
714
+ case 'Bytes':
715
+ return `decode(substr(${str}, 3), 'hex')`
716
+ case 'DateTime':
717
+ return `(${str})::timestamptz`
718
+ default:
719
+ return str
720
+ }
628
721
  } else {
629
722
  return this.column(propName)
630
723
  }
@@ -676,6 +769,7 @@ class Cursor {
676
769
 
677
770
  return new Cursor(
678
771
  this.model,
772
+ this.dialect,
679
773
  this.ident,
680
774
  this.aliases,
681
775
  this.join,
@@ -702,7 +796,7 @@ class Cursor {
702
796
  fk(propName: string): string {
703
797
  return this.object.kind == 'entity'
704
798
  ? this.ident(this.alias) + '.' + this.ident(toFkColumn(propName))
705
- : fromJsonCast('ID', this.prefix, propName)
799
+ : `${this.prefix}->>'${propName}'`
706
800
  }
707
801
 
708
802
  tsv(queryName: string): string {