@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.
- package/README.md +1 -1
- package/dist/dialect.d.ts +2 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +3 -0
- package/dist/dialect.js.map +1 -0
- package/dist/gql/opencrud.d.ts +4 -3
- package/dist/gql/opencrud.d.ts.map +1 -1
- package/dist/gql/opencrud.js +12 -5
- package/dist/gql/opencrud.js.map +1 -1
- package/dist/gql/scalars/BigInt.d.ts +3 -0
- package/dist/gql/scalars/BigInt.d.ts.map +1 -0
- package/dist/gql/scalars/BigInt.js +36 -0
- package/dist/gql/scalars/BigInt.js.map +1 -0
- package/dist/gql/scalars/Bytes.d.ts +3 -0
- package/dist/gql/scalars/Bytes.d.ts.map +1 -0
- package/dist/gql/scalars/Bytes.js +32 -0
- package/dist/gql/scalars/Bytes.js.map +1 -0
- package/dist/gql/scalars/DateTime.d.ts +3 -0
- package/dist/gql/scalars/DateTime.d.ts.map +1 -0
- package/dist/gql/scalars/DateTime.js +44 -0
- package/dist/gql/scalars/DateTime.js.map +1 -0
- package/dist/gql/scalars/JSON.d.ts +3 -0
- package/dist/gql/scalars/JSON.d.ts.map +1 -0
- package/dist/gql/scalars/JSON.js +9 -0
- package/dist/gql/scalars/JSON.js.map +1 -0
- package/dist/gql/scalars/index.d.ts +7 -0
- package/dist/gql/scalars/index.d.ts.map +1 -0
- package/dist/gql/scalars/index.js +14 -0
- package/dist/gql/scalars/index.js.map +1 -0
- package/dist/gql/schema.d.ts.map +1 -1
- package/dist/gql/schema.js +3 -2
- package/dist/gql/schema.js.map +1 -1
- package/dist/orderBy.d.ts.map +1 -1
- package/dist/orderBy.js +4 -2
- package/dist/orderBy.js.map +1 -1
- package/dist/queryBuilder.d.ts +3 -1
- package/dist/queryBuilder.d.ts.map +1 -1
- package/dist/queryBuilder.js +121 -24
- package/dist/queryBuilder.js.map +1 -1
- package/dist/resolver.d.ts +2 -1
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +12 -12
- package/dist/resolver.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/test/basic.test.js +3 -3
- package/dist/test/basic.test.js.map +1 -1
- package/dist/test/connection.test.js +1 -1
- package/dist/test/connection.test.js.map +1 -1
- package/dist/test/fts.test.js +2 -2
- package/dist/test/fts.test.js.map +1 -1
- package/dist/test/isNull.test.js +1 -1
- package/dist/test/isNull.test.js.map +1 -1
- package/dist/test/lists.test.js +3 -3
- package/dist/test/lists.test.js.map +1 -1
- package/dist/test/lookup.test.js +13 -7
- package/dist/test/lookup.test.js.map +1 -1
- package/dist/test/regressions.test.d.ts +2 -0
- package/dist/test/regressions.test.d.ts.map +1 -0
- package/dist/test/regressions.test.js +39 -0
- package/dist/test/regressions.test.js.map +1 -0
- package/dist/test/scalars.test.js +47 -10
- package/dist/test/scalars.test.js.map +1 -1
- package/dist/test/{util/setup.d.ts → setup.d.ts} +8 -1
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/{util/setup.js → setup.js} +19 -8
- package/dist/test/setup.js.map +1 -0
- package/dist/test/typed-json.test.js +16 -2
- package/dist/test/typed-json.test.js.map +1 -1
- package/dist/test/unions.test.js +1 -1
- package/dist/test/unions.test.js.map +1 -1
- package/dist/test/where.test.js +1 -1
- package/dist/test/where.test.js.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +5 -1
- package/dist/util.js.map +1 -1
- package/dist/where.d.ts +1 -1
- package/dist/where.d.ts.map +1 -1
- package/dist/where.js +5 -1
- package/dist/where.js.map +1 -1
- package/package.json +5 -5
- package/src/dialect.ts +2 -0
- package/src/gql/opencrud.ts +15 -6
- package/src/gql/scalars/BigInt.ts +34 -0
- package/src/gql/scalars/Bytes.ts +28 -0
- package/src/gql/scalars/DateTime.ts +45 -0
- package/src/gql/scalars/JSON.ts +7 -0
- package/src/gql/scalars/index.ts +12 -0
- package/src/gql/schema.ts +3 -2
- package/src/orderBy.ts +4 -2
- package/src/queryBuilder.ts +114 -20
- package/src/resolver.ts +13 -11
- package/src/server.ts +5 -2
- package/src/test/basic.test.ts +3 -3
- package/src/test/connection.test.ts +1 -1
- package/src/test/fts.test.ts +4 -2
- package/src/test/isNull.test.ts +1 -1
- package/src/test/lists.test.ts +3 -3
- package/src/test/lookup.test.ts +13 -7
- package/src/test/regressions.test.ts +39 -0
- package/src/test/scalars.test.ts +49 -10
- package/src/test/{util/setup.ts → setup.ts} +21 -7
- package/src/test/typed-json.test.ts +17 -2
- package/src/test/unions.test.ts +1 -1
- package/src/test/where.test.ts +1 -1
- package/src/util.ts +5 -0
- package/src/where.ts +9 -2
- package/dist/scalars.d.ts +0 -36
- package/dist/scalars.d.ts.map +0 -1
- package/dist/scalars.js +0 -229
- package/dist/scalars.js.map +0 -1
- package/dist/test/util/setup.d.ts.map +0 -1
- package/dist/test/util/setup.js.map +0 -1
- 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":";;;
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
package/src/gql/opencrud.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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,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 {
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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':
|
package/src/queryBuilder.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
663
|
+
return `${this.prefix}->>'${propName}'`
|
|
602
664
|
} else {
|
|
603
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
799
|
+
: `${this.prefix}->>'${propName}'`
|
|
706
800
|
}
|
|
707
801
|
|
|
708
802
|
tsv(queryName: string): string {
|