@subsquid/openreader 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/context.d.ts +5 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/ir/args.d.ts +1 -1
- package/lib/ir/args.d.ts.map +1 -1
- package/lib/ir/connection.d.ts +3 -4
- package/lib/ir/connection.d.ts.map +1 -1
- package/lib/ir/connection.js.map +1 -1
- package/lib/ir/fields.d.ts +6 -2
- package/lib/ir/fields.d.ts.map +1 -1
- package/lib/ir/fields.js +15 -0
- package/lib/ir/fields.js.map +1 -1
- package/lib/limit.size.d.ts +7 -2
- package/lib/limit.size.d.ts.map +1 -1
- package/lib/limit.size.js +106 -12
- package/lib/limit.size.js.map +1 -1
- package/lib/main.js +6 -9
- package/lib/main.js.map +1 -1
- package/lib/model.d.ts +3 -1
- package/lib/model.d.ts.map +1 -1
- package/lib/model.schema.d.ts +2 -2
- package/lib/model.schema.d.ts.map +1 -1
- package/lib/model.schema.js +29 -7
- package/lib/model.schema.js.map +1 -1
- package/lib/model.tools.d.ts +6 -1
- package/lib/model.tools.d.ts.map +1 -1
- package/lib/model.tools.js +111 -8
- package/lib/model.tools.js.map +1 -1
- package/lib/opencrud/orderBy.d.ts +2 -2
- package/lib/opencrud/orderBy.d.ts.map +1 -1
- package/lib/opencrud/orderBy.js +13 -17
- package/lib/opencrud/orderBy.js.map +1 -1
- package/lib/opencrud/schema.d.ts +4 -4
- package/lib/opencrud/schema.d.ts.map +1 -1
- package/lib/opencrud/schema.js +60 -64
- package/lib/opencrud/schema.js.map +1 -1
- package/lib/opencrud/tree.d.ts +9 -7
- package/lib/opencrud/tree.d.ts.map +1 -1
- package/lib/opencrud/tree.js +32 -14
- package/lib/opencrud/tree.js.map +1 -1
- package/lib/server.d.ts +16 -12
- package/lib/server.d.ts.map +1 -1
- package/lib/server.js +29 -4
- package/lib/server.js.map +1 -1
- package/lib/sql/cursor.js +2 -2
- package/lib/sql/cursor.js.map +1 -1
- package/lib/sql/mapping.d.ts +3 -1
- package/lib/sql/mapping.d.ts.map +1 -1
- package/lib/sql/mapping.js +16 -1
- package/lib/sql/mapping.js.map +1 -1
- package/lib/sql/printer.d.ts +29 -11
- package/lib/sql/printer.d.ts.map +1 -1
- package/lib/sql/printer.js +106 -10
- package/lib/sql/printer.js.map +1 -1
- package/lib/sql/query.d.ts +11 -11
- package/lib/sql/query.d.ts.map +1 -1
- package/lib/sql/query.js +41 -19
- package/lib/sql/query.js.map +1 -1
- package/lib/test/limits.test.d.ts +2 -0
- package/lib/test/limits.test.d.ts.map +1 -0
- package/lib/test/limits.test.js +159 -0
- package/lib/test/limits.test.js.map +1 -0
- package/lib/test/queryable.test.d.ts +2 -0
- package/lib/test/queryable.test.d.ts.map +1 -0
- package/lib/test/queryable.test.js +255 -0
- package/lib/test/queryable.test.js.map +1 -0
- package/lib/test/setup.d.ts +2 -1
- package/lib/test/setup.d.ts.map +1 -1
- package/lib/test/setup.js +5 -2
- package/lib/test/setup.js.map +1 -1
- package/lib/util/execute.d.ts +5 -0
- package/lib/util/execute.d.ts.map +1 -0
- package/lib/util/execute.js +28 -0
- package/lib/util/execute.js.map +1 -0
- package/lib/util/limit.d.ts +11 -0
- package/lib/util/limit.d.ts.map +1 -0
- package/lib/util/limit.js +39 -0
- package/lib/util/limit.js.map +1 -0
- package/package.json +3 -3
- package/src/context.ts +5 -2
- package/src/ir/args.ts +1 -1
- package/src/ir/connection.ts +3 -4
- package/src/ir/fields.ts +22 -2
- package/src/limit.size.ts +122 -13
- package/src/main.ts +18 -20
- package/src/model.schema.ts +40 -13
- package/src/model.tools.ts +121 -8
- package/src/model.ts +3 -1
- package/src/opencrud/orderBy.ts +13 -17
- package/src/opencrud/schema.ts +86 -85
- package/src/opencrud/tree.ts +55 -26
- package/src/server.ts +66 -26
- package/src/sql/cursor.ts +2 -2
- package/src/sql/mapping.ts +18 -1
- package/src/sql/printer.ts +137 -21
- package/src/sql/query.ts +50 -30
- package/src/test/limits.test.ts +163 -0
- package/src/test/queryable.test.ts +258 -0
- package/src/test/setup.ts +6 -3
- package/src/util/execute.ts +53 -0
- package/src/util/limit.ts +34 -0
package/src/opencrud/tree.ts
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import {unexpectedCase} from
|
|
2
|
-
import assert from
|
|
3
|
-
import {GraphQLSchema} from
|
|
4
|
-
import {ResolveTree} from
|
|
5
|
-
import {
|
|
6
|
-
import {FieldRequest, OpaqueRequest} from
|
|
7
|
-
import {Model} from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import {unexpectedCase} from '@subsquid/util-internal'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import {GraphQLSchema} from 'graphql'
|
|
4
|
+
import {ResolveTree} from 'graphql-parse-resolve-info'
|
|
5
|
+
import {SqlArguments} from '../ir/args'
|
|
6
|
+
import {AnyFields, FieldRequest, FieldsByEntity, OpaqueRequest} from '../ir/fields'
|
|
7
|
+
import {Model} from '../model'
|
|
8
|
+
import {getQueryableEntities} from '../model.tools'
|
|
9
|
+
import {simplifyResolveTree} from '../util/resolve-tree'
|
|
10
|
+
import {ensureArray} from '../util/util'
|
|
11
|
+
import {parseOrderBy} from './orderBy'
|
|
12
|
+
import {parseWhere} from './where'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function parseObjectTree(
|
|
15
16
|
model: Model,
|
|
16
|
-
|
|
17
|
+
typeName: string,
|
|
17
18
|
schema: GraphQLSchema,
|
|
18
19
|
tree: ResolveTree
|
|
19
20
|
): FieldRequest[] {
|
|
20
21
|
|
|
21
22
|
let requests: FieldRequest[] = []
|
|
22
23
|
let requestedScalars: Record<string, true> = {}
|
|
23
|
-
let object = model[
|
|
24
|
+
let object = model[typeName]
|
|
24
25
|
assert(object.kind == "entity" || object.kind == "object")
|
|
25
26
|
|
|
26
|
-
let fields = simplifyResolveTree(schema, tree,
|
|
27
|
+
let fields = simplifyResolveTree(schema, tree, typeName).fields
|
|
27
28
|
for (let alias in fields) {
|
|
28
29
|
let f = fields[alias]
|
|
29
30
|
let prop = object.properties[f.name]
|
|
@@ -51,7 +52,7 @@ export function parseResolveTree(
|
|
|
51
52
|
type: prop.type,
|
|
52
53
|
prop,
|
|
53
54
|
index: 0,
|
|
54
|
-
children:
|
|
55
|
+
children: parseObjectTree(model, prop.type.name, schema, f)
|
|
55
56
|
})
|
|
56
57
|
break
|
|
57
58
|
case "union": {
|
|
@@ -59,7 +60,7 @@ export function parseResolveTree(
|
|
|
59
60
|
assert(union.kind == "union")
|
|
60
61
|
let children: FieldRequest[] = []
|
|
61
62
|
for (let variant of union.variants) {
|
|
62
|
-
for (let req of
|
|
63
|
+
for (let req of parseObjectTree(model, variant, schema, f)) {
|
|
63
64
|
req.ifType = variant
|
|
64
65
|
children.push(req)
|
|
65
66
|
}
|
|
@@ -83,7 +84,7 @@ export function parseResolveTree(
|
|
|
83
84
|
type: prop.type,
|
|
84
85
|
prop,
|
|
85
86
|
index: 0,
|
|
86
|
-
children:
|
|
87
|
+
children: parseObjectTree(model, prop.type.entity, schema, f)
|
|
87
88
|
})
|
|
88
89
|
break
|
|
89
90
|
case "lookup":
|
|
@@ -94,7 +95,7 @@ export function parseResolveTree(
|
|
|
94
95
|
type: prop.type,
|
|
95
96
|
prop,
|
|
96
97
|
index: 0,
|
|
97
|
-
children:
|
|
98
|
+
children: parseObjectTree(model, prop.type.entity, schema, f)
|
|
98
99
|
})
|
|
99
100
|
break
|
|
100
101
|
case "list-lookup":
|
|
@@ -105,8 +106,8 @@ export function parseResolveTree(
|
|
|
105
106
|
type: prop.type,
|
|
106
107
|
prop,
|
|
107
108
|
index: 0,
|
|
108
|
-
args:
|
|
109
|
-
children:
|
|
109
|
+
args: parseSqlArguments(model, prop.type.entity, f.args),
|
|
110
|
+
children: parseObjectTree(model, prop.type.entity, schema, f)
|
|
110
111
|
})
|
|
111
112
|
break
|
|
112
113
|
default:
|
|
@@ -118,8 +119,8 @@ export function parseResolveTree(
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
|
|
121
|
-
export function
|
|
122
|
-
let args:
|
|
122
|
+
export function parseSqlArguments(model: Model, typeName: string, gqlArgs: any): SqlArguments {
|
|
123
|
+
let args: SqlArguments = {}
|
|
123
124
|
|
|
124
125
|
let where = parseWhere(gqlArgs.where)
|
|
125
126
|
if (where) {
|
|
@@ -127,7 +128,7 @@ export function parseEntityListArguments(model: Model, entityName: string, gqlAr
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
if (gqlArgs.orderBy) {
|
|
130
|
-
args.orderBy = parseOrderBy(model,
|
|
131
|
+
args.orderBy = parseOrderBy(model, typeName, ensureArray(gqlArgs.orderBy))
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
if (gqlArgs.offset) {
|
|
@@ -142,3 +143,31 @@ export function parseEntityListArguments(model: Model, entityName: string, gqlAr
|
|
|
142
143
|
|
|
143
144
|
return args
|
|
144
145
|
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
export function parseQueryableTree(
|
|
149
|
+
model: Model,
|
|
150
|
+
queryableName: string,
|
|
151
|
+
schema: GraphQLSchema,
|
|
152
|
+
tree: ResolveTree
|
|
153
|
+
): FieldsByEntity {
|
|
154
|
+
let fields: FieldsByEntity = {}
|
|
155
|
+
for (let entity of getQueryableEntities(model, queryableName)) {
|
|
156
|
+
fields[entity] = parseObjectTree(model, entity, schema, tree)
|
|
157
|
+
}
|
|
158
|
+
return fields
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
export function parseAnyTree(
|
|
163
|
+
model: Model,
|
|
164
|
+
typeName: string,
|
|
165
|
+
schema: GraphQLSchema,
|
|
166
|
+
tree: ResolveTree
|
|
167
|
+
): AnyFields {
|
|
168
|
+
if (model[typeName].kind == 'interface') {
|
|
169
|
+
return parseQueryableTree(model, typeName, schema, tree)
|
|
170
|
+
} else {
|
|
171
|
+
return parseObjectTree(model, typeName, schema, tree)
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import type {Logger} from
|
|
2
|
-
import {listen, ListeningServer} from
|
|
3
|
-
import {PluginDefinition} from
|
|
4
|
-
import {ApolloServer} from
|
|
5
|
-
import express from
|
|
6
|
-
import fs from
|
|
7
|
-
import {GraphQLSchema} from
|
|
8
|
-
import {useServer as useWsServer} from
|
|
9
|
-
import http from
|
|
10
|
-
import path from
|
|
11
|
-
import type {Pool} from
|
|
12
|
-
import {WebSocketServer} from
|
|
13
|
-
import {Context} from
|
|
14
|
-
import {PoolOpenreaderContext} from
|
|
15
|
-
import type {Dialect} from
|
|
16
|
-
import type {Model} from
|
|
17
|
-
import {SchemaBuilder} from
|
|
18
|
-
import {logGraphQLError} from
|
|
1
|
+
import type {Logger} from '@subsquid/logger'
|
|
2
|
+
import {listen, ListeningServer} from '@subsquid/util-internal-http-server'
|
|
3
|
+
import {PluginDefinition} from 'apollo-server-core'
|
|
4
|
+
import {ApolloServer} from 'apollo-server-express'
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
import {ExecutionArgs, GraphQLSchema} from 'graphql'
|
|
8
|
+
import {useServer as useWsServer} from 'graphql-ws/lib/use/ws'
|
|
9
|
+
import http from 'http'
|
|
10
|
+
import path from 'path'
|
|
11
|
+
import type {Pool} from 'pg'
|
|
12
|
+
import {WebSocketServer} from 'ws'
|
|
13
|
+
import {Context, OpenreaderContext} from './context'
|
|
14
|
+
import {PoolOpenreaderContext} from './db'
|
|
15
|
+
import type {Dialect} from './dialect'
|
|
16
|
+
import type {Model} from './model'
|
|
17
|
+
import {SchemaBuilder} from './opencrud/schema'
|
|
18
|
+
import {logGraphQLError} from './util/error-handling'
|
|
19
|
+
import {executeWithLimit} from './util/execute'
|
|
20
|
+
import {ResponseSizeLimit} from './util/limit'
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
export interface ServerOptions {
|
|
@@ -23,25 +25,46 @@ export interface ServerOptions {
|
|
|
23
25
|
model: Model
|
|
24
26
|
connection: Pool
|
|
25
27
|
dialect?: Dialect
|
|
26
|
-
subscriptions?: boolean
|
|
27
|
-
subscriptionPollInterval?: number
|
|
28
|
-
subscriptionConnection?: Pool
|
|
29
28
|
graphiqlConsole?: boolean
|
|
30
29
|
log?: Logger
|
|
31
30
|
maxRequestSizeBytes?: number
|
|
31
|
+
maxRootFields?: number
|
|
32
|
+
maxResponseNodes?: number
|
|
33
|
+
subscriptions?: boolean
|
|
34
|
+
subscriptionPollInterval?: number
|
|
35
|
+
subscriptionConnection?: Pool
|
|
36
|
+
subscriptionMaxResponseNodes?: number
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
export async function serve(options: ServerOptions): Promise<ListeningServer> {
|
|
36
|
-
let {connection, subscriptionConnection, subscriptionPollInterval} = options
|
|
41
|
+
let {connection, subscriptionConnection, subscriptionPollInterval, maxResponseNodes, subscriptionMaxResponseNodes} = options
|
|
37
42
|
let dialect = options.dialect ?? 'postgres'
|
|
38
43
|
|
|
39
44
|
let schema = new SchemaBuilder(options).build()
|
|
45
|
+
|
|
40
46
|
let context = () => {
|
|
47
|
+
let openreader: OpenreaderContext = new PoolOpenreaderContext(
|
|
48
|
+
dialect,
|
|
49
|
+
connection,
|
|
50
|
+
subscriptionConnection,
|
|
51
|
+
subscriptionPollInterval
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (maxResponseNodes) {
|
|
55
|
+
openreader.responseSizeLimit = new ResponseSizeLimit(maxResponseNodes)
|
|
56
|
+
openreader.subscriptionResponseSizeLimit = new ResponseSizeLimit(maxResponseNodes)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (subscriptionMaxResponseNodes) {
|
|
60
|
+
openreader.subscriptionResponseSizeLimit = new ResponseSizeLimit(subscriptionMaxResponseNodes)
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
return {
|
|
42
|
-
openreader
|
|
64
|
+
openreader
|
|
43
65
|
}
|
|
44
66
|
}
|
|
67
|
+
|
|
45
68
|
let disposals: Dispose[] = []
|
|
46
69
|
|
|
47
70
|
return addServerCleanup(disposals, runApollo({
|
|
@@ -52,7 +75,8 @@ export async function serve(options: ServerOptions): Promise<ListeningServer> {
|
|
|
52
75
|
subscriptions: options.subscriptions,
|
|
53
76
|
log: options.log,
|
|
54
77
|
graphiqlConsole: options.graphiqlConsole,
|
|
55
|
-
maxRequestSizeBytes: options.maxRequestSizeBytes
|
|
78
|
+
maxRequestSizeBytes: options.maxRequestSizeBytes,
|
|
79
|
+
maxRootFields: options.maxRootFields
|
|
56
80
|
}), options.log)
|
|
57
81
|
}
|
|
58
82
|
|
|
@@ -70,15 +94,21 @@ export interface ApolloOptions {
|
|
|
70
94
|
graphiqlConsole?: boolean
|
|
71
95
|
log?: Logger
|
|
72
96
|
maxRequestSizeBytes?: number
|
|
97
|
+
maxRootFields?: number
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
|
|
76
101
|
export async function runApollo(options: ApolloOptions): Promise<ListeningServer> {
|
|
77
|
-
|
|
102
|
+
const {disposals, context, schema, log, maxRootFields} = options
|
|
103
|
+
|
|
78
104
|
let maxRequestSizeBytes = options.maxRequestSizeBytes ?? 256 * 1024
|
|
79
105
|
let app = express()
|
|
80
106
|
let server = http.createServer(app)
|
|
81
107
|
|
|
108
|
+
const execute = maxRootFields
|
|
109
|
+
? (args: ExecutionArgs) => executeWithLimit(maxRootFields, args)
|
|
110
|
+
: undefined
|
|
111
|
+
|
|
82
112
|
if (options.subscriptions) {
|
|
83
113
|
let wsServer = new WebSocketServer({
|
|
84
114
|
server,
|
|
@@ -89,6 +119,7 @@ export async function runApollo(options: ApolloOptions): Promise<ListeningServer
|
|
|
89
119
|
{
|
|
90
120
|
schema,
|
|
91
121
|
context,
|
|
122
|
+
execute,
|
|
92
123
|
onError(ctx, message, errors) {
|
|
93
124
|
if (log) {
|
|
94
125
|
// FIXME: we don't want to log client errors
|
|
@@ -111,6 +142,16 @@ export async function runApollo(options: ApolloOptions): Promise<ListeningServer
|
|
|
111
142
|
schema,
|
|
112
143
|
context,
|
|
113
144
|
stopOnTerminationSignals: false,
|
|
145
|
+
executor: execute && (async req => {
|
|
146
|
+
return execute({
|
|
147
|
+
schema,
|
|
148
|
+
document: req.document,
|
|
149
|
+
rootValue: {},
|
|
150
|
+
contextValue: req.context,
|
|
151
|
+
variableValues: req.request.variables,
|
|
152
|
+
operationName: req.operationName
|
|
153
|
+
})
|
|
154
|
+
}),
|
|
114
155
|
plugins: [
|
|
115
156
|
...options.plugins || [],
|
|
116
157
|
{
|
|
@@ -136,7 +177,6 @@ export async function runApollo(options: ApolloOptions): Promise<ListeningServer
|
|
|
136
177
|
setupGraphiqlConsole(app)
|
|
137
178
|
}
|
|
138
179
|
|
|
139
|
-
|
|
140
180
|
await apollo.start()
|
|
141
181
|
disposals.push(() => apollo.stop())
|
|
142
182
|
|
package/src/sql/cursor.ts
CHANGED
|
@@ -154,7 +154,7 @@ export class EntityCursor implements Cursor {
|
|
|
154
154
|
case "fk":
|
|
155
155
|
return new EntityCursor(
|
|
156
156
|
this.ctx,
|
|
157
|
-
prop.type.
|
|
157
|
+
prop.type.entity,
|
|
158
158
|
{on: 'id', rhs: this.native(field)}
|
|
159
159
|
)
|
|
160
160
|
case "lookup":
|
|
@@ -281,7 +281,7 @@ export class ObjectCursor implements Cursor {
|
|
|
281
281
|
case "fk":
|
|
282
282
|
return new EntityCursor(
|
|
283
283
|
this.ctx,
|
|
284
|
-
prop.type.
|
|
284
|
+
prop.type.entity,
|
|
285
285
|
{on: 'id', rhs: this.string(field)}
|
|
286
286
|
)
|
|
287
287
|
default:
|
package/src/sql/mapping.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {unexpectedCase} from "@subsquid/util-internal"
|
|
2
|
-
import {FieldRequest} from
|
|
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
|
+
}
|
package/src/sql/printer.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {unexpectedCase} from
|
|
2
|
-
import assert from
|
|
3
|
-
import {Dialect} from
|
|
4
|
-
import {
|
|
5
|
-
import {FieldRequest} from
|
|
6
|
-
import {Model} from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import {unexpectedCase} from '@subsquid/util-internal'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import {Dialect} from '../dialect'
|
|
4
|
+
import {OrderBy, SortOrder, SqlArguments, Where} from '../ir/args'
|
|
5
|
+
import {FieldRequest, FieldsByEntity} from '../ir/fields'
|
|
6
|
+
import {Model} from '../model'
|
|
7
|
+
import {getQueryableEntities} from '../model.tools'
|
|
8
|
+
import {Cursor, EntityCursor} from './cursor'
|
|
9
|
+
import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from './util'
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
export class
|
|
12
|
+
export class EntitySqlPrinter {
|
|
12
13
|
private aliases: AliasSet
|
|
13
14
|
private join: JoinSet
|
|
14
15
|
private root: EntityCursor
|
|
@@ -19,9 +20,9 @@ export class EntityListQueryPrinter {
|
|
|
19
20
|
constructor(
|
|
20
21
|
private model: Model,
|
|
21
22
|
private dialect: Dialect,
|
|
22
|
-
|
|
23
|
+
public readonly entityName: string,
|
|
23
24
|
private params: unknown[],
|
|
24
|
-
private args:
|
|
25
|
+
private args: SqlArguments = {},
|
|
25
26
|
fields?: FieldRequest[],
|
|
26
27
|
aliases?: AliasSet
|
|
27
28
|
) {
|
|
@@ -43,12 +44,14 @@ export class EntityListQueryPrinter {
|
|
|
43
44
|
this.populateWhere(this.root, args.where, this.where)
|
|
44
45
|
}
|
|
45
46
|
if (args.orderBy) {
|
|
46
|
-
this.
|
|
47
|
+
this.traverseOrderBy(args.orderBy, (field, cursor, order) => {
|
|
48
|
+
this.orderBy.push(cursor.native(field) + ' ' + order)
|
|
49
|
+
})
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
private sub(entityName: string, args?:
|
|
51
|
-
return new
|
|
53
|
+
private sub(entityName: string, args?: SqlArguments, fields?: FieldRequest[]): EntitySqlPrinter {
|
|
54
|
+
return new EntitySqlPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
private populateColumns(cursor: Cursor, fields: FieldRequest[]): void {
|
|
@@ -128,7 +131,9 @@ export class EntityListQueryPrinter {
|
|
|
128
131
|
break
|
|
129
132
|
}
|
|
130
133
|
case "isNull": {
|
|
131
|
-
let f = cursor.
|
|
134
|
+
let f = cursor.prop(where.field).type.kind == 'lookup'
|
|
135
|
+
? cursor.child(where.field).ref('id')
|
|
136
|
+
: cursor.ref(where.field)
|
|
132
137
|
if (where.yes) {
|
|
133
138
|
exps.push(`${f} IS NULL`)
|
|
134
139
|
} else {
|
|
@@ -261,13 +266,17 @@ export class EntityListQueryPrinter {
|
|
|
261
266
|
return printClause("AND", exps)
|
|
262
267
|
}
|
|
263
268
|
|
|
264
|
-
|
|
269
|
+
traverseOrderBy(orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
|
|
270
|
+
this.visitOrderBy(this.root, orderBy, cb)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private visitOrderBy(cursor: Cursor, orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
|
|
265
274
|
for (let field in orderBy) {
|
|
266
275
|
let spec = orderBy[field]
|
|
267
276
|
if (typeof spec == "string") {
|
|
268
|
-
|
|
277
|
+
cb(field, cursor, spec)
|
|
269
278
|
} else {
|
|
270
|
-
this.
|
|
279
|
+
this.visitOrderBy(cursor.child(field), spec, cb)
|
|
271
280
|
}
|
|
272
281
|
}
|
|
273
282
|
}
|
|
@@ -280,14 +289,18 @@ export class EntityListQueryPrinter {
|
|
|
280
289
|
return escapeIdentifier(this.dialect, name)
|
|
281
290
|
}
|
|
282
291
|
|
|
283
|
-
addWhereDerivedFrom(field: string, parentIdExp: string): this {
|
|
292
|
+
private addWhereDerivedFrom(field: string, parentIdExp: string): this {
|
|
284
293
|
this.where.push(`${this.root.native(field)} = ${parentIdExp}`)
|
|
285
294
|
return this
|
|
286
295
|
}
|
|
287
296
|
|
|
297
|
+
hasColumns(): boolean {
|
|
298
|
+
return this.columns.size() > 0
|
|
299
|
+
}
|
|
300
|
+
|
|
288
301
|
printColumnList(options?: {withAliases?: boolean}): string {
|
|
302
|
+
assert(this.hasColumns())
|
|
289
303
|
let names = this.columns.names()
|
|
290
|
-
assert(names.length > 0)
|
|
291
304
|
if (options?.withAliases) {
|
|
292
305
|
names = names.map((name, idx) => `${name} AS _c${idx}`)
|
|
293
306
|
}
|
|
@@ -322,7 +335,110 @@ export class EntityListQueryPrinter {
|
|
|
322
335
|
return `SELECT ${this.printColumnList({withAliases: true})} ${this.printFrom()}`
|
|
323
336
|
}
|
|
324
337
|
|
|
325
|
-
|
|
338
|
+
printAsCount(): string {
|
|
339
|
+
if (this.args.offset || this.args.limit) {
|
|
340
|
+
return `SELECT count(*) FROM (SELECT true ${this.printFrom()}) AS rows`
|
|
341
|
+
} else {
|
|
342
|
+
return `SELECT count(*) ${this.printFrom()}`
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private printAsJsonRows(): string {
|
|
326
347
|
return `SELECT ${this.printColumnListAsJsonArray()} AS row ${this.printFrom()}`
|
|
327
348
|
}
|
|
328
349
|
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
export class QueryableSqlPrinter {
|
|
353
|
+
private printers: EntitySqlPrinter[] = []
|
|
354
|
+
private orders: SortOrder[] = []
|
|
355
|
+
private orderColumns: string[][] = []
|
|
356
|
+
|
|
357
|
+
constructor(
|
|
358
|
+
private model: Model,
|
|
359
|
+
private dialect: Dialect,
|
|
360
|
+
private queryableName: string,
|
|
361
|
+
private params: unknown[],
|
|
362
|
+
private args: SqlArguments = {},
|
|
363
|
+
fields?: FieldsByEntity
|
|
364
|
+
) {
|
|
365
|
+
for (let entityName of getQueryableEntities(this.model, this.queryableName)) {
|
|
366
|
+
let entityFields = fields?.[entityName]
|
|
367
|
+
|
|
368
|
+
let printer = new EntitySqlPrinter(
|
|
369
|
+
model,
|
|
370
|
+
dialect,
|
|
371
|
+
entityName,
|
|
372
|
+
this.params,
|
|
373
|
+
{where: args.where},
|
|
374
|
+
entityFields
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if (this.args.orderBy) {
|
|
378
|
+
let cols: string[] = []
|
|
379
|
+
this.orders.length = 0
|
|
380
|
+
printer.traverseOrderBy(this.args.orderBy, (field, cursor, order) => {
|
|
381
|
+
let col = field == '_type' ? `'${entityName}'` : cursor.native(field)
|
|
382
|
+
this.orders.push(order)
|
|
383
|
+
cols.push(`${col} AS o${this.orders.length}`)
|
|
384
|
+
})
|
|
385
|
+
this.orderColumns.push(cols)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.printers.push(printer)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
print(): string {
|
|
393
|
+
let from = this.printers.map((printer, idx) => {
|
|
394
|
+
let cols: string[] = []
|
|
395
|
+
cols.push(`'${printer.entityName}' AS e`)
|
|
396
|
+
if (printer.hasColumns()) {
|
|
397
|
+
cols.push(printer.printColumnListAsJsonArray() + ' AS d')
|
|
398
|
+
} else {
|
|
399
|
+
cols.push('null AS d')
|
|
400
|
+
}
|
|
401
|
+
cols.push(...this.orderColumns[idx])
|
|
402
|
+
return `SELECT ${cols.join(', ')} ${printer.printFrom()}`
|
|
403
|
+
}).join('\nUNION ALL\n')
|
|
404
|
+
|
|
405
|
+
let args = this.printArgs()
|
|
406
|
+
if (args) {
|
|
407
|
+
return `SELECT e, d FROM (\n${from}\n) AS rows` + args
|
|
408
|
+
} else {
|
|
409
|
+
return from
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
printAsCount(): string {
|
|
414
|
+
let union = this.orders.length
|
|
415
|
+
? this.printers.map((printer, idx) => {
|
|
416
|
+
return `SELECT ${this.orderColumns[idx].join(', ')} ${printer.printFrom()}`
|
|
417
|
+
})
|
|
418
|
+
: this.printers.map(printer => {
|
|
419
|
+
return `SELECT true ${printer.printFrom()}`
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
let from = union.join('\nUNION ALL\n')
|
|
423
|
+
let args = this.printArgs()
|
|
424
|
+
if (args) {
|
|
425
|
+
from = `SELECT true FROM (\n${from}\n) AS src` + args
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return `SELECT count(*) FROM (\n${from}\n) AS rows`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private printArgs(): string {
|
|
432
|
+
let sql = ''
|
|
433
|
+
if (this.orders.length) {
|
|
434
|
+
sql += '\nORDER BY ' + this.orders.map((o, idx) => `o${idx + 1} ${o}`).join(', ')
|
|
435
|
+
}
|
|
436
|
+
if (this.args.offset) {
|
|
437
|
+
sql += `\nOFFSET ${this.args.offset}`
|
|
438
|
+
}
|
|
439
|
+
if (this.args.limit) {
|
|
440
|
+
sql += `\nLIMIT ${this.args.limit}`
|
|
441
|
+
}
|
|
442
|
+
return sql
|
|
443
|
+
}
|
|
444
|
+
}
|