@platformatic/sql-graphql 0.7.0 → 0.9.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/index.js +21 -4
- package/lib/entity-to-type.js +81 -24
- package/lib/relationship.js +9 -6
- package/lib/subscriptions.js +10 -5
- package/lib/utils.js +2 -0
- package/package.json +4 -4
- package/test/composite.test.js +474 -0
- package/test/helper.js +5 -0
- package/test/ignore.test.js +90 -0
- package/test/simple.test.js +57 -1
- package/test/subscription.test.js +1 -1
- package/test/where.test.js +288 -0
package/index.js
CHANGED
|
@@ -16,6 +16,7 @@ async function mapperToGraphql (app, opts) {
|
|
|
16
16
|
const loaders = {}
|
|
17
17
|
const federationReplacements = []
|
|
18
18
|
const relations = []
|
|
19
|
+
const ignore = opts.ignore || {}
|
|
19
20
|
|
|
20
21
|
const graphOpts = {
|
|
21
22
|
queryTopFields,
|
|
@@ -35,8 +36,11 @@ async function mapperToGraphql (app, opts) {
|
|
|
35
36
|
}
|
|
36
37
|
} else {
|
|
37
38
|
for (const entity of Object.values(mapper.entities)) {
|
|
39
|
+
if (ignore[entity.pluralName] === true) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
38
42
|
relations.push(...entity.relations)
|
|
39
|
-
const meta = constructGraph(app, entity, graphOpts)
|
|
43
|
+
const meta = constructGraph(app, entity, graphOpts, ignore[entity.pluralName] || {})
|
|
40
44
|
metaMap.set(entity, meta)
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -89,10 +93,23 @@ async function mapperToGraphql (app, opts) {
|
|
|
89
93
|
|
|
90
94
|
let subscription
|
|
91
95
|
if (app.platformatic.mq) {
|
|
92
|
-
|
|
93
|
-
const ignoreList = opts.subscriptionIgnore || []
|
|
96
|
+
// Create a copy because we will alter it
|
|
97
|
+
const ignoreList = Array.from(opts.subscriptionIgnore || [])
|
|
98
|
+
|
|
99
|
+
const entitiesList = []
|
|
100
|
+
|
|
101
|
+
for (const entity of Object.values(app.platformatic.entities)) {
|
|
102
|
+
if (ignoreList.includes(entity.singularName)) {
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
// We currently do not support subscriptions for join tables
|
|
106
|
+
if (entity.primaryKeys.size > 1) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
entitiesList.push(entity)
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
if (entitiesList.length
|
|
112
|
+
if (entitiesList.length > 0) {
|
|
96
113
|
opts.subscription = {
|
|
97
114
|
emitter: app.platformatic.mq
|
|
98
115
|
}
|
package/lib/entity-to-type.js
CHANGED
|
@@ -15,8 +15,13 @@ const ascDesc = new graphql.GraphQLEnumType({
|
|
|
15
15
|
}
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const limitType = new graphql.GraphQLScalarType({
|
|
19
|
+
name: 'LimitInt',
|
|
20
|
+
description: 'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown'
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function constructGraph (app, entity, opts, ignore) {
|
|
24
|
+
const primaryKeys = Array.from(entity.primaryKeys).map((key) => camelcase(key))
|
|
20
25
|
const relationalFields = entity.relations
|
|
21
26
|
.map((relation) => relation.column_name)
|
|
22
27
|
const entityName = entity.name
|
|
@@ -35,6 +40,10 @@ function constructGraph (app, entity, opts) {
|
|
|
35
40
|
|
|
36
41
|
for (const key of Object.keys(entity.fields)) {
|
|
37
42
|
const field = entity.fields[key]
|
|
43
|
+
if (ignore[field.camelcase]) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
const meta = { field }
|
|
39
48
|
|
|
40
49
|
// sqlite doesn't support enums
|
|
@@ -66,7 +75,12 @@ function constructGraph (app, entity, opts) {
|
|
|
66
75
|
resolvers.Mutation = resolvers.Mutation || {}
|
|
67
76
|
loaders.Query = loaders.Query || {}
|
|
68
77
|
|
|
69
|
-
const getBy = camelcase(['get', singular,
|
|
78
|
+
const getBy = camelcase(['get', singular, ...(primaryKeys.map((key, i) => {
|
|
79
|
+
if (i === 0) {
|
|
80
|
+
return ['by', key]
|
|
81
|
+
}
|
|
82
|
+
return ['and', key]
|
|
83
|
+
}).flat())].join('_'))
|
|
70
84
|
|
|
71
85
|
const whereArgType = new graphql.GraphQLInputObjectType({
|
|
72
86
|
name: `${entityName}WhereArguments`,
|
|
@@ -81,6 +95,7 @@ function constructGraph (app, entity, opts) {
|
|
|
81
95
|
gte: { type: fields[field].type },
|
|
82
96
|
lt: { type: fields[field].type },
|
|
83
97
|
lte: { type: fields[field].type },
|
|
98
|
+
like: { type: fields[field].type },
|
|
84
99
|
in: { type: new graphql.GraphQLList(fields[field].type) },
|
|
85
100
|
nin: { type: new graphql.GraphQLList(fields[field].type) }
|
|
86
101
|
}
|
|
@@ -93,14 +108,21 @@ function constructGraph (app, entity, opts) {
|
|
|
93
108
|
queryTopFields[getBy] = {
|
|
94
109
|
type,
|
|
95
110
|
args: {
|
|
96
|
-
|
|
111
|
+
...(primaryKeys.reduce((acc, primaryKey) => {
|
|
112
|
+
acc[primaryKey] = { type: new graphql.GraphQLNonNull(fields[primaryKey].type) }
|
|
113
|
+
return acc
|
|
114
|
+
}, {}))
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
loaders.Query[getBy] = {
|
|
100
118
|
loader (queries, ctx) {
|
|
101
119
|
const keys = []
|
|
102
120
|
for (const query of queries) {
|
|
103
|
-
|
|
121
|
+
const pairs = []
|
|
122
|
+
for (const key of primaryKeys) {
|
|
123
|
+
pairs.push({ key, value: query.params[key] })
|
|
124
|
+
}
|
|
125
|
+
keys.push(pairs)
|
|
104
126
|
}
|
|
105
127
|
return loadMany(keys, queries, ctx)
|
|
106
128
|
},
|
|
@@ -121,7 +143,7 @@ function constructGraph (app, entity, opts) {
|
|
|
121
143
|
queryTopFields[plural] = {
|
|
122
144
|
type: new graphql.GraphQLList(type),
|
|
123
145
|
args: {
|
|
124
|
-
limit: { type:
|
|
146
|
+
limit: { type: limitType },
|
|
125
147
|
offset: { type: graphql.GraphQLInt },
|
|
126
148
|
orderBy: {
|
|
127
149
|
type: new graphql.GraphQLList(new graphql.GraphQLInputObjectType({
|
|
@@ -138,7 +160,9 @@ function constructGraph (app, entity, opts) {
|
|
|
138
160
|
|
|
139
161
|
resolvers.Query[plural] = (_, query, ctx, info) => {
|
|
140
162
|
const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
|
|
141
|
-
|
|
163
|
+
for (const primaryKey of primaryKeys) {
|
|
164
|
+
requestedFields.push(primaryKey)
|
|
165
|
+
}
|
|
142
166
|
return entity.find({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
|
|
143
167
|
}
|
|
144
168
|
|
|
@@ -172,7 +196,9 @@ function constructGraph (app, entity, opts) {
|
|
|
172
196
|
|
|
173
197
|
resolvers.Query[count] = async (_, query, ctx, info) => {
|
|
174
198
|
const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
|
|
175
|
-
|
|
199
|
+
for (const primaryKey of primaryKeys) {
|
|
200
|
+
requestedFields.push(primaryKey)
|
|
201
|
+
}
|
|
176
202
|
const total = await entity.count({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
|
|
177
203
|
return { total }
|
|
178
204
|
}
|
|
@@ -220,14 +246,21 @@ function constructGraph (app, entity, opts) {
|
|
|
220
246
|
|
|
221
247
|
federationReplacements.push({
|
|
222
248
|
find: new RegExp(`type ${entityName}`),
|
|
223
|
-
replace: `type ${entityName} @key(fields: "${
|
|
249
|
+
replace: `type ${entityName} @key(fields: "${primaryKeys}")`
|
|
224
250
|
})
|
|
225
251
|
|
|
226
252
|
if (federationMetadata) {
|
|
227
253
|
loaders[entityName] = loaders[entityName] || {}
|
|
228
254
|
loaders[entityName].__resolveReference = {
|
|
229
255
|
loader (queries, ctx) {
|
|
230
|
-
const keys =
|
|
256
|
+
const keys = []
|
|
257
|
+
for (const { obj } of queries) {
|
|
258
|
+
const pairs = []
|
|
259
|
+
for (const key of primaryKeys) {
|
|
260
|
+
pairs.push({ key, value: obj[key] })
|
|
261
|
+
}
|
|
262
|
+
keys.push(pairs)
|
|
263
|
+
}
|
|
231
264
|
return loadMany(keys, queries, ctx)
|
|
232
265
|
},
|
|
233
266
|
opts: {
|
|
@@ -246,25 +279,47 @@ function constructGraph (app, entity, opts) {
|
|
|
246
279
|
|
|
247
280
|
async function loadMany (keys, queries, ctx) {
|
|
248
281
|
const fields = getFields(queries)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
282
|
+
|
|
283
|
+
// TODO this is inefficient as it might load
|
|
284
|
+
// more data than needed if there are more than
|
|
285
|
+
// one primary key
|
|
286
|
+
const where = keys.reduce((acc, pairs) => {
|
|
287
|
+
pairs.reduce((acc, { key, value }) => {
|
|
288
|
+
if (acc[key]) {
|
|
289
|
+
acc[key].in.push(value)
|
|
290
|
+
} else {
|
|
291
|
+
acc[key] = {
|
|
292
|
+
in: [value]
|
|
293
|
+
}
|
|
253
294
|
}
|
|
254
|
-
|
|
295
|
+
return acc
|
|
296
|
+
}, acc)
|
|
297
|
+
return acc
|
|
298
|
+
}, {})
|
|
299
|
+
|
|
300
|
+
const res = await entity.find({
|
|
301
|
+
where,
|
|
255
302
|
fields,
|
|
256
303
|
ctx
|
|
257
304
|
})
|
|
258
305
|
|
|
259
|
-
const map = {}
|
|
260
|
-
|
|
261
|
-
for (const row of res) {
|
|
262
|
-
map[row[primaryKey]] = row
|
|
263
|
-
}
|
|
264
|
-
|
|
265
306
|
const output = []
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
// TODO this is extremely inefficient
|
|
308
|
+
// we need a better data structure
|
|
309
|
+
for (const pair of keys) {
|
|
310
|
+
for (const row of res) {
|
|
311
|
+
let target = row
|
|
312
|
+
for (const { key, value } of pair) {
|
|
313
|
+
if (row[key] !== value) {
|
|
314
|
+
target = null
|
|
315
|
+
break
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (target) {
|
|
320
|
+
output.push(target)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
268
323
|
}
|
|
269
324
|
|
|
270
325
|
return output
|
|
@@ -272,7 +327,9 @@ function constructGraph (app, entity, opts) {
|
|
|
272
327
|
|
|
273
328
|
function getFields (queries) {
|
|
274
329
|
const fields = new Set([...relationalFields])
|
|
275
|
-
|
|
330
|
+
for (const primaryKey of primaryKeys) {
|
|
331
|
+
fields.add(primaryKey)
|
|
332
|
+
}
|
|
276
333
|
for (const query of queries) {
|
|
277
334
|
fromSelectionSet(query.info.fieldNodes[0].selectionSet, fields)
|
|
278
335
|
}
|
package/lib/relationship.js
CHANGED
|
@@ -15,7 +15,7 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
15
15
|
const entity = entities[key]
|
|
16
16
|
tablesTypeMap[entity.table] = metaMap.get(entity)
|
|
17
17
|
}
|
|
18
|
-
for (const { table_name, foreign_table_name, column_name } of relations) {
|
|
18
|
+
for (const { table_name, foreign_table_name, column_name, foreign_column_name } of relations) {
|
|
19
19
|
const enhanceAssertLogMsg = `(table: "${table_name}", foreign table: "${foreign_table_name}", column: "${column_name}")`
|
|
20
20
|
|
|
21
21
|
assert(table_name, `table_name is required ${enhanceAssertLogMsg}`)
|
|
@@ -34,11 +34,13 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
34
34
|
const originalField = camelcase(column_name)
|
|
35
35
|
delete current.fields[originalField]
|
|
36
36
|
loaders[current.type] = loaders[current.type] || resolvers[current.type] || {}
|
|
37
|
+
const key = camelcase(foreign_column_name)
|
|
37
38
|
loaders[current.type][lowered] = {
|
|
38
39
|
loader (queries, ctx) {
|
|
39
|
-
const keys =
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const keys = []
|
|
41
|
+
for (const { obj } of queries) {
|
|
42
|
+
keys.push([{ key, value: obj[originalField].toString() }])
|
|
43
|
+
}
|
|
42
44
|
return foreign.loadMany(keys, queries, ctx)
|
|
43
45
|
},
|
|
44
46
|
opts: {
|
|
@@ -48,8 +50,9 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
|
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
// foreign to current
|
|
52
|
-
|
|
53
|
+
// foreign to current, we skip this if the current table has a composite primary key
|
|
54
|
+
// TODO implement support for this case
|
|
55
|
+
if (current.entity.primaryKeys.size === 1) {
|
|
53
56
|
const lowered = lowerCaseFirst(camelcase(current.entity.table))
|
|
54
57
|
if (!relationships[foreign.type] || relationships[foreign.type][lowered] !== false) {
|
|
55
58
|
foreign.fields[lowered] = queryTopFields[lowered]
|
package/lib/subscriptions.js
CHANGED
|
@@ -9,6 +9,14 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
9
9
|
if (ignores.indexOf(field.singularName) >= 0) {
|
|
10
10
|
continue
|
|
11
11
|
}
|
|
12
|
+
// TODO currently we are not supporting subscriptions for
|
|
13
|
+
// entities that have a composite primary key
|
|
14
|
+
/* istanbul ignore next */
|
|
15
|
+
if (field.primaryKeys.size !== 1) {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
const entity = app.platformatic.entities[field.singularName]
|
|
19
|
+
const primaryKey = entity.primaryKeys.values().next().value
|
|
12
20
|
const { type } = meta
|
|
13
21
|
const saved = `${field.singularName}Saved`
|
|
14
22
|
fields[saved] = {
|
|
@@ -18,11 +26,9 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
18
26
|
subscribe: async function * (_, query, ctx, info) {
|
|
19
27
|
const log = ctx.reply.request.log
|
|
20
28
|
const { pubsub } = ctx
|
|
21
|
-
const entity = app.platformatic.entities[field.singularName]
|
|
22
29
|
const topic = await entity.getSubscriptionTopic({ action: 'save', ctx })
|
|
23
30
|
log.trace({ topic }, 'subscribed')
|
|
24
31
|
const res = await pubsub.subscribe(topic)
|
|
25
|
-
const primaryKey = entity.primaryKey
|
|
26
32
|
const fields = meta.getFields([{ info }])
|
|
27
33
|
|
|
28
34
|
for await (const msg of res) {
|
|
@@ -55,8 +61,8 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
55
61
|
type: new graphql.GraphQLObjectType({
|
|
56
62
|
name: `${field.name}Deleted`,
|
|
57
63
|
fields: {
|
|
58
|
-
[
|
|
59
|
-
type: meta.fields[
|
|
64
|
+
[primaryKey]: {
|
|
65
|
+
type: meta.fields[primaryKey].type
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
})
|
|
@@ -64,7 +70,6 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
|
|
|
64
70
|
resolvers.Subscription[deleted] = {
|
|
65
71
|
subscribe: async (_, query, ctx, info) => {
|
|
66
72
|
const { pubsub } = ctx
|
|
67
|
-
const entity = app.platformatic.entities[field.singularName]
|
|
68
73
|
const topic = await entity.getSubscriptionTopic({ action: 'delete', ctx })
|
|
69
74
|
const res = await pubsub.subscribe(topic)
|
|
70
75
|
ctx.reply.request.log.trace({ topic }, 'subscribed')
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Map SQL dbs to GraphQL",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@platformatic/sql-events": "0.
|
|
18
|
-
"@platformatic/sql-mapper": "0.
|
|
17
|
+
"@platformatic/sql-events": "0.9.0",
|
|
18
|
+
"@platformatic/sql-mapper": "0.9.0",
|
|
19
19
|
"fastify": "^4.6.0",
|
|
20
20
|
"snazzy": "^9.0.0",
|
|
21
21
|
"standard": "^17.0.0",
|
|
22
22
|
"tap": "^16.0.0",
|
|
23
|
-
"tsd": "^0.
|
|
23
|
+
"tsd": "^0.25.0",
|
|
24
24
|
"ws": "^8.9.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { clear, connInfo, isSQLite, isMysql, isPg } = require('./helper')
|
|
4
|
+
const { test } = require('tap')
|
|
5
|
+
const fastify = require('fastify')
|
|
6
|
+
const sqlMapper = require('@platformatic/sql-mapper')
|
|
7
|
+
const sqlEvents = require('@platformatic/sql-events')
|
|
8
|
+
const sqlGraphQL = require('..')
|
|
9
|
+
|
|
10
|
+
test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
|
|
11
|
+
/* https://github.com/platformatic/platformatic/issues/299 */
|
|
12
|
+
async function onDatabaseLoad (db, sql) {
|
|
13
|
+
await clear(db, sql)
|
|
14
|
+
|
|
15
|
+
if (isSQLite) {
|
|
16
|
+
await db.query(sql`CREATE TABLE pages (
|
|
17
|
+
id INTEGER PRIMARY KEY,
|
|
18
|
+
the_title VARCHAR(42)
|
|
19
|
+
);`)
|
|
20
|
+
|
|
21
|
+
await db.query(sql`CREATE TABLE users (
|
|
22
|
+
id INTEGER PRIMARY KEY,
|
|
23
|
+
username VARCHAR(255) NOT NULL
|
|
24
|
+
);`)
|
|
25
|
+
|
|
26
|
+
await db.query(sql`CREATE TABLE editors (
|
|
27
|
+
page_id INTEGER NOT NULL,
|
|
28
|
+
user_id INTEGER NOT NULL,
|
|
29
|
+
role VARCHAR(255) NOT NULL,
|
|
30
|
+
CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
|
|
31
|
+
CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
|
|
32
|
+
PRIMARY KEY (page_id, user_id)
|
|
33
|
+
);`)
|
|
34
|
+
} else if (isPg) {
|
|
35
|
+
await db.query(sql`CREATE TABLE pages (
|
|
36
|
+
id SERIAL PRIMARY KEY,
|
|
37
|
+
the_title VARCHAR(255) NOT NULL
|
|
38
|
+
);`)
|
|
39
|
+
|
|
40
|
+
await db.query(sql`CREATE TABLE users (
|
|
41
|
+
id SERIAL PRIMARY KEY,
|
|
42
|
+
username VARCHAR(255) NOT NULL
|
|
43
|
+
);`)
|
|
44
|
+
|
|
45
|
+
await db.query(sql`CREATE TABLE editors (
|
|
46
|
+
page_id INTEGER NOT NULL,
|
|
47
|
+
user_id INTEGER NOT NULL,
|
|
48
|
+
role VARCHAR(255) NOT NULL,
|
|
49
|
+
CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
|
|
50
|
+
CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
|
|
51
|
+
PRIMARY KEY (page_id, user_id)
|
|
52
|
+
);`)
|
|
53
|
+
} else if (isMysql) {
|
|
54
|
+
await db.query(sql`CREATE TABLE pages (
|
|
55
|
+
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
56
|
+
the_title VARCHAR(255) NOT NULL
|
|
57
|
+
);`)
|
|
58
|
+
|
|
59
|
+
await db.query(sql`CREATE TABLE users (
|
|
60
|
+
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
61
|
+
username VARCHAR(255) NOT NULL
|
|
62
|
+
);`)
|
|
63
|
+
|
|
64
|
+
await db.query(sql`CREATE TABLE editors (
|
|
65
|
+
page_id INTEGER NOT NULL,
|
|
66
|
+
user_id INTEGER NOT NULL,
|
|
67
|
+
role VARCHAR(255) NOT NULL,
|
|
68
|
+
CONSTRAINT \`fk_editor_pages\` FOREIGN KEY (page_id) REFERENCES pages (id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
|
69
|
+
CONSTRAINT \`fk_editor_users\` FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
|
70
|
+
PRIMARY KEY (page_id, user_id)
|
|
71
|
+
);`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const app = fastify()
|
|
76
|
+
app.register(sqlMapper, {
|
|
77
|
+
...connInfo,
|
|
78
|
+
onDatabaseLoad
|
|
79
|
+
})
|
|
80
|
+
app.register(sqlEvents) // needed as if it's present it might throw
|
|
81
|
+
app.register(sqlGraphQL)
|
|
82
|
+
teardown(app.close.bind(app))
|
|
83
|
+
|
|
84
|
+
await app.ready()
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
const res = await app.inject({
|
|
88
|
+
method: 'POST',
|
|
89
|
+
url: '/graphql',
|
|
90
|
+
body: {
|
|
91
|
+
query: `
|
|
92
|
+
mutation {
|
|
93
|
+
savePage(input: { theTitle: "foobar" }) {
|
|
94
|
+
id
|
|
95
|
+
theTitle
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
equal(res.statusCode, 200, 'savePage status code')
|
|
102
|
+
same(res.json(), {
|
|
103
|
+
data: {
|
|
104
|
+
savePage: {
|
|
105
|
+
id: 1,
|
|
106
|
+
theTitle: 'foobar'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, 'savePage response')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
const res = await app.inject({
|
|
114
|
+
method: 'POST',
|
|
115
|
+
url: '/graphql',
|
|
116
|
+
body: {
|
|
117
|
+
query: `
|
|
118
|
+
mutation {
|
|
119
|
+
saveUser(input: { username: "mcollina" }) {
|
|
120
|
+
id
|
|
121
|
+
username
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
equal(res.statusCode, 200, 'saveUser status code')
|
|
128
|
+
same(res.json(), {
|
|
129
|
+
data: {
|
|
130
|
+
saveUser: {
|
|
131
|
+
id: 1,
|
|
132
|
+
username: 'mcollina'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, 'saveUser response')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
const res = await app.inject({
|
|
140
|
+
method: 'POST',
|
|
141
|
+
url: '/graphql',
|
|
142
|
+
body: {
|
|
143
|
+
query: `
|
|
144
|
+
mutation {
|
|
145
|
+
saveUser(input: { username: "lucamaraschi" }) {
|
|
146
|
+
id
|
|
147
|
+
username
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
equal(res.statusCode, 200, 'saveUser status code')
|
|
154
|
+
same(res.json(), {
|
|
155
|
+
data: {
|
|
156
|
+
saveUser: {
|
|
157
|
+
id: 2,
|
|
158
|
+
username: 'lucamaraschi'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}, 'saveUser response')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
{
|
|
165
|
+
const res = await app.inject({
|
|
166
|
+
method: 'POST',
|
|
167
|
+
url: '/graphql',
|
|
168
|
+
body: {
|
|
169
|
+
query: `
|
|
170
|
+
mutation {
|
|
171
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "admin" }) {
|
|
172
|
+
user {
|
|
173
|
+
id
|
|
174
|
+
username
|
|
175
|
+
}
|
|
176
|
+
page {
|
|
177
|
+
id
|
|
178
|
+
theTitle
|
|
179
|
+
}
|
|
180
|
+
role
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
`
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
187
|
+
same(res.json(), {
|
|
188
|
+
data: {
|
|
189
|
+
saveEditor: {
|
|
190
|
+
user: {
|
|
191
|
+
id: 1,
|
|
192
|
+
username: 'mcollina'
|
|
193
|
+
},
|
|
194
|
+
page: {
|
|
195
|
+
id: 1,
|
|
196
|
+
theTitle: 'foobar'
|
|
197
|
+
},
|
|
198
|
+
role: 'admin'
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}, 'saveEditor response')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
const res = await app.inject({
|
|
206
|
+
method: 'POST',
|
|
207
|
+
url: '/graphql',
|
|
208
|
+
body: {
|
|
209
|
+
query: `
|
|
210
|
+
mutation {
|
|
211
|
+
saveEditor(input: { userId: "2", pageId: "1", role: "author" }) {
|
|
212
|
+
user {
|
|
213
|
+
id
|
|
214
|
+
username
|
|
215
|
+
}
|
|
216
|
+
page {
|
|
217
|
+
id
|
|
218
|
+
theTitle
|
|
219
|
+
}
|
|
220
|
+
role
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
`
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
227
|
+
same(res.json(), {
|
|
228
|
+
data: {
|
|
229
|
+
saveEditor: {
|
|
230
|
+
user: {
|
|
231
|
+
id: 2,
|
|
232
|
+
username: 'lucamaraschi'
|
|
233
|
+
},
|
|
234
|
+
page: {
|
|
235
|
+
id: 1,
|
|
236
|
+
theTitle: 'foobar'
|
|
237
|
+
},
|
|
238
|
+
role: 'author'
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}, 'saveEditor response')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
const res = await app.inject({
|
|
246
|
+
method: 'POST',
|
|
247
|
+
url: '/graphql',
|
|
248
|
+
body: {
|
|
249
|
+
query: `
|
|
250
|
+
mutation {
|
|
251
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "captain" }) {
|
|
252
|
+
user {
|
|
253
|
+
id
|
|
254
|
+
username
|
|
255
|
+
}
|
|
256
|
+
page {
|
|
257
|
+
id
|
|
258
|
+
theTitle
|
|
259
|
+
}
|
|
260
|
+
role
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
`
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
267
|
+
same(res.json(), {
|
|
268
|
+
data: {
|
|
269
|
+
saveEditor: {
|
|
270
|
+
user: {
|
|
271
|
+
id: 1,
|
|
272
|
+
username: 'mcollina'
|
|
273
|
+
},
|
|
274
|
+
page: {
|
|
275
|
+
id: 1,
|
|
276
|
+
theTitle: 'foobar'
|
|
277
|
+
},
|
|
278
|
+
role: 'captain'
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, 'saveEditor response')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
const res = await app.inject({
|
|
286
|
+
method: 'post',
|
|
287
|
+
url: '/graphql',
|
|
288
|
+
body: {
|
|
289
|
+
query: `
|
|
290
|
+
query {
|
|
291
|
+
editors(orderBy: { field: role, direction: DESC }) {
|
|
292
|
+
user {
|
|
293
|
+
id
|
|
294
|
+
username
|
|
295
|
+
}
|
|
296
|
+
page {
|
|
297
|
+
id
|
|
298
|
+
theTitle
|
|
299
|
+
}
|
|
300
|
+
role
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
`
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
equal(res.statusCode, 200, 'editors status code')
|
|
307
|
+
same(res.json(), {
|
|
308
|
+
data: {
|
|
309
|
+
editors: [{
|
|
310
|
+
user: {
|
|
311
|
+
id: '1',
|
|
312
|
+
username: 'mcollina'
|
|
313
|
+
},
|
|
314
|
+
page: {
|
|
315
|
+
id: '1',
|
|
316
|
+
theTitle: 'foobar'
|
|
317
|
+
},
|
|
318
|
+
role: 'captain'
|
|
319
|
+
}, {
|
|
320
|
+
user: {
|
|
321
|
+
id: '2',
|
|
322
|
+
username: 'lucamaraschi'
|
|
323
|
+
},
|
|
324
|
+
page: {
|
|
325
|
+
id: '1',
|
|
326
|
+
theTitle: 'foobar'
|
|
327
|
+
},
|
|
328
|
+
role: 'author'
|
|
329
|
+
}]
|
|
330
|
+
}
|
|
331
|
+
}, 'editor response')
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('composite primary keys with no foreign keys', async ({ equal, same, teardown, rejects }) => {
|
|
336
|
+
/* https://github.com/platformatic/platformatic/issues/299 */
|
|
337
|
+
async function onDatabaseLoad (db, sql) {
|
|
338
|
+
await clear(db, sql)
|
|
339
|
+
|
|
340
|
+
await db.query(sql`CREATE TABLE editors (
|
|
341
|
+
page_id INTEGER NOT NULL,
|
|
342
|
+
user_id INTEGER NOT NULL,
|
|
343
|
+
role VARCHAR(255) NOT NULL,
|
|
344
|
+
PRIMARY KEY (page_id, user_id)
|
|
345
|
+
);`)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const app = fastify()
|
|
349
|
+
app.register(sqlMapper, {
|
|
350
|
+
...connInfo,
|
|
351
|
+
onDatabaseLoad
|
|
352
|
+
})
|
|
353
|
+
app.register(sqlEvents) // needed as if it's present it will throw
|
|
354
|
+
app.register(sqlGraphQL)
|
|
355
|
+
teardown(app.close.bind(app))
|
|
356
|
+
|
|
357
|
+
await app.ready()
|
|
358
|
+
|
|
359
|
+
{
|
|
360
|
+
const res = await app.inject({
|
|
361
|
+
method: 'POST',
|
|
362
|
+
url: '/graphql',
|
|
363
|
+
body: {
|
|
364
|
+
query: `
|
|
365
|
+
mutation {
|
|
366
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "admin" }) {
|
|
367
|
+
userId
|
|
368
|
+
pageId
|
|
369
|
+
role
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
`
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
376
|
+
same(res.json(), {
|
|
377
|
+
data: {
|
|
378
|
+
saveEditor: {
|
|
379
|
+
userId: '1',
|
|
380
|
+
pageId: '1',
|
|
381
|
+
role: 'admin'
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, 'saveEditor response')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
{
|
|
388
|
+
const res = await app.inject({
|
|
389
|
+
method: 'POST',
|
|
390
|
+
url: '/graphql',
|
|
391
|
+
body: {
|
|
392
|
+
query: `
|
|
393
|
+
mutation {
|
|
394
|
+
saveEditor(input: { userId: "2", pageId: "1", role: "author" }) {
|
|
395
|
+
userId
|
|
396
|
+
pageId
|
|
397
|
+
role
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
`
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
404
|
+
same(res.json(), {
|
|
405
|
+
data: {
|
|
406
|
+
saveEditor: {
|
|
407
|
+
userId: '2',
|
|
408
|
+
pageId: '1',
|
|
409
|
+
role: 'author'
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}, 'saveEditor response')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
{
|
|
416
|
+
const res = await app.inject({
|
|
417
|
+
method: 'POST',
|
|
418
|
+
url: '/graphql',
|
|
419
|
+
body: {
|
|
420
|
+
query: `
|
|
421
|
+
mutation {
|
|
422
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "captain" }) {
|
|
423
|
+
userId
|
|
424
|
+
pageId
|
|
425
|
+
role
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
`
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
432
|
+
same(res.json(), {
|
|
433
|
+
data: {
|
|
434
|
+
saveEditor: {
|
|
435
|
+
userId: 1,
|
|
436
|
+
pageId: 1,
|
|
437
|
+
role: 'captain'
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}, 'saveEditor response')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
{
|
|
444
|
+
const res = await app.inject({
|
|
445
|
+
method: 'post',
|
|
446
|
+
url: '/graphql',
|
|
447
|
+
body: {
|
|
448
|
+
query: `
|
|
449
|
+
query {
|
|
450
|
+
editors(orderBy: { field: role, direction: DESC }) {
|
|
451
|
+
userId
|
|
452
|
+
pageId
|
|
453
|
+
role
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
`
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
equal(res.statusCode, 200, 'editors status code')
|
|
460
|
+
same(res.json(), {
|
|
461
|
+
data: {
|
|
462
|
+
editors: [{
|
|
463
|
+
userId: '1',
|
|
464
|
+
pageId: '1',
|
|
465
|
+
role: 'captain'
|
|
466
|
+
}, {
|
|
467
|
+
userId: '2',
|
|
468
|
+
pageId: '1',
|
|
469
|
+
role: 'author'
|
|
470
|
+
}]
|
|
471
|
+
}
|
|
472
|
+
}, 'editor response')
|
|
473
|
+
}
|
|
474
|
+
})
|
package/test/helper.js
CHANGED
|
@@ -30,6 +30,11 @@ if (!process.env.DB || process.env.DB === 'postgresql') {
|
|
|
30
30
|
module.exports.connInfo = connInfo
|
|
31
31
|
|
|
32
32
|
module.exports.clear = async function (db, sql) {
|
|
33
|
+
try {
|
|
34
|
+
await db.query(sql`DROP TABLE editors`)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
try {
|
|
34
39
|
await db.query(sql`DROP TABLE pages`)
|
|
35
40
|
} catch (err) {
|
package/test/ignore.test.js
CHANGED
|
@@ -115,3 +115,93 @@ test('ignore a column', async ({ pass, teardown, equal }) => {
|
|
|
115
115
|
equal(Category.fields.find((f) => f.name === 'name'), undefined, 'name column is ignored')
|
|
116
116
|
}
|
|
117
117
|
})
|
|
118
|
+
|
|
119
|
+
test('ignore a table via sql-graphql option', async ({ pass, teardown, equal }) => {
|
|
120
|
+
const app = fastify()
|
|
121
|
+
app.register(sqlMapper, {
|
|
122
|
+
...connInfo,
|
|
123
|
+
async onDatabaseLoad (db, sql) {
|
|
124
|
+
pass('onDatabaseLoad called')
|
|
125
|
+
|
|
126
|
+
await clear(db, sql)
|
|
127
|
+
await createBasicPages(db, sql)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
app.register(sqlGraphQL, {
|
|
131
|
+
ignore: {
|
|
132
|
+
categories: true
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
teardown(app.close.bind(app))
|
|
136
|
+
|
|
137
|
+
await app.ready()
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
const res = await app.inject({
|
|
141
|
+
method: 'POST',
|
|
142
|
+
url: '/graphql',
|
|
143
|
+
body: {
|
|
144
|
+
query: `
|
|
145
|
+
query {
|
|
146
|
+
__schema {
|
|
147
|
+
types {
|
|
148
|
+
name
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
equal(res.statusCode, 200, 'introspection query status code')
|
|
156
|
+
const data = res.json().data
|
|
157
|
+
equal(data.__schema.types.find((t) => t.name === 'Category'), undefined, 'Category type is ignored')
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('ignore a column via sql-graphql option', async ({ pass, teardown, equal }) => {
|
|
162
|
+
const app = fastify()
|
|
163
|
+
app.register(sqlMapper, {
|
|
164
|
+
...connInfo,
|
|
165
|
+
async onDatabaseLoad (db, sql) {
|
|
166
|
+
pass('onDatabaseLoad called')
|
|
167
|
+
|
|
168
|
+
await clear(db, sql)
|
|
169
|
+
await createBasicPages(db, sql)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
app.register(sqlGraphQL, {
|
|
173
|
+
ignore: {
|
|
174
|
+
categories: {
|
|
175
|
+
name: true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
teardown(app.close.bind(app))
|
|
180
|
+
|
|
181
|
+
await app.ready()
|
|
182
|
+
|
|
183
|
+
{
|
|
184
|
+
const res = await app.inject({
|
|
185
|
+
method: 'POST',
|
|
186
|
+
url: '/graphql',
|
|
187
|
+
body: {
|
|
188
|
+
query: `
|
|
189
|
+
query {
|
|
190
|
+
__schema {
|
|
191
|
+
types {
|
|
192
|
+
name
|
|
193
|
+
fields {
|
|
194
|
+
name
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
equal(res.statusCode, 200, 'introspection query status code')
|
|
203
|
+
const data = res.json().data
|
|
204
|
+
const Category = data.__schema.types.find((t) => t.name === 'Category')
|
|
205
|
+
equal(Category.fields.find((f) => f.name === 'name'), undefined, 'name column is ignored')
|
|
206
|
+
}
|
|
207
|
+
})
|
package/test/simple.test.js
CHANGED
|
@@ -4,7 +4,7 @@ const { test } = require('tap')
|
|
|
4
4
|
const sqlGraphQL = require('..')
|
|
5
5
|
const sqlMapper = require('@platformatic/sql-mapper')
|
|
6
6
|
const fastify = require('fastify')
|
|
7
|
-
const { clear, connInfo, isSQLite, isMysql, isMariaDB } = require('./helper')
|
|
7
|
+
const { clear, connInfo, isSQLite, isMysql, isMariaDB, isPg } = require('./helper')
|
|
8
8
|
|
|
9
9
|
async function createBasicPages (db, sql) {
|
|
10
10
|
if (isSQLite) {
|
|
@@ -901,3 +901,59 @@ test('deserialize JSON columns', { skip: isSQLite }, async (t) => {
|
|
|
901
901
|
same(json.data.getPageById.metadata, jsonData)
|
|
902
902
|
}
|
|
903
903
|
})
|
|
904
|
+
|
|
905
|
+
test('deserialize JSONB columns', { skip: !isPg }, async (t) => {
|
|
906
|
+
const { pass, teardown, same } = t
|
|
907
|
+
const app = fastify()
|
|
908
|
+
const jsonData = {
|
|
909
|
+
foo: 'bar',
|
|
910
|
+
baz: 42,
|
|
911
|
+
items: ['foo', 'bar'],
|
|
912
|
+
nested: {
|
|
913
|
+
hello: 'world'
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
app.register(sqlMapper, {
|
|
917
|
+
...connInfo,
|
|
918
|
+
async onDatabaseLoad (db, sql) {
|
|
919
|
+
pass('onDatabaseLoad called')
|
|
920
|
+
|
|
921
|
+
await clear(db, sql)
|
|
922
|
+
|
|
923
|
+
await db.query(sql`CREATE TABLE pages (
|
|
924
|
+
id SERIAL PRIMARY KEY,
|
|
925
|
+
title VARCHAR(255) NOT NULL,
|
|
926
|
+
metadata JSONB NOT NULL
|
|
927
|
+
);`)
|
|
928
|
+
|
|
929
|
+
await db.query(sql`INSERT INTO pages (id, title, metadata) VALUES (1, 'Hello World', ${JSON.stringify(jsonData)})`)
|
|
930
|
+
}
|
|
931
|
+
})
|
|
932
|
+
app.register(sqlGraphQL, {
|
|
933
|
+
graphiql: false
|
|
934
|
+
})
|
|
935
|
+
teardown(app.close.bind(app))
|
|
936
|
+
|
|
937
|
+
await app.ready()
|
|
938
|
+
const res = await app.inject({
|
|
939
|
+
method: 'POST',
|
|
940
|
+
url: '/graphql',
|
|
941
|
+
body: {
|
|
942
|
+
query: `
|
|
943
|
+
query {
|
|
944
|
+
getPageById(id: 1) {
|
|
945
|
+
id
|
|
946
|
+
title
|
|
947
|
+
metadata
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
`
|
|
951
|
+
}
|
|
952
|
+
})
|
|
953
|
+
const json = res.json()
|
|
954
|
+
if (isMariaDB) {
|
|
955
|
+
same(json.data.getPageById.metadata, JSON.stringify(jsonData))
|
|
956
|
+
} else {
|
|
957
|
+
same(json.data.getPageById.metadata, jsonData)
|
|
958
|
+
}
|
|
959
|
+
})
|
package/test/where.test.js
CHANGED
|
@@ -812,3 +812,291 @@ test('delete all', async ({ pass, teardown, same, equal }) => {
|
|
|
812
812
|
}, 'posts response')
|
|
813
813
|
}
|
|
814
814
|
})
|
|
815
|
+
|
|
816
|
+
test('like', async ({ pass, teardown, same, equal }) => {
|
|
817
|
+
const app = fastify()
|
|
818
|
+
app.register(sqlMapper, {
|
|
819
|
+
...connInfo,
|
|
820
|
+
async onDatabaseLoad (db, sql) {
|
|
821
|
+
pass('onDatabaseLoad called')
|
|
822
|
+
|
|
823
|
+
await clear(db, sql)
|
|
824
|
+
|
|
825
|
+
if (isSQLite) {
|
|
826
|
+
await db.query(sql`CREATE TABLE posts (
|
|
827
|
+
id INTEGER PRIMARY KEY,
|
|
828
|
+
title VARCHAR(42),
|
|
829
|
+
long_text TEXT,
|
|
830
|
+
counter INTEGER
|
|
831
|
+
);`)
|
|
832
|
+
} else {
|
|
833
|
+
await db.query(sql`CREATE TABLE posts (
|
|
834
|
+
id SERIAL PRIMARY KEY,
|
|
835
|
+
title VARCHAR(42),
|
|
836
|
+
long_text TEXT,
|
|
837
|
+
counter INTEGER
|
|
838
|
+
);`)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
})
|
|
842
|
+
app.register(sqlGraphQL)
|
|
843
|
+
teardown(app.close.bind(app))
|
|
844
|
+
|
|
845
|
+
await app.ready()
|
|
846
|
+
|
|
847
|
+
const posts = [{
|
|
848
|
+
title: 'Dog',
|
|
849
|
+
longText: 'The dog barks',
|
|
850
|
+
counter: 10
|
|
851
|
+
}, {
|
|
852
|
+
title: 'Cat',
|
|
853
|
+
longText: 'The cat meows',
|
|
854
|
+
counter: 20
|
|
855
|
+
}, {
|
|
856
|
+
title: 'Mouse',
|
|
857
|
+
longText: 'The mouse likes cheese',
|
|
858
|
+
counter: 30
|
|
859
|
+
}, {
|
|
860
|
+
title: 'Duck',
|
|
861
|
+
longText: 'A duck tale',
|
|
862
|
+
counter: 40
|
|
863
|
+
}]
|
|
864
|
+
|
|
865
|
+
await app.inject({
|
|
866
|
+
method: 'POST',
|
|
867
|
+
url: '/graphql',
|
|
868
|
+
body: {
|
|
869
|
+
query: `
|
|
870
|
+
mutation batch($inputs : [PostInput]!) {
|
|
871
|
+
insertPosts(inputs: $inputs) {
|
|
872
|
+
id
|
|
873
|
+
title
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
`,
|
|
877
|
+
variables: {
|
|
878
|
+
inputs: posts
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
{
|
|
884
|
+
const res = await app.inject({
|
|
885
|
+
method: 'POST',
|
|
886
|
+
url: '/graphql',
|
|
887
|
+
body: {
|
|
888
|
+
query: `
|
|
889
|
+
query {
|
|
890
|
+
posts {
|
|
891
|
+
id
|
|
892
|
+
title
|
|
893
|
+
longText
|
|
894
|
+
counter
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
`
|
|
898
|
+
}
|
|
899
|
+
})
|
|
900
|
+
equal(res.statusCode, 200, 'posts status code')
|
|
901
|
+
same(res.json(), {
|
|
902
|
+
data: {
|
|
903
|
+
posts: [{
|
|
904
|
+
id: '1',
|
|
905
|
+
title: 'Dog',
|
|
906
|
+
longText: 'The dog barks',
|
|
907
|
+
counter: 10
|
|
908
|
+
}, {
|
|
909
|
+
id: '2',
|
|
910
|
+
title: 'Cat',
|
|
911
|
+
longText: 'The cat meows',
|
|
912
|
+
counter: 20
|
|
913
|
+
}, {
|
|
914
|
+
id: '3',
|
|
915
|
+
title: 'Mouse',
|
|
916
|
+
longText: 'The mouse likes cheese',
|
|
917
|
+
counter: 30
|
|
918
|
+
}, {
|
|
919
|
+
id: '4',
|
|
920
|
+
title: 'Duck',
|
|
921
|
+
longText: 'A duck tale',
|
|
922
|
+
counter: 40
|
|
923
|
+
}]
|
|
924
|
+
}
|
|
925
|
+
}, 'posts response')
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
{
|
|
929
|
+
const res = await app.inject({
|
|
930
|
+
method: 'POST',
|
|
931
|
+
url: '/graphql',
|
|
932
|
+
body: {
|
|
933
|
+
query: `
|
|
934
|
+
query {
|
|
935
|
+
posts(where: { longText: { like: "The%" } }) {
|
|
936
|
+
id
|
|
937
|
+
title
|
|
938
|
+
longText
|
|
939
|
+
counter
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
`
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "The%" } } status code')
|
|
946
|
+
same(res.json(), {
|
|
947
|
+
data: {
|
|
948
|
+
posts: [{
|
|
949
|
+
id: '1',
|
|
950
|
+
title: 'Dog',
|
|
951
|
+
longText: 'The dog barks',
|
|
952
|
+
counter: 10
|
|
953
|
+
}, {
|
|
954
|
+
id: '2',
|
|
955
|
+
title: 'Cat',
|
|
956
|
+
longText: 'The cat meows',
|
|
957
|
+
counter: 20
|
|
958
|
+
}, {
|
|
959
|
+
id: '3',
|
|
960
|
+
title: 'Mouse',
|
|
961
|
+
longText: 'The mouse likes cheese',
|
|
962
|
+
counter: 30
|
|
963
|
+
}]
|
|
964
|
+
}
|
|
965
|
+
}, 'where: { longText: { like: "The%" } } response')
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
{
|
|
969
|
+
const res = await app.inject({
|
|
970
|
+
method: 'POST',
|
|
971
|
+
url: '/graphql',
|
|
972
|
+
body: {
|
|
973
|
+
query: `
|
|
974
|
+
query {
|
|
975
|
+
posts(where: { longText: { like: "%s" } }) {
|
|
976
|
+
id
|
|
977
|
+
title
|
|
978
|
+
longText
|
|
979
|
+
counter
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
`
|
|
983
|
+
}
|
|
984
|
+
})
|
|
985
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "%s" } } status code')
|
|
986
|
+
same(res.json(), {
|
|
987
|
+
data: {
|
|
988
|
+
posts: [{
|
|
989
|
+
id: '1',
|
|
990
|
+
title: 'Dog',
|
|
991
|
+
longText: 'The dog barks',
|
|
992
|
+
counter: 10
|
|
993
|
+
}, {
|
|
994
|
+
id: '2',
|
|
995
|
+
title: 'Cat',
|
|
996
|
+
longText: 'The cat meows',
|
|
997
|
+
counter: 20
|
|
998
|
+
}]
|
|
999
|
+
}
|
|
1000
|
+
}, 'where: { longText: { like: "%s" } } response')
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
{
|
|
1004
|
+
const res = await app.inject({
|
|
1005
|
+
method: 'POST',
|
|
1006
|
+
url: '/graphql',
|
|
1007
|
+
body: {
|
|
1008
|
+
query: `
|
|
1009
|
+
query {
|
|
1010
|
+
posts(where: { longText: { like: "%o%" } }) {
|
|
1011
|
+
id
|
|
1012
|
+
title
|
|
1013
|
+
longText
|
|
1014
|
+
counter
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
`
|
|
1018
|
+
}
|
|
1019
|
+
})
|
|
1020
|
+
equal(res.statusCode, 200, 'where: { longText: { like: "%o%" } } status code')
|
|
1021
|
+
same(res.json(), {
|
|
1022
|
+
data: {
|
|
1023
|
+
posts: [{
|
|
1024
|
+
id: '1',
|
|
1025
|
+
title: 'Dog',
|
|
1026
|
+
longText: 'The dog barks',
|
|
1027
|
+
counter: 10
|
|
1028
|
+
}, {
|
|
1029
|
+
id: '2',
|
|
1030
|
+
title: 'Cat',
|
|
1031
|
+
longText: 'The cat meows',
|
|
1032
|
+
counter: 20
|
|
1033
|
+
}, {
|
|
1034
|
+
id: '3',
|
|
1035
|
+
title: 'Mouse',
|
|
1036
|
+
longText: 'The mouse likes cheese',
|
|
1037
|
+
counter: 30
|
|
1038
|
+
}]
|
|
1039
|
+
}
|
|
1040
|
+
}, 'where: { longText: { like: "%o%" } } response')
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
{
|
|
1044
|
+
const res = await app.inject({
|
|
1045
|
+
method: 'POST',
|
|
1046
|
+
url: '/graphql',
|
|
1047
|
+
body: {
|
|
1048
|
+
query: `
|
|
1049
|
+
query {
|
|
1050
|
+
posts(where: { counter: { like: 10 } }) {
|
|
1051
|
+
id
|
|
1052
|
+
title
|
|
1053
|
+
longText
|
|
1054
|
+
counter
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
`
|
|
1058
|
+
}
|
|
1059
|
+
})
|
|
1060
|
+
equal(res.statusCode, 200, 'where: { counter: { like: 10 } } status code')
|
|
1061
|
+
same(res.json(), {
|
|
1062
|
+
data: {
|
|
1063
|
+
posts: [{
|
|
1064
|
+
id: '1',
|
|
1065
|
+
title: 'Dog',
|
|
1066
|
+
longText: 'The dog barks',
|
|
1067
|
+
counter: 10
|
|
1068
|
+
}]
|
|
1069
|
+
}
|
|
1070
|
+
}, 'where: { counter: { like: 10 } } response')
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
{
|
|
1074
|
+
const res = await app.inject({
|
|
1075
|
+
method: 'POST',
|
|
1076
|
+
url: '/graphql',
|
|
1077
|
+
body: {
|
|
1078
|
+
query: `
|
|
1079
|
+
query {
|
|
1080
|
+
posts(where: { counter: { like: 40 } }) {
|
|
1081
|
+
id
|
|
1082
|
+
title
|
|
1083
|
+
longText
|
|
1084
|
+
counter
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
`
|
|
1088
|
+
}
|
|
1089
|
+
})
|
|
1090
|
+
equal(res.statusCode, 200, 'where: { counter: { like: 40 } } status code')
|
|
1091
|
+
same(res.json(), {
|
|
1092
|
+
data: {
|
|
1093
|
+
posts: [{
|
|
1094
|
+
id: '4',
|
|
1095
|
+
title: 'Duck',
|
|
1096
|
+
longText: 'A duck tale',
|
|
1097
|
+
counter: 40
|
|
1098
|
+
}]
|
|
1099
|
+
}
|
|
1100
|
+
}, 'where: { counter: { like: 40 } } response')
|
|
1101
|
+
}
|
|
1102
|
+
})
|