@platformatic/sql-mapper 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.
@@ -1,12 +1,14 @@
1
1
  'use strict'
2
2
 
3
+ const { tableName } = require('../utils')
4
+
3
5
  /* istanbul ignore file */
4
6
 
5
- async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRetrieve) {
7
+ async function insertOne (db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve) {
6
8
  const inputKeys = Object.keys(input)
7
9
  if (inputKeys.length === 0) {
8
10
  const insert = sql`
9
- INSERT INTO ${sql.ident(table)}
11
+ INSERT INTO ${tableName(sql, table, schema)}
10
12
  ()
11
13
  VALUES ()
12
14
  RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
@@ -24,7 +26,7 @@ async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRet
24
26
  sql`, `
25
27
  )
26
28
  const insert = sql`
27
- INSERT INTO ${sql.ident(table)} (${keys})
29
+ INSERT INTO ${tableName(sql, table, schema)} (${keys})
28
30
  VALUES (${values})
29
31
  RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
30
32
  `
@@ -32,9 +34,9 @@ async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRet
32
34
  return res[0]
33
35
  }
34
36
 
35
- async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
37
+ async function deleteAll (db, sql, table, schema, criteria, fieldsToRetrieve) {
36
38
  let query = sql`
37
- DELETE FROM ${sql.ident(table)}
39
+ DELETE FROM ${tableName(sql, table, schema)}
38
40
  `
39
41
 
40
42
  if (criteria.length > 0) {
@@ -46,10 +48,10 @@ async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
46
48
  return res
47
49
  }
48
50
 
49
- async function insertMany (db, sql, table, inputs, inputToFieldMap, primaryKey, fieldsToRetrieve, fields) {
51
+ async function insertMany (db, sql, table, schema, inputs, inputToFieldMap, primaryKey, fieldsToRetrieve, fields) {
50
52
  const { keys, values } = insertPrep(inputs, inputToFieldMap, fields, sql)
51
53
  const insert = sql`
52
- insert into ${sql.ident(table)} (${keys})
54
+ insert into ${tableName(sql, table, schema)} (${keys})
53
55
  values ${sql.join(values, sql`, `)}
54
56
  returning ${sql.join(fieldsToRetrieve, sql`, `)}
55
57
  `
@@ -75,7 +77,13 @@ function insertPrep (inputs, inputToFieldMap, fields, sql) {
75
77
 
76
78
  inputSet.add(newKey)
77
79
 
78
- const value = input[key] || input[newKey]
80
+ let value = input[key] || input[newKey]
81
+
82
+ if (value && typeof value === 'object' && !(value instanceof Date)) {
83
+ // This is a JSON field
84
+ value = JSON.stringify(value)
85
+ }
86
+
79
87
  inputValues.push(sql.value(value))
80
88
  }
81
89
 
@@ -92,7 +100,7 @@ function insertPrep (inputs, inputToFieldMap, fields, sql) {
92
100
  return { keys, values }
93
101
  }
94
102
 
95
- async function updateMany (db, sql, table, criteria, input, fieldsToRetrieve) {
103
+ async function updateMany (db, sql, table, schema, criteria, input, fieldsToRetrieve) {
96
104
  const pairs = Object.keys(input).map((key) => {
97
105
  const value = input[key]
98
106
  return sql`${sql.ident(key)} = ${value}`
@@ -4,11 +4,12 @@ const { randomUUID } = require('crypto')
4
4
  const shared = require('./shared')
5
5
 
6
6
  async function listTables (db, sql) {
7
- const tables = await db.query(sql`
7
+ const res = await db.query(sql`
8
8
  SELECT name FROM sqlite_master
9
9
  WHERE type='table'
10
10
  `)
11
- return tables.map(t => t.name)
11
+ // sqlite has no schemas
12
+ return res.map(r => ({ schema: null, table: r.name }))
12
13
  }
13
14
 
14
15
  module.exports.listTables = listTables
@@ -37,13 +38,9 @@ async function listConstraints (db, sql, table) {
37
38
  WHERE pk > 0
38
39
  `)
39
40
 
40
- if (pks.length > 1) {
41
- throw new Error(`Table ${table} has ${pks.length} primary keys`)
42
- }
43
-
44
- if (pks.length === 1) {
41
+ for (const pk of pks) {
45
42
  constraints.push({
46
- column_name: pks[0].name,
43
+ column_name: pk.name,
47
44
  constraint_type: 'PRIMARY KEY'
48
45
  })
49
46
  }
@@ -67,25 +64,38 @@ async function listConstraints (db, sql, table) {
67
64
 
68
65
  module.exports.listConstraints = listConstraints
69
66
 
70
- async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRetrieve) {
71
- const keysToSql = Object.keys(input).map((key) => sql.ident(key))
72
- keysToSql.push(sql.ident(primaryKey))
67
+ async function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
68
+ const fieldNames = Object.keys(input)
69
+ const keysToSql = fieldNames.map((key) => sql.ident(key))
70
+ const valuesToSql = fieldNames.map((key) => sql.value(input[key]))
71
+
72
+ const primaryKeyValues = {}
73
+ let useUUID = false
74
+ const where = []
75
+ let autoIncrement = 0
76
+ for (const { key, sqlType } of primaryKeys) {
77
+ keysToSql.push(sql.ident(key))
78
+ // TODO figure out while this is not covered by tests
79
+ /* istanbul ignore next */
80
+ if (sqlType === 'uuid') {
81
+ useUUID = true
82
+ primaryKeyValues[key] = randomUUID()
83
+ } else if (autoIncrement > 1) {
84
+ throw new Error('SQLite only supports autoIncrement on one column')
85
+ } else if (input[key]) {
86
+ primaryKeyValues[key] = input[key]
87
+ } else {
88
+ autoIncrement++
89
+ primaryKeyValues[key] = null
90
+ }
91
+ valuesToSql.push(sql.value(primaryKeyValues[key]))
92
+ }
93
+
73
94
  const keys = sql.join(
74
95
  keysToSql,
75
96
  sql`, `
76
97
  )
77
98
 
78
- const valuesToSql = Object.keys(input).map((key) => {
79
- return sql.value(input[key])
80
- })
81
- let primaryKeyValue
82
- // TODO add test for this
83
- if (useUUID) {
84
- primaryKeyValue = randomUUID()
85
- valuesToSql.push(sql.value(primaryKeyValue))
86
- } else {
87
- valuesToSql.push(sql.value(null))
88
- }
89
99
  const values = sql.join(
90
100
  valuesToSql,
91
101
  sql`, `
@@ -97,18 +107,22 @@ async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRe
97
107
  `
98
108
  await db.query(insert)
99
109
 
100
- if (!useUUID) {
110
+ if (!useUUID && primaryKeys.length === 1) {
101
111
  const res2 = await db.query(sql`
102
112
  SELECT last_insert_rowid()
103
113
  `)
104
114
 
105
- primaryKeyValue = res2[0]['last_insert_rowid()']
115
+ primaryKeyValues[primaryKeys[0].key] = res2[0]['last_insert_rowid()']
116
+ }
117
+
118
+ for (const { key } of primaryKeys) {
119
+ where.push(sql`${sql.ident(key)} = ${sql.value(primaryKeyValues[key])}`)
106
120
  }
107
121
 
108
122
  const res = await db.query(sql`
109
123
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
110
124
  FROM ${sql.ident(table)}
111
- WHERE ${sql.ident(primaryKey)} = ${sql.value(primaryKeyValue)}
125
+ WHERE ${sql.join(where, sql` AND `)}
112
126
  `)
113
127
 
114
128
  return res[0]
@@ -116,23 +130,28 @@ async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRe
116
130
 
117
131
  module.exports.insertOne = insertOne
118
132
 
119
- async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
133
+ async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
120
134
  const pairs = Object.keys(input).map((key) => {
121
135
  const value = input[key]
122
136
  return sql`${sql.ident(key)} = ${value}`
123
137
  })
124
138
 
139
+ const where = []
140
+ for (const key of primaryKeys) {
141
+ where.push(sql`${sql.ident(key)} = ${input[key]}`)
142
+ }
143
+
125
144
  const update = sql`
126
145
  UPDATE ${sql.ident(table)}
127
146
  SET ${sql.join(pairs, sql`, `)}
128
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
147
+ WHERE ${sql.join(where, sql` AND `)}
129
148
  `
130
149
  await db.query(update)
131
150
 
132
151
  const select = sql`
133
152
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
134
153
  FROM ${sql.ident(table)}
135
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
154
+ WHERE ${sql.join(where, sql` AND `)}
136
155
  `
137
156
  const res = await db.query(select)
138
157
  return res[0]
@@ -140,7 +159,7 @@ async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
140
159
 
141
160
  module.exports.updateOne = updateOne
142
161
 
143
- async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
162
+ async function deleteAll (db, sql, table, schema, criteria, fieldsToRetrieve) {
144
163
  let query = sql`
145
164
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
146
165
  FROM ${sql.ident(table)}
package/lib/utils.js CHANGED
@@ -3,12 +3,49 @@
3
3
  const { singularize } = require('inflected')
4
4
  const camelcase = require('camelcase')
5
5
 
6
+ function toUpperFirst (str) {
7
+ return str[0].toUpperCase() + str.slice(1)
8
+ }
9
+
6
10
  function toSingular (str) {
7
11
  str = camelcase(singularize(str))
8
- str = str[0].toUpperCase() + str.slice(1)
12
+ str = toUpperFirst(str)
9
13
  return str
10
14
  }
11
15
 
16
+ /**
17
+ * If limit is not defined or invalid
18
+ * let's set a safe default value preventing to load huge amount of data in memory
19
+ */
20
+ function sanitizeLimit (unsafeLimit, conf) {
21
+ const defaultLimit = conf?.default ?? 10
22
+ const limit = (unsafeLimit !== undefined) ? unsafeLimit : defaultLimit
23
+ const max = conf?.max ?? 100
24
+
25
+ if (limit > max) {
26
+ throw new Error(`Param limit=${limit} not allowed. Max accepted value ${max}.`)
27
+ }
28
+
29
+ if (limit < 0) {
30
+ throw new Error(`Param limit=${limit} not allowed. It must be not negative value.`)
31
+ }
32
+
33
+ return limit
34
+ }
35
+
36
+ function tableName (sql, table, schema) {
37
+ /* istanbul ignore next */
38
+ return schema ? sql.ident(schema, table) : sql.ident(table)
39
+ }
40
+
41
+ function areSchemasSupported (sql) {
42
+ return !sql.isSQLite
43
+ }
44
+
12
45
  module.exports = {
13
- toSingular
46
+ toSingular,
47
+ toUpperFirst,
48
+ sanitizeLimit,
49
+ tableName,
50
+ areSchemasSupported
14
51
  }
package/mapper.d.ts CHANGED
@@ -94,6 +94,10 @@ export interface WhereCondition {
94
94
  * Not in values.
95
95
  */
96
96
  nin?: any[]
97
+ /**
98
+ * Like value.
99
+ */
100
+ like?: string
97
101
  }
98
102
  }
99
103
 
package/mapper.js CHANGED
@@ -1,12 +1,11 @@
1
- 'use strict'
2
-
3
1
  const buildEntity = require('./lib/entity')
4
2
  const queriesFactory = require('./lib/queries')
5
3
  const fp = require('fastify-plugin')
4
+ const { areSchemasSupported } = require('./lib/utils')
6
5
 
7
6
  // Ignore the function as it is only used only for MySQL and PostgreSQL
8
7
  /* istanbul ignore next */
9
- async function buildConnection (log, createConnectionPool, connectionString, poolSize) {
8
+ async function buildConnection (log, createConnectionPool, connectionString, poolSize, schema) {
10
9
  const db = await createConnectionPool({
11
10
  connectionString,
12
11
  bigIntMode: 'string',
@@ -34,12 +33,13 @@ async function buildConnection (log, createConnectionPool, connectionString, poo
34
33
  error: err.message
35
34
  }
36
35
  }, 'query error')
37
- }
36
+ },
37
+ schema
38
38
  })
39
39
  return db
40
40
  }
41
41
 
42
- async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {} }) {
42
+ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {} }) {
43
43
  // TODO validate config using the schema
44
44
  if (!connectionString) {
45
45
  throw new Error('connectionString is required')
@@ -87,6 +87,11 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
87
87
  throw new Error('You must specify either postgres, mysql or sqlite as protocols')
88
88
  }
89
89
 
90
+ // Specify an empty array must be the same of specifying no schema
91
+ /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
92
+ const schemaList = areSchemasSupported(db) && schema?.length > 0 ? schema : null
93
+ const useSchema = !!schemaList
94
+
90
95
  const entities = {}
91
96
 
92
97
  try {
@@ -95,9 +100,9 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
95
100
  await onDatabaseLoad(db, sql)
96
101
  }
97
102
 
98
- const tables = await queries.listTables(db, sql)
103
+ const tablesWithSchema = await queries.listTables(db, sql, schemaList)
99
104
 
100
- for (const table of tables) {
105
+ for (const { table, schema } of tablesWithSchema) {
101
106
  // The following line is a safety net when developing this module,
102
107
  // it should never happen.
103
108
  /* istanbul ignore next */
@@ -107,13 +112,14 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
107
112
  if (ignore[table] === true) {
108
113
  continue
109
114
  }
110
-
111
- const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, ignore[table] || {})
115
+ const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
112
116
  // Check for primary key of all entities
113
- if (!entity.primaryKey) {
114
- throw new Error(`Cannot find primary key for ${entity.name} entity`)
117
+ if (entity.primaryKeys.size === 0) {
118
+ throw Error(`Cannot find any primary keys for ${entity.name} entity`)
115
119
  }
120
+
116
121
  entities[entity.singularName] = entity
122
+
117
123
  if (hooks[entity.name]) {
118
124
  addEntityHooks(entity.singularName, hooks[entity.name])
119
125
  } else if (hooks[entity.singularName]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "snazzy": "^9.0.0",
19
19
  "standard": "^17.0.0",
20
20
  "tap": "^16.0.0",
21
- "tsd": "^0.24.0"
21
+ "tsd": "^0.25.0"
22
22
  },
23
23
  "dependencies": {
24
24
  "@databases/mysql": "^5.2.0",
@@ -0,0 +1,162 @@
1
+ 'use strict'
2
+
3
+ const { clear, connInfo, isSQLite, isMysql, isPg } = require('./helper')
4
+ const { test } = require('tap')
5
+ const { connect } = require('..')
6
+ const fakeLogger = {
7
+ trace: () => {},
8
+ // trace: console.log,
9
+ error: () => {}
10
+ }
11
+
12
+ test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
13
+ /* https://github.com/platformatic/platformatic/issues/299 */
14
+ async function onDatabaseLoad (db, sql) {
15
+ await clear(db, sql)
16
+ teardown(() => db.dispose())
17
+
18
+ if (isSQLite) {
19
+ await db.query(sql`CREATE TABLE pages (
20
+ id INTEGER PRIMARY KEY,
21
+ the_title VARCHAR(42)
22
+ );`)
23
+
24
+ await db.query(sql`CREATE TABLE users (
25
+ id INTEGER PRIMARY KEY,
26
+ username VARCHAR(255) NOT NULL
27
+ );`)
28
+
29
+ await db.query(sql`CREATE TABLE editors (
30
+ page_id INTEGER NOT NULL,
31
+ user_id INTEGER NOT NULL,
32
+ role VARCHAR(255) NOT NULL,
33
+ CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
34
+ CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
35
+ PRIMARY KEY (page_id, user_id)
36
+ );`)
37
+ } else if (isPg) {
38
+ await db.query(sql`CREATE TABLE pages (
39
+ id SERIAL PRIMARY KEY,
40
+ the_title VARCHAR(255) NOT NULL
41
+ );`)
42
+
43
+ await db.query(sql`CREATE TABLE users (
44
+ id SERIAL PRIMARY KEY,
45
+ username VARCHAR(255) NOT NULL
46
+ );`)
47
+
48
+ await db.query(sql`CREATE TABLE editors (
49
+ page_id INTEGER NOT NULL,
50
+ user_id INTEGER NOT NULL,
51
+ role VARCHAR(255) NOT NULL,
52
+ CONSTRAINT fk_editor_pages FOREIGN KEY (page_id) REFERENCES pages(id),
53
+ CONSTRAINT fk_editor_users FOREIGN KEY (user_id) REFERENCES users(id),
54
+ PRIMARY KEY (page_id, user_id)
55
+ );`)
56
+ } else if (isMysql) {
57
+ await db.query(sql`CREATE TABLE pages (
58
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
59
+ the_title VARCHAR(255) NOT NULL
60
+ );`)
61
+
62
+ await db.query(sql`CREATE TABLE users (
63
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
64
+ username VARCHAR(255) NOT NULL
65
+ );`)
66
+
67
+ await db.query(sql`CREATE TABLE editors (
68
+ page_id INTEGER NOT NULL,
69
+ user_id INTEGER NOT NULL,
70
+ role VARCHAR(255) NOT NULL,
71
+ CONSTRAINT \`fk_editor_pages\` FOREIGN KEY (page_id) REFERENCES pages (id) ON DELETE CASCADE ON UPDATE RESTRICT,
72
+ CONSTRAINT \`fk_editor_users\` FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT,
73
+ PRIMARY KEY (page_id, user_id)
74
+ );`)
75
+ }
76
+ }
77
+ const mapper = await connect({
78
+ connectionString: connInfo.connectionString,
79
+ log: fakeLogger,
80
+ onDatabaseLoad,
81
+ ignore: {},
82
+ hooks: {}
83
+ })
84
+ const pageEntity = mapper.entities.page
85
+ const userEntity = mapper.entities.user
86
+ const editorEntity = mapper.entities.editor
87
+
88
+ const page = await pageEntity.save({
89
+ input: { theTitle: 'foobar' }
90
+ })
91
+ same(page, { id: '1', theTitle: 'foobar' })
92
+
93
+ const user = await userEntity.save({
94
+ input: { username: 'mcollina' }
95
+ })
96
+ same(user, { id: '1', username: 'mcollina' })
97
+
98
+ const user2 = await userEntity.save({
99
+ input: { username: 'lucamaraschi' }
100
+ })
101
+ same(user2, { id: '2', username: 'lucamaraschi' })
102
+
103
+ const editor1 = await editorEntity.save({
104
+ input: {
105
+ pageId: '1',
106
+ userId: '1',
107
+ role: 'admin'
108
+ }
109
+ })
110
+ same(editor1, { pageId: '1', userId: '1', role: 'admin' })
111
+
112
+ const editor2 = await editorEntity.save({
113
+ input: {
114
+ pageId: '1',
115
+ userId: '2',
116
+ role: 'author'
117
+ }
118
+ })
119
+ same(editor2, { pageId: '1', userId: '2', role: 'author' })
120
+
121
+ await editorEntity.save({
122
+ input: {
123
+ pageId: '1',
124
+ userId: '1',
125
+ role: 'captain'
126
+ }
127
+ })
128
+
129
+ const editors = await editorEntity.find({ orderBy: [{ field: 'userId', direction: 'ASC' }] })
130
+ same(editors, [{
131
+ pageId: '1',
132
+ userId: '1',
133
+ role: 'captain'
134
+ }, {
135
+ pageId: '1',
136
+ userId: '2',
137
+ role: 'author'
138
+ }])
139
+
140
+ await editorEntity.delete({})
141
+
142
+ const editorsInserted = await editorEntity.insert({
143
+ inputs: [{
144
+ pageId: '1',
145
+ userId: '1',
146
+ role: 'admin'
147
+ }, {
148
+ pageId: '1',
149
+ userId: '2',
150
+ role: 'author'
151
+ }]
152
+ })
153
+ same(editorsInserted, [{
154
+ pageId: '1',
155
+ userId: '1',
156
+ role: 'admin'
157
+ }, {
158
+ pageId: '1',
159
+ userId: '2',
160
+ role: 'author'
161
+ }])
162
+ })
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { test } = require('tap')
4
4
 
5
- const { clear, connInfo, isSQLite, isMysql, isPg } = require('./helper')
5
+ const { clear, connInfo, isSQLite, isMysql, isPg, isMysql8 } = require('./helper')
6
6
  const { connect } = require('..')
7
7
  const fakeLogger = {
8
8
  trace: () => {},
@@ -38,12 +38,12 @@ test('entity fields', async ({ equal, not, same, teardown }) => {
38
38
  equal(pageEntity.name, 'Page')
39
39
  equal(pageEntity.singularName, 'page')
40
40
  equal(pageEntity.pluralName, 'pages')
41
- equal(pageEntity.primaryKey, 'id')
41
+ same(pageEntity.primaryKeys, new Set(['id']))
42
42
  equal(pageEntity.table, 'pages')
43
43
  equal(pageEntity.camelCasedFields.id.primaryKey, true)
44
44
  })
45
45
 
46
- test('entity API', async ({ equal, same, teardown, rejects }) => {
46
+ test('entity API', { only: true }, async ({ equal, same, teardown, rejects }) => {
47
47
  async function onDatabaseLoad (db, sql) {
48
48
  await clear(db, sql)
49
49
  teardown(() => db.dispose())
@@ -168,6 +168,33 @@ test('empty save', async ({ equal, same, teardown, rejects }) => {
168
168
  same(insertResult, { id: '1', theTitle: null })
169
169
  })
170
170
 
171
+ test('insert with explicit PK value', async ({ same, teardown }) => {
172
+ async function onDatabaseLoad (db, sql) {
173
+ await clear(db, sql)
174
+ teardown(() => db.dispose())
175
+ await db.query(sql`CREATE TABLE pages (
176
+ id INTEGER PRIMARY KEY,
177
+ title varchar(255) NOT NULL
178
+ );`)
179
+ }
180
+ const mapper = await connect({
181
+ connectionString: connInfo.connectionString,
182
+ log: fakeLogger,
183
+ onDatabaseLoad,
184
+ ignore: {},
185
+ hooks: {}
186
+ })
187
+ const pageEntity = mapper.entities.page
188
+ const [newPage] = await pageEntity.insert({
189
+ fields: ['id', 'title'],
190
+ inputs: [{ id: 13, title: '13th page with explicit id equal to 13' }]
191
+ })
192
+ same(newPage, {
193
+ id: '13',
194
+ title: '13th page with explicit id equal to 13'
195
+ })
196
+ })
197
+
171
198
  test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equal }) => {
172
199
  const mapper = await connect({
173
200
  connectionString: connInfo.connectionString,
@@ -186,6 +213,7 @@ test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equa
186
213
  );`)
187
214
  }
188
215
  })
216
+ teardown(() => mapper.db.dispose())
189
217
 
190
218
  const pageEntity = mapper.entities.page
191
219
 
@@ -216,7 +244,7 @@ test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equa
216
244
  }
217
245
  })
218
246
 
219
- test('[sqlite] throws if PK is not INTEGER', { skip: !isSQLite }, async ({ fail, equal, teardown, rejects }) => {
247
+ test('[SQLite] throws if PK is not INTEGER', { skip: !isSQLite }, async ({ fail, equal, teardown, rejects }) => {
220
248
  async function onDatabaseLoad (db, sql) {
221
249
  await clear(db, sql)
222
250
  await db.query(sql`CREATE TABLE pages (
@@ -638,3 +666,54 @@ test('include possible values of enum columns', { skip: isSQLite }, async ({ sam
638
666
  const typeField = pageEntity.fields.type
639
667
  same(typeField.enum, ['blank', 'non-blank'])
640
668
  })
669
+
670
+ test('JSON type', { skip: !(isPg || isMysql8) }, async ({ teardown, same, equal, pass }) => {
671
+ async function onDatabaseLoad (db, sql) {
672
+ await clear(db, sql)
673
+ teardown(() => db.dispose())
674
+
675
+ await db.query(sql`CREATE TABLE simple_types (
676
+ id SERIAL PRIMARY KEY,
677
+ config json NOT NULL
678
+ );`)
679
+ }
680
+ const mapper = await connect({
681
+ connectionString: connInfo.connectionString,
682
+ log: fakeLogger,
683
+ onDatabaseLoad,
684
+ ignore: {},
685
+ hooks: {}
686
+ })
687
+
688
+ const simpleType = mapper.entities.simpleType
689
+
690
+ // save - new record
691
+ same(await simpleType.save({
692
+ input: { config: { foo: 'bar' } }
693
+ }), { id: 1, config: { foo: 'bar' } })
694
+
695
+ // save - update
696
+ same(await simpleType.save({
697
+ input: { id: 1, config: { foo: 'bar', bar: 'foo' } }
698
+ }), { id: 1, config: { foo: 'bar', bar: 'foo' } })
699
+
700
+ // insert
701
+ same(await simpleType.insert({
702
+ inputs: [{ config: { foo: 'bar' } }]
703
+ }), [{ id: 2, config: { foo: 'bar' } }])
704
+
705
+ // updateMany
706
+ same(await simpleType.updateMany({
707
+ where: {
708
+ id: {
709
+ eq: 2
710
+ }
711
+ },
712
+ input: {
713
+ config: {
714
+ foo: 'bar',
715
+ bar: 'foo'
716
+ }
717
+ }
718
+ }), [{ id: 2, config: { foo: 'bar', bar: 'foo' } }])
719
+ })
@@ -42,7 +42,6 @@ test('entity transactions', async ({ equal, same, teardown, rejects }) => {
42
42
  same(findResult, [{ title: 'foo' }, { title: 'bar' }])
43
43
 
44
44
  try {
45
- console.log('isSQLite', mapper.db.isSQLite)
46
45
  await mapper.db.tx(async tx => {
47
46
  same(await pageEntity.save({
48
47
  input: { title: 'new page' },