@platformatic/sql-graphql 0.10.0 → 0.12.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 CHANGED
@@ -118,7 +118,21 @@ async function mapperToGraphql (app, opts) {
118
118
  }
119
119
  }
120
120
 
121
- let sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
121
+ let sdl = ''
122
+ try {
123
+ sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
124
+ } catch (error) {
125
+ // The next lines are excluded from test coverage:
126
+ // it's quite hard to test the following lines for all of the DB types
127
+ /* istanbul ignore next */
128
+ app.log.debug({ query, mutation, subscription }, 'GraphQL input schema')
129
+ /* istanbul ignore next */
130
+ const newError = new Error('Error printing the GraphQL schema')
131
+ /* istanbul ignore next */
132
+ newError.cause = error
133
+ /* istanbul ignore next */
134
+ throw newError
135
+ }
122
136
 
123
137
  if (opts.federationMetadata) {
124
138
  for (const replacement of federationReplacements) {
@@ -50,10 +50,21 @@ function constructGraph (app, entity, opts, ignore) {
50
50
  /* istanbul ignore next */
51
51
  if (field.enum) {
52
52
  const enumValues = field.enum.reduce((acc, enumValue) => {
53
- acc[enumValue] = { value: enumValue }
53
+ const valueStartsWithLetterOrUnderscore = !!enumValue.match(/^[_a-zA-Z]/g)
54
+ if (!valueStartsWithLetterOrUnderscore) {
55
+ enumValue = `_${enumValue}`
56
+ }
57
+
58
+ acc[enumValue.replace(/[^\w\s]/g, '_')] = { value: enumValue }
54
59
  return acc
55
60
  }, {})
56
- meta.type = new graphql.GraphQLEnumType({ name: key, values: enumValues })
61
+ try {
62
+ const name = camelcase([entityName, key])
63
+ meta.type = new graphql.GraphQLEnumType({ name, values: enumValues })
64
+ } catch (error) {
65
+ app.log.error({ key, enumValues, entityName, table: entity.table, schema: entity.schema })
66
+ throw new Error('Unable to generate GraphQLEnumType')
67
+ }
57
68
  } else {
58
69
  meta.type = sqlTypeToGraphQL(field.sqlType)
59
70
  }
@@ -281,11 +292,14 @@ function constructGraph (app, entity, opts, ignore) {
281
292
  async function loadMany (keys, queries, ctx) {
282
293
  const fields = getFields(queries)
283
294
 
295
+ let limit = 0
296
+
284
297
  // TODO this is inefficient as it might load
285
298
  // more data than needed if there are more than
286
299
  // one primary key
287
300
  const where = keys.reduce((acc, pairs) => {
288
301
  pairs.reduce((acc, { key, value }) => {
302
+ limit++
289
303
  if (acc[key]) {
290
304
  acc[key].in.push(value)
291
305
  } else {
@@ -301,6 +315,7 @@ function constructGraph (app, entity, opts, ignore) {
301
315
  const res = await entity.find({
302
316
  where,
303
317
  fields,
318
+ limit,
304
319
  ctx
305
320
  })
306
321
 
@@ -9,20 +9,20 @@ const {
9
9
  const assert = require('assert')
10
10
 
11
11
  module.exports = function establishRelations (app, relations, resolvers, loaders, queryTopFields, relationships, metaMap) {
12
- const tablesTypeMap = {}
12
+ const entitiesTypeMap = {}
13
13
  const entities = app.platformatic.entities
14
14
  for (const key of Object.keys(entities)) {
15
- const entity = entities[key]
16
- tablesTypeMap[entity.table] = metaMap.get(entity)
15
+ entitiesTypeMap[key] = metaMap.get(entities[key])
17
16
  }
18
- for (const { table_name, foreign_table_name, column_name, foreign_column_name } of relations) {
17
+ for (const relation of relations) {
18
+ const { table_name, foreign_table_name, column_name, foreign_column_name, entityName, foreignEntityName, loweredTableWithSchemaName } = relation
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}`)
22
22
  assert(foreign_table_name, `foreign_table_name is required ${enhanceAssertLogMsg}`)
23
23
 
24
- const current = tablesTypeMap[table_name]
25
- const foreign = tablesTypeMap[foreign_table_name]
24
+ const current = entitiesTypeMap[entityName]
25
+ const foreign = entitiesTypeMap[foreignEntityName]
26
26
  assert(foreign !== undefined, `No foreign table named "${foreign_table_name}" was found ${enhanceAssertLogMsg}`)
27
27
 
28
28
  // current to foreign, we skip this if the foreign table has a composite primary key
@@ -54,7 +54,7 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
54
54
  // foreign to current, we skip this if the current table has a composite primary key
55
55
  // TODO implement support for this case
56
56
  if (current.entity.primaryKeys.size === 1) {
57
- const lowered = lowerCaseFirst(camelcase(current.entity.table))
57
+ const lowered = loweredTableWithSchemaName
58
58
  if (!relationships[foreign.type] || relationships[foreign.type][lowered] !== false) {
59
59
  foreign.fields[lowered] = queryTopFields[lowered]
60
60
  resolvers[foreign.type] = resolvers[foreign.type] || {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-graphql",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Map SQL dbs to GraphQL",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -14,23 +14,23 @@
14
14
  },
15
15
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "@platformatic/sql-events": "0.10.0",
18
- "@platformatic/sql-mapper": "0.10.0",
19
- "fastify": "^4.6.0",
17
+ "fastify": "^4.10.2",
20
18
  "snazzy": "^9.0.0",
21
19
  "standard": "^17.0.0",
22
- "tap": "^16.0.0",
20
+ "tap": "^16.3.2",
23
21
  "tsd": "^0.25.0",
24
- "ws": "^8.9.0"
22
+ "ws": "^8.11.0",
23
+ "@platformatic/sql-events": "0.12.0",
24
+ "@platformatic/sql-mapper": "0.12.0"
25
25
  },
26
26
  "dependencies": {
27
- "camelcase": "^6.0.0",
28
- "fastify-plugin": "^4.1.0",
27
+ "camelcase": "^6.3.0",
28
+ "fastify-plugin": "^4.4.0",
29
29
  "graphql": "^16.6.0",
30
- "graphql-scalars": "^1.13.1",
30
+ "graphql-scalars": "^1.20.1",
31
31
  "graphql-type-json": "^0.3.2",
32
32
  "inflected": "^2.1.0",
33
- "mercurius": "^11.2.0"
33
+ "mercurius": "^11.4.0"
34
34
  },
35
35
  "tsd": {
36
36
  "directory": "test/types"
@@ -0,0 +1,517 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { clear, connInfo, isSQLite, isMysql } = require('./helper')
8
+ const { setTimeout } = require('timers/promises')
9
+
10
+ async function createBasicPages (db, sql) {
11
+ if (isSQLite) {
12
+ await db.query(sql`CREATE TABLE pages (
13
+ id INTEGER PRIMARY KEY,
14
+ title VARCHAR(42),
15
+ created_at TIMESTAMP,
16
+ updated_at TIMESTAMP
17
+ );`)
18
+ } else if (isMysql) {
19
+ await db.query(sql`CREATE TABLE pages (
20
+ id SERIAL PRIMARY KEY,
21
+ title VARCHAR(42),
22
+ created_at TIMESTAMP NULL DEFAULT NULL,
23
+ updated_at TIMESTAMP NULL DEFAULT NULL
24
+ );`)
25
+ } else {
26
+ await db.query(sql`CREATE TABLE pages (
27
+ id SERIAL PRIMARY KEY,
28
+ title VARCHAR(42),
29
+ created_at TIMESTAMP,
30
+ updated_at TIMESTAMP
31
+ );`)
32
+ }
33
+ }
34
+
35
+ test('created_at updated_at happy path', async ({ pass, teardown, same, equal, not, comment }) => {
36
+ const app = fastify()
37
+ app.register(sqlMapper, {
38
+ ...connInfo,
39
+ async onDatabaseLoad (db, sql) {
40
+ pass('onDatabaseLoad called')
41
+
42
+ await clear(db, sql)
43
+ await createBasicPages(db, sql)
44
+ }
45
+ })
46
+ app.register(sqlGraphQL)
47
+ teardown(app.close.bind(app))
48
+
49
+ await app.ready()
50
+
51
+ let original
52
+
53
+ {
54
+ const res = await app.inject({
55
+ method: 'POST',
56
+ url: '/graphql',
57
+ body: {
58
+ query: `
59
+ mutation {
60
+ savePage(input: { title: "Hello" }) {
61
+ id
62
+ title
63
+ createdAt
64
+ updatedAt
65
+ }
66
+ }
67
+ `
68
+ }
69
+ })
70
+ equal(res.statusCode, 200, 'savePage status code')
71
+ const data = res.json().data
72
+ not(data.savePage.createdAt, null, 'createdAt')
73
+ not(data.savePage.updatedAt, null, 'updatedAt')
74
+ comment(`createdAt: ${data.savePage.createdAt}`)
75
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
76
+ original = data.savePage
77
+ }
78
+
79
+ {
80
+ const res = await app.inject({
81
+ method: 'POST',
82
+ url: '/graphql',
83
+ body: {
84
+ query: `
85
+ query {
86
+ getPageById(id: 1) {
87
+ id
88
+ title
89
+ createdAt
90
+ updatedAt
91
+ }
92
+ }
93
+ `
94
+ }
95
+ })
96
+ equal(res.statusCode, 200, 'pages status code')
97
+ const data = res.json().data
98
+ equal(data.getPageById.createdAt, original.createdAt, 'createdAt')
99
+ equal(data.getPageById.updatedAt, original.updatedAt, 'updatedAt')
100
+ comment(`createdAt: ${data.getPageById.createdAt}`)
101
+ comment(`updatedAt: ${data.getPageById.updatedAt}`)
102
+ }
103
+
104
+ await setTimeout(1000) // await 1s
105
+
106
+ let updated
107
+ {
108
+ const res = await app.inject({
109
+ method: 'POST',
110
+ url: '/graphql',
111
+ body: {
112
+ query: `
113
+ mutation {
114
+ savePage(input: { id: 1, title: "Hello World" }) {
115
+ id
116
+ title
117
+ createdAt
118
+ updatedAt
119
+ }
120
+ }
121
+ `
122
+ }
123
+ })
124
+ const data = res.json().data
125
+ equal(data.savePage.createdAt, original.createdAt, 'createdAt')
126
+ not(data.savePage.updatedAt, original.updatedAt, 'updatedAt')
127
+ updated = data.savePage
128
+ comment(`createdAt: ${data.savePage.createdAt}`)
129
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
130
+ }
131
+
132
+ {
133
+ const res = await app.inject({
134
+ method: 'POST',
135
+ url: '/graphql',
136
+ body: {
137
+ query: `
138
+ query {
139
+ getPageById(id: 1) {
140
+ id
141
+ title
142
+ createdAt
143
+ updatedAt
144
+ }
145
+ }
146
+ `
147
+ }
148
+ })
149
+ equal(res.statusCode, 200, 'pages status code')
150
+ const data = res.json().data
151
+ equal(data.getPageById.createdAt, updated.createdAt, 'createdAt')
152
+ equal(data.getPageById.updatedAt, updated.updatedAt, 'updatedAt')
153
+ comment(`createdAt: ${data.getPageById.createdAt}`)
154
+ comment(`updatedAt: ${data.getPageById.updatedAt}`)
155
+ }
156
+ })
157
+
158
+ test('cannot set created_at', async ({ pass, teardown, same, equal, not, comment }) => {
159
+ const app = fastify()
160
+ app.register(sqlMapper, {
161
+ ...connInfo,
162
+ async onDatabaseLoad (db, sql) {
163
+ pass('onDatabaseLoad called')
164
+
165
+ await clear(db, sql)
166
+ await createBasicPages(db, sql)
167
+ }
168
+ })
169
+ app.register(sqlGraphQL)
170
+ teardown(app.close.bind(app))
171
+
172
+ await app.ready()
173
+
174
+ {
175
+ const res = await app.inject({
176
+ method: 'POST',
177
+ url: '/graphql',
178
+ body: {
179
+ query: `
180
+ mutation {
181
+ savePage(input: { title: "Hello", createdAt: "${new Date().toISOString()}" }) {
182
+ id
183
+ title
184
+ createdAt
185
+ updatedAt
186
+ }
187
+ }
188
+ `
189
+ }
190
+ })
191
+ equal(res.statusCode, 400, 'savePage status code')
192
+ const data = res.json()
193
+ equal(data.errors[0].message, 'Field "createdAt" is not defined by type "PageInput".')
194
+ }
195
+ })
196
+
197
+ test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
198
+ const app = fastify()
199
+ app.register(sqlMapper, {
200
+ ...connInfo,
201
+ async onDatabaseLoad (db, sql) {
202
+ pass('onDatabaseLoad called')
203
+
204
+ await clear(db, sql)
205
+ await createBasicPages(db, sql)
206
+ }
207
+ })
208
+ app.register(sqlGraphQL)
209
+ teardown(app.close.bind(app))
210
+
211
+ await app.ready()
212
+
213
+ {
214
+ const res = await app.inject({
215
+ method: 'POST',
216
+ url: '/graphql',
217
+ body: {
218
+ query: `
219
+ mutation {
220
+ savePage(input: { title: "Hello" }) {
221
+ id
222
+ title
223
+ createdAt
224
+ updatedAt
225
+ }
226
+ }
227
+ `
228
+ }
229
+ })
230
+ equal(res.statusCode, 200, 'savePage status code')
231
+ const data = res.json().data
232
+ not(data.savePage.createdAt, null, 'createdAt')
233
+ not(data.savePage.updatedAt, null, 'updatedAt')
234
+ comment(`createdAt: ${data.savePage.createdAt}`)
235
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
236
+ }
237
+
238
+ {
239
+ const res = await app.inject({
240
+ method: 'POST',
241
+ url: '/graphql',
242
+ body: {
243
+ query: `
244
+ mutation {
245
+ savePage(input: { id: 1, title: "Hello World", updatedAt: "${new Date().toISOString()}" }) {
246
+ id
247
+ title
248
+ createdAt
249
+ updatedAt
250
+ }
251
+ }
252
+ `
253
+ }
254
+ })
255
+ equal(res.statusCode, 400, 'savePage status code')
256
+ const data = res.json()
257
+ equal(data.errors[0].message, 'Field "updatedAt" is not defined by type "PageInput".')
258
+ }
259
+ })
260
+
261
+ test('do not assign created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
262
+ const app = fastify()
263
+ app.register(sqlMapper, {
264
+ ...connInfo,
265
+ autoTimestamp: false,
266
+ async onDatabaseLoad (db, sql) {
267
+ pass('onDatabaseLoad called')
268
+
269
+ await clear(db, sql)
270
+ await createBasicPages(db, sql)
271
+ }
272
+ })
273
+ app.register(sqlGraphQL)
274
+ teardown(app.close.bind(app))
275
+
276
+ await app.ready()
277
+
278
+ {
279
+ const res = await app.inject({
280
+ method: 'POST',
281
+ url: '/graphql',
282
+ body: {
283
+ query: `
284
+ mutation {
285
+ savePage(input: { title: "Hello" }) {
286
+ id
287
+ title
288
+ createdAt
289
+ updatedAt
290
+ }
291
+ }
292
+ `
293
+ }
294
+ })
295
+ equal(res.statusCode, 200, 'savePage status code')
296
+ const data = res.json().data
297
+ equal(data.savePage.createdAt, null, 'createdAt')
298
+ equal(data.savePage.updatedAt, null, 'updatedAt')
299
+ }
300
+
301
+ {
302
+ const res = await app.inject({
303
+ method: 'POST',
304
+ url: '/graphql',
305
+ body: {
306
+ query: `
307
+ query {
308
+ getPageById(id: 1) {
309
+ id
310
+ title
311
+ createdAt
312
+ updatedAt
313
+ }
314
+ }
315
+ `
316
+ }
317
+ })
318
+ equal(res.statusCode, 200, 'pages status code')
319
+ const data = res.json().data
320
+ equal(data.getPageById.createdAt, null, 'createdAt')
321
+ equal(data.getPageById.updatedAt, null, 'updatedAt')
322
+ }
323
+
324
+ {
325
+ const res = await app.inject({
326
+ method: 'POST',
327
+ url: '/graphql',
328
+ body: {
329
+ query: `
330
+ mutation {
331
+ savePage(input: { id: 1, title: "Hello World" }) {
332
+ id
333
+ title
334
+ createdAt
335
+ updatedAt
336
+ }
337
+ }
338
+ `
339
+ }
340
+ })
341
+ const data = res.json().data
342
+ equal(data.savePage.createdAt, null, 'createdAt')
343
+ equal(data.savePage.updatedAt, null, 'updatedAt')
344
+ }
345
+
346
+ {
347
+ const res = await app.inject({
348
+ method: 'POST',
349
+ url: '/graphql',
350
+ body: {
351
+ query: `
352
+ query {
353
+ getPageById(id: 1) {
354
+ id
355
+ title
356
+ createdAt
357
+ updatedAt
358
+ }
359
+ }
360
+ `
361
+ }
362
+ })
363
+ equal(res.statusCode, 200, 'pages status code')
364
+ const data = res.json().data
365
+ equal(data.getPageById.createdAt, null, 'createdAt')
366
+ equal(data.getPageById.updatedAt, null, 'updatedAt')
367
+ }
368
+ })
369
+
370
+ test('bulk insert adds created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
371
+ const app = fastify()
372
+ app.register(sqlMapper, {
373
+ ...connInfo,
374
+ async onDatabaseLoad (db, sql) {
375
+ pass('onDatabaseLoad called')
376
+
377
+ await clear(db, sql)
378
+ await createBasicPages(db, sql)
379
+ }
380
+ })
381
+ app.register(sqlGraphQL)
382
+ teardown(app.close.bind(app))
383
+
384
+ await app.ready()
385
+
386
+ {
387
+ const res = await app.inject({
388
+ method: 'POST',
389
+ url: '/graphql',
390
+ body: {
391
+ query: `
392
+ mutation {
393
+ savePage(input: { title: "Hello" }) {
394
+ id
395
+ title
396
+ createdAt
397
+ updatedAt
398
+ }
399
+ }
400
+ `
401
+ }
402
+ })
403
+ equal(res.statusCode, 200, 'savePage status code')
404
+ const data = res.json().data
405
+ not(data.savePage.createdAt, null, 'createdAt')
406
+ not(data.savePage.updatedAt, null, 'updatedAt')
407
+ }
408
+
409
+ {
410
+ const res = await app.inject({
411
+ method: 'POST',
412
+ url: '/graphql',
413
+ body: {
414
+ query: `
415
+ mutation batch($inputs : [PageInput]!) {
416
+ insertPages (inputs: $inputs) {
417
+ id
418
+ title
419
+ createdAt
420
+ updatedAt
421
+ }
422
+ }
423
+ `,
424
+ variables: {
425
+ inputs: [
426
+ { title: 'Page 1' },
427
+ { title: 'Page 2' },
428
+ { title: 'Page 3' }
429
+ ]
430
+ }
431
+ }
432
+ })
433
+ equal(res.statusCode, 200, 'savePage status code')
434
+ const pages = res.json().data.insertPages
435
+ for (const page of pages) {
436
+ not(page.createdAt, null, 'createdAt')
437
+ not(page.updatedAt, null, 'updatedAt')
438
+ equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
439
+ }
440
+ }
441
+
442
+ {
443
+ const res = await app.inject({
444
+ method: 'POST',
445
+ url: '/graphql',
446
+ body: {
447
+ query: `
448
+ query {
449
+ pages {
450
+ id
451
+ title
452
+ createdAt
453
+ updatedAt
454
+ }
455
+ }
456
+ `
457
+ }
458
+ })
459
+ equal(res.statusCode, 200, 'pages status code')
460
+ const pages = res.json().data.pages
461
+ for (const page of pages) {
462
+ not(page.createdAt, null, 'createdAt')
463
+ not(page.updatedAt, null, 'updatedAt')
464
+ equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
465
+ }
466
+ }
467
+ })
468
+
469
+ test('bulk insert with autoTimestamp=false do not had created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
470
+ const app = fastify()
471
+ app.register(sqlMapper, {
472
+ ...connInfo,
473
+ autoTimestamp: false,
474
+ async onDatabaseLoad (db, sql) {
475
+ pass('onDatabaseLoad called')
476
+
477
+ await clear(db, sql)
478
+ await createBasicPages(db, sql)
479
+ }
480
+ })
481
+ app.register(sqlGraphQL)
482
+ teardown(app.close.bind(app))
483
+
484
+ await app.ready()
485
+
486
+ {
487
+ const res = await app.inject({
488
+ method: 'POST',
489
+ url: '/graphql',
490
+ body: {
491
+ query: `
492
+ mutation batch($inputs : [PageInput]!) {
493
+ insertPages (inputs: $inputs) {
494
+ id
495
+ title
496
+ createdAt
497
+ updatedAt
498
+ }
499
+ }
500
+ `,
501
+ variables: {
502
+ inputs: [
503
+ { title: 'Page 1' },
504
+ { title: 'Page 2' },
505
+ { title: 'Page 3' }
506
+ ]
507
+ }
508
+ }
509
+ })
510
+ equal(res.statusCode, 200, 'savePage status code')
511
+ const pages = res.json().data.insertPages
512
+ for (const page of pages) {
513
+ equal(page.createdAt, null, 'createdAt')
514
+ equal(page.updatedAt, null, 'updatedAt')
515
+ }
516
+ }
517
+ })
@@ -0,0 +1,100 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { clear, connInfo, isSQLite, isPg } = require('./helper')
8
+
9
+ test('should properly setup the enum types', { skip: isSQLite }, async ({ pass, teardown, same }) => {
10
+ const app = fastify()
11
+ app.register(sqlMapper, {
12
+ ...connInfo,
13
+ async onDatabaseLoad (db, sql) {
14
+ pass('onDatabaseLoad called')
15
+
16
+ await clear(db, sql)
17
+
18
+ if (isPg) {
19
+ await db.query(sql`
20
+ CREATE TYPE custom_enum AS ENUM('1', '2', '3');
21
+
22
+ CREATE TABLE enum_tests (
23
+ id INTEGER NOT NULL,
24
+ test_enum custom_enum,
25
+ PRIMARY KEY (id)
26
+ );`)
27
+ } else {
28
+ await db.query(sql`
29
+ CREATE TABLE enum_tests (
30
+ id INTEGER NOT NULL,
31
+ test_enum ENUM ('1', '2', '3') DEFAULT NULL,
32
+ PRIMARY KEY (id)
33
+ );`)
34
+ }
35
+ }
36
+ })
37
+
38
+ app.register(sqlGraphQL)
39
+ teardown(app.close.bind(app))
40
+
41
+ try {
42
+ await app.ready()
43
+ same(true, true, 'Enum values are properly interpreted')
44
+ } catch (err) {
45
+ console.log(err)
46
+ same(true, false, 'Previous call should never fail')
47
+ }
48
+ })
49
+
50
+ test('should not fail if tables have duplicate enum names', { skip: isSQLite }, async ({ pass, teardown, same }) => {
51
+ const app = fastify()
52
+ app.register(sqlMapper, {
53
+ ...connInfo,
54
+ async onDatabaseLoad (db, sql) {
55
+ pass('onDatabaseLoad called')
56
+
57
+ await clear(db, sql)
58
+
59
+ if (isPg) {
60
+ await db.query(sql`
61
+ CREATE TYPE custom_enum AS ENUM('1', '2', '3');
62
+ CREATE TYPE simple_enum AS ENUM('4', '5', '6');
63
+
64
+ CREATE TABLE enum_tests (
65
+ id INTEGER NOT NULL,
66
+ test_enum custom_enum,
67
+ PRIMARY KEY (id)
68
+ );
69
+ CREATE TABLE simple_types (
70
+ pk INTEGER NOT NULL,
71
+ test_enum simple_enum,
72
+ PRIMARY KEY (pk)
73
+ );`)
74
+ } else {
75
+ await db.query(sql`
76
+ CREATE TABLE enum_tests (
77
+ id INTEGER NOT NULL,
78
+ test_enum ENUM ('1', '2', '3') DEFAULT NULL,
79
+ PRIMARY KEY (id)
80
+ );
81
+ CREATE TABLE simple_types (
82
+ pk INTEGER NOT NULL,
83
+ test_enum ENUM ('4', '5', '6') DEFAULT NULL,
84
+ PRIMARY KEY (pk)
85
+ );`)
86
+ }
87
+ }
88
+ })
89
+
90
+ app.register(sqlGraphQL)
91
+ teardown(app.close.bind(app))
92
+
93
+ try {
94
+ await app.ready()
95
+ same(true, true, 'test_enum is used twice but app not throws')
96
+ } catch (err) {
97
+ console.log(err)
98
+ same(true, false, 'Previous call should never fail')
99
+ }
100
+ })
package/test/helper.js CHANGED
@@ -96,4 +96,54 @@ module.exports.clear = async function (db, sql) {
96
96
  await db.query(sql`DROP Type simple_enum`)
97
97
  } catch (err) {
98
98
  }
99
+
100
+ try {
101
+ await db.query(sql`DROP TABLE "myschema"."mytable"`)
102
+ } catch (err) {
103
+ }
104
+
105
+ try {
106
+ await db.query(sql`DROP TABLE "myschema"."template"`)
107
+ } catch (err) {
108
+ }
109
+
110
+ try {
111
+ await db.query(sql`DROP SCHEMA "myschema"`)
112
+ } catch (err) {
113
+ }
114
+
115
+ try {
116
+ await db.query(sql`DROP TABLE generated_test`)
117
+ } catch (err) {
118
+ }
119
+
120
+ try {
121
+ await db.query(sql`DROP TABLE enum_tests`)
122
+ } catch (err) {
123
+ }
124
+
125
+ try {
126
+ await db.query(sql`DROP TYPE custom_enum`)
127
+ } catch (err) {
128
+ }
129
+
130
+ try {
131
+ await db.query(sql`DROP TABLE test2.books`)
132
+ } catch (err) {
133
+ }
134
+
135
+ try {
136
+ await db.query(sql`DROP TABLE test1.authors`)
137
+ } catch (err) {
138
+ }
139
+
140
+ try {
141
+ await db.query(sql`DROP SCHEMA test2`)
142
+ } catch (err) {
143
+ }
144
+
145
+ try {
146
+ await db.query(sql`DROP SCHEMA test1`)
147
+ } catch (err) {
148
+ }
99
149
  }
@@ -36,6 +36,10 @@ test('inserted_at updated_at happy path', async ({ pass, teardown, same, equal,
36
36
  const app = fastify()
37
37
  app.register(sqlMapper, {
38
38
  ...connInfo,
39
+ autoTimestamp: {
40
+ createdAt: 'inserted_at',
41
+ updatedAt: 'updated_at'
42
+ },
39
43
  async onDatabaseLoad (db, sql) {
40
44
  pass('onDatabaseLoad called')
41
45
 
@@ -159,6 +163,10 @@ test('cannot set inserted_at', async ({ pass, teardown, same, equal, not, commen
159
163
  const app = fastify()
160
164
  app.register(sqlMapper, {
161
165
  ...connInfo,
166
+ autoTimestamp: {
167
+ createdAt: 'inserted_at',
168
+ updatedAt: 'updated_at'
169
+ },
162
170
  async onDatabaseLoad (db, sql) {
163
171
  pass('onDatabaseLoad called')
164
172
 
@@ -198,6 +206,10 @@ test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment
198
206
  const app = fastify()
199
207
  app.register(sqlMapper, {
200
208
  ...connInfo,
209
+ autoTimestamp: {
210
+ createdAt: 'inserted_at',
211
+ updatedAt: 'updated_at'
212
+ },
201
213
  async onDatabaseLoad (db, sql) {
202
214
  pass('onDatabaseLoad called')
203
215
 
@@ -371,6 +383,10 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
371
383
  const app = fastify()
372
384
  app.register(sqlMapper, {
373
385
  ...connInfo,
386
+ autoTimestamp: {
387
+ createdAt: 'inserted_at',
388
+ updatedAt: 'updated_at'
389
+ },
374
390
  async onDatabaseLoad (db, sql) {
375
391
  pass('onDatabaseLoad called')
376
392
 
@@ -466,7 +482,7 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
466
482
  }
467
483
  })
468
484
 
469
- test('bulk insert with autoTimestamp=false do not hadd inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
485
+ test('bulk insert with autoTimestamp=false do not had inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
470
486
  const app = fastify()
471
487
  app.register(sqlMapper, {
472
488
  ...connInfo,
@@ -258,6 +258,161 @@ test('nested resolver', async ({ pass, teardown, same, equal }) => {
258
258
  }
259
259
  })
260
260
 
261
+ test('nested resolver with more of 10 rows in nested entity', async ({ pass, teardown, same, equal }) => {
262
+ const app = fastify()
263
+ app.register(sqlMapper, {
264
+ ...connInfo,
265
+ async onDatabaseLoad (db, sql) {
266
+ pass('onDatabaseLoad called')
267
+
268
+ await clear(db, sql)
269
+
270
+ if (isMysql) {
271
+ await db.query(sql`
272
+ CREATE TABLE categories (
273
+ id SERIAL PRIMARY KEY,
274
+ name VARCHAR(255)
275
+ );
276
+ CREATE TABLE pages (
277
+ id SERIAL PRIMARY KEY,
278
+ title VARCHAR(42),
279
+ category_id BIGINT UNSIGNED,
280
+ FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
281
+ );
282
+ `)
283
+ } else if (isSQLite) {
284
+ await db.query(sql`
285
+ CREATE TABLE categories (
286
+ id INTEGER PRIMARY KEY,
287
+ name VARCHAR(42)
288
+ );
289
+ `)
290
+ await db.query(sql`
291
+ CREATE TABLE pages (
292
+ id INTEGER PRIMARY KEY,
293
+ title VARCHAR(42),
294
+ category_id INTEGER REFERENCES categories(id)
295
+ );
296
+ `)
297
+ } else {
298
+ await db.query(sql`
299
+ CREATE TABLE categories (
300
+ id SERIAL PRIMARY KEY,
301
+ name VARCHAR(42)
302
+ );
303
+ CREATE TABLE pages (
304
+ id SERIAL PRIMARY KEY,
305
+ title VARCHAR(42),
306
+ category_id INTEGER REFERENCES categories(id)
307
+ );
308
+ `)
309
+ }
310
+ }
311
+ })
312
+ app.register(sqlGraphQL)
313
+ teardown(app.close.bind(app))
314
+
315
+ const categories = [
316
+ { name: 'Category 01' },
317
+ { name: 'Category 02' },
318
+ { name: 'Category 03' },
319
+ { name: 'Category 04' },
320
+ { name: 'Category 05' },
321
+ { name: 'Category 06' },
322
+ { name: 'Category 07' },
323
+ { name: 'Category 08' },
324
+ { name: 'Category 09' },
325
+ { name: 'Category 10' },
326
+ { name: 'Category 11' }
327
+ ]
328
+ await app.inject({
329
+ method: 'POST',
330
+ url: '/graphql',
331
+ body: {
332
+ query: `
333
+ mutation batch($inputs: [CategoryInput]!) {
334
+ insertCategories(inputs: $inputs) {
335
+ id
336
+ name
337
+ }
338
+ }
339
+ `,
340
+ variables: {
341
+ inputs: categories
342
+ }
343
+ }
344
+ })
345
+
346
+ const pages = [
347
+ { title: 'Page 01', categoryId: 1 },
348
+ { title: 'Page 02', categoryId: 2 },
349
+ { title: 'Page 03', categoryId: 3 },
350
+ { title: 'Page 04', categoryId: 4 },
351
+ { title: 'Page 05', categoryId: 5 },
352
+ { title: 'Page 06', categoryId: 6 },
353
+ { title: 'Page 07', categoryId: 7 },
354
+ { title: 'Page 08', categoryId: 8 },
355
+ { title: 'Page 09', categoryId: 9 },
356
+ { title: 'Page 10', categoryId: 10 },
357
+ { title: 'Page 11', categoryId: 11 }
358
+ ]
359
+ await app.inject({
360
+ method: 'POST',
361
+ url: '/graphql',
362
+ body: {
363
+ query: `
364
+ mutation batch($inputs: [PageInput]!) {
365
+ insertPages(inputs: $inputs) {
366
+ id
367
+ title
368
+ }
369
+ }
370
+ `,
371
+ variables: {
372
+ inputs: pages
373
+ }
374
+ }
375
+ })
376
+
377
+ {
378
+ const res = await app.inject({
379
+ method: 'POST',
380
+ url: '/graphql',
381
+ body: {
382
+ query: `
383
+ query {
384
+ pages(limit: 100) {
385
+ id
386
+ title
387
+ category {
388
+ id
389
+ }
390
+ }
391
+ }
392
+ `
393
+ }
394
+ })
395
+ equal(res.statusCode, 200, 'pages.category status code')
396
+ same(res.json(), {
397
+ data: {
398
+ pages: [
399
+ { id: 1, title: 'Page 01', category: { id: 1 } },
400
+ { id: 2, title: 'Page 02', category: { id: 2 } },
401
+ { id: 3, title: 'Page 03', category: { id: 3 } },
402
+ { id: 4, title: 'Page 04', category: { id: 4 } },
403
+ { id: 5, title: 'Page 05', category: { id: 5 } },
404
+ { id: 6, title: 'Page 06', category: { id: 6 } },
405
+ { id: 7, title: 'Page 07', category: { id: 7 } },
406
+ { id: 8, title: 'Page 08', category: { id: 8 } },
407
+ { id: 9, title: 'Page 09', category: { id: 9 } },
408
+ { id: 10, title: 'Page 10', category: { id: 10 } },
409
+ { id: 11, title: 'Page 11', category: { id: 11 } }
410
+ ]
411
+ }
412
+ }, 'pages.category response')
413
+ }
414
+ })
415
+
261
416
  test('disable one-too-many', async ({ pass, teardown, same, equal, match }) => {
262
417
  const app = fastify()
263
418
  app.register(sqlMapper, {
@@ -0,0 +1,167 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { isSQLite, connInfo, isMysql, clear } = require('./helper')
8
+
9
+ test('should handle relationships with different schemas', { skip: isSQLite }, async ({ pass, teardown, same, equal }) => {
10
+ const app = fastify()
11
+ app.register(sqlMapper, {
12
+ ...connInfo,
13
+ schema: ['test1', 'test2'],
14
+ async onDatabaseLoad (db, sql) {
15
+ pass('onDatabaseLoad called')
16
+
17
+ await clear(db, sql)
18
+
19
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
20
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test2;`)
21
+ if (isMysql) {
22
+ await db.query(sql`
23
+ CREATE TABLE test1.authors (
24
+ id SERIAL PRIMARY KEY,
25
+ name VARCHAR(255)
26
+ );`)
27
+ await db.query(sql`
28
+ CREATE TABLE test2.books (
29
+ id SERIAL PRIMARY KEY,
30
+ title VARCHAR(42),
31
+ author_id BIGINT UNSIGNED,
32
+ FOREIGN KEY(author_id) REFERENCES test1.authors(id) ON DELETE CASCADE
33
+ );
34
+ `)
35
+ } else {
36
+ await db.query(sql`
37
+ CREATE TABLE test1.authors (
38
+ id SERIAL PRIMARY KEY,
39
+ name VARCHAR(42)
40
+ );
41
+ CREATE TABLE test2.books (
42
+ id SERIAL PRIMARY KEY,
43
+ title VARCHAR(42),
44
+ author_id INTEGER REFERENCES test1.authors(id)
45
+ );
46
+ `)
47
+ }
48
+ }
49
+ })
50
+ app.register(sqlGraphQL)
51
+ teardown(app.close.bind(app))
52
+
53
+ await app.ready()
54
+
55
+ const authors = [{
56
+ id: 1,
57
+ name: 'Mark'
58
+ }]
59
+
60
+ const books = [{
61
+ id: 1,
62
+ title: 'Harry',
63
+ authorId: 1
64
+ }]
65
+
66
+ {
67
+ const res = await app.inject({
68
+ method: 'POST',
69
+ url: '/graphql',
70
+ body: {
71
+ query: `
72
+ mutation batch($inputs : [Test1AuthorInput]!) {
73
+ insertTest1Authors(inputs: $inputs) {
74
+ id
75
+ name
76
+ }
77
+ }
78
+ `,
79
+ variables: {
80
+ inputs: authors
81
+ }
82
+ }
83
+ })
84
+ equal(res.statusCode, 200, 'authors status code')
85
+ }
86
+
87
+ {
88
+ const res = await app.inject({
89
+ method: 'POST',
90
+ url: '/graphql',
91
+ body: {
92
+ query: `
93
+ mutation batch($inputs : [Test2BookInput]!) {
94
+ insertTest2Books(inputs: $inputs) {
95
+ id
96
+ title
97
+ }
98
+ }
99
+ `,
100
+ variables: {
101
+ inputs: books
102
+ }
103
+ }
104
+ })
105
+ equal(res.statusCode, 200, 'books status code')
106
+ }
107
+
108
+ {
109
+ const res = await app.inject({
110
+ method: 'POST',
111
+ url: '/graphql',
112
+ body: {
113
+ query: `
114
+ query {
115
+ test2Books {
116
+ id
117
+ author {
118
+ id
119
+ }
120
+ }
121
+ }
122
+ `
123
+ }
124
+ })
125
+ equal(res.statusCode, 200, 'query books')
126
+ same(res.json(), {
127
+ data: {
128
+ test2Books: [{
129
+ id: 1,
130
+ author: {
131
+ id: 1
132
+ }
133
+ }]
134
+ }
135
+ }, 'query book response')
136
+ }
137
+
138
+ {
139
+ const res = await app.inject({
140
+ method: 'POST',
141
+ url: '/graphql',
142
+ body: {
143
+ query: `
144
+ query {
145
+ test1Authors {
146
+ id
147
+ test2Books {
148
+ id
149
+ }
150
+ }
151
+ }
152
+ `
153
+ }
154
+ })
155
+ equal(res.statusCode, 200, 'query authors')
156
+ same(res.json(), {
157
+ data: {
158
+ test1Authors: [{
159
+ id: 1,
160
+ test2Books: [{
161
+ id: 1
162
+ }]
163
+ }]
164
+ }
165
+ }, 'query authors response')
166
+ }
167
+ })
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { clear, connInfo, isSQLite, isPg } = require('./helper')
8
+
9
+ test('should correctly get the special characters', { skip: isSQLite }, async ({ pass, teardown, same }) => {
10
+ const app = fastify()
11
+ app.register(sqlMapper, {
12
+ ...connInfo,
13
+ async onDatabaseLoad (db, sql) {
14
+ pass('onDatabaseLoad called')
15
+
16
+ await clear(db, sql)
17
+
18
+ if (isPg) {
19
+ await db.query(sql`
20
+ CREATE TYPE custom_enum AS ENUM ('application/pdf', 'image/jpeg', 'image/png');
21
+ CREATE TABLE enum_tests (
22
+ id INTEGER NOT NULL,
23
+ test_enum custom_enum,
24
+ PRIMARY KEY (id)
25
+ );`)
26
+ } else {
27
+ await db.query(sql`
28
+ CREATE TABLE enum_tests (
29
+ id INTEGER NOT NULL,
30
+ test_enum ENUM ('application/pdf', 'image/jpeg', 'image/png'),
31
+ PRIMARY KEY (id)
32
+ );`)
33
+ }
34
+ }
35
+ })
36
+
37
+ app.register(sqlGraphQL)
38
+ teardown(app.close.bind(app))
39
+
40
+ try {
41
+ await app.ready()
42
+ same(true, true, 'Special characters are properly configured')
43
+ } catch (err) {
44
+ console.log(err)
45
+ same(true, false, 'Previous call should never fail')
46
+ }
47
+ })