@platformatic/sql-mapper 0.6.1 → 0.8.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,9 +100,25 @@ function insertPrep (inputs, inputToFieldMap, fields, sql) {
92
100
  return { keys, values }
93
101
  }
94
102
 
103
+ async function updateMany (db, sql, table, schema, criteria, input, fieldsToRetrieve) {
104
+ const pairs = Object.keys(input).map((key) => {
105
+ const value = input[key]
106
+ return sql`${sql.ident(key)} = ${value}`
107
+ })
108
+ const update = sql`
109
+ UPDATE ${sql.ident(table)}
110
+ SET ${sql.join(pairs, sql`, `)}
111
+ WHERE ${sql.join(criteria, sql` AND `)}
112
+ RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
113
+ `
114
+ const res = await db.query(update)
115
+ return res
116
+ }
117
+
95
118
  module.exports = {
96
119
  insertOne,
97
120
  insertPrep,
98
121
  deleteAll,
99
- insertMany
122
+ insertMany,
123
+ updateMany
100
124
  }
@@ -1,13 +1,15 @@
1
1
  'use strict'
2
2
 
3
3
  const { randomUUID } = require('crypto')
4
+ const shared = require('./shared')
4
5
 
5
6
  async function listTables (db, sql) {
6
- const tables = await db.query(sql`
7
+ const res = await db.query(sql`
7
8
  SELECT name FROM sqlite_master
8
9
  WHERE type='table'
9
10
  `)
10
- return tables.map(t => t.name)
11
+ // sqlite has no schemas
12
+ return res.map(r => ({ schema: null, table: r.name }))
11
13
  }
12
14
 
13
15
  module.exports.listTables = listTables
@@ -36,13 +38,9 @@ async function listConstraints (db, sql, table) {
36
38
  WHERE pk > 0
37
39
  `)
38
40
 
39
- if (pks.length > 1) {
40
- throw new Error(`Table ${table} has ${pks.length} primary keys`)
41
- }
42
-
43
- if (pks.length === 1) {
41
+ for (const pk of pks) {
44
42
  constraints.push({
45
- column_name: pks[0].name,
43
+ column_name: pk.name,
46
44
  constraint_type: 'PRIMARY KEY'
47
45
  })
48
46
  }
@@ -66,25 +64,38 @@ async function listConstraints (db, sql, table) {
66
64
 
67
65
  module.exports.listConstraints = listConstraints
68
66
 
69
- async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRetrieve) {
70
- const keysToSql = Object.keys(input).map((key) => sql.ident(key))
71
- 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
+
72
94
  const keys = sql.join(
73
95
  keysToSql,
74
96
  sql`, `
75
97
  )
76
98
 
77
- const valuesToSql = Object.keys(input).map((key) => {
78
- return sql.value(input[key])
79
- })
80
- let primaryKeyValue
81
- // TODO add test for this
82
- if (useUUID) {
83
- primaryKeyValue = randomUUID()
84
- valuesToSql.push(sql.value(primaryKeyValue))
85
- } else {
86
- valuesToSql.push(sql.value(null))
87
- }
88
99
  const values = sql.join(
89
100
  valuesToSql,
90
101
  sql`, `
@@ -96,18 +107,22 @@ async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRe
96
107
  `
97
108
  await db.query(insert)
98
109
 
99
- if (!useUUID) {
110
+ if (!useUUID && primaryKeys.length === 1) {
100
111
  const res2 = await db.query(sql`
101
112
  SELECT last_insert_rowid()
102
113
  `)
103
114
 
104
- 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])}`)
105
120
  }
106
121
 
107
122
  const res = await db.query(sql`
108
123
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
109
124
  FROM ${sql.ident(table)}
110
- WHERE ${sql.ident(primaryKey)} = ${sql.value(primaryKeyValue)}
125
+ WHERE ${sql.join(where, sql` AND `)}
111
126
  `)
112
127
 
113
128
  return res[0]
@@ -115,23 +130,28 @@ async function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRe
115
130
 
116
131
  module.exports.insertOne = insertOne
117
132
 
118
- async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
133
+ async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
119
134
  const pairs = Object.keys(input).map((key) => {
120
135
  const value = input[key]
121
136
  return sql`${sql.ident(key)} = ${value}`
122
137
  })
123
138
 
139
+ const where = []
140
+ for (const key of primaryKeys) {
141
+ where.push(sql`${sql.ident(key)} = ${input[key]}`)
142
+ }
143
+
124
144
  const update = sql`
125
145
  UPDATE ${sql.ident(table)}
126
146
  SET ${sql.join(pairs, sql`, `)}
127
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
147
+ WHERE ${sql.join(where, sql` AND `)}
128
148
  `
129
149
  await db.query(update)
130
150
 
131
151
  const select = sql`
132
152
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
133
153
  FROM ${sql.ident(table)}
134
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
154
+ WHERE ${sql.join(where, sql` AND `)}
135
155
  `
136
156
  const res = await db.query(select)
137
157
  return res[0]
@@ -139,7 +159,7 @@ async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
139
159
 
140
160
  module.exports.updateOne = updateOne
141
161
 
142
- async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
162
+ async function deleteAll (db, sql, table, schema, criteria, fieldsToRetrieve) {
143
163
  let query = sql`
144
164
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
145
165
  FROM ${sql.ident(table)}
@@ -167,3 +187,5 @@ async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
167
187
  }
168
188
 
169
189
  module.exports.deleteAll = deleteAll
190
+
191
+ module.exports.updateMany = shared.updateMany
package/lib/utils.js CHANGED
@@ -9,6 +9,12 @@ function toSingular (str) {
9
9
  return str
10
10
  }
11
11
 
12
+ function tableName (sql, table, schema) {
13
+ /* istanbul ignore next */
14
+ return schema ? sql.ident(schema, table) : sql.ident(table)
15
+ }
16
+
12
17
  module.exports = {
13
- toSingular
18
+ toSingular,
19
+ tableName
14
20
  }
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
@@ -6,7 +6,7 @@ const fp = require('fastify-plugin')
6
6
 
7
7
  // Ignore the function as it is only used only for MySQL and PostgreSQL
8
8
  /* istanbul ignore next */
9
- async function buildConnection (log, createConnectionPool, connectionString, poolSize) {
9
+ async function buildConnection (log, createConnectionPool, connectionString, poolSize, schema) {
10
10
  const db = await createConnectionPool({
11
11
  connectionString,
12
12
  bigIntMode: 'string',
@@ -34,12 +34,13 @@ async function buildConnection (log, createConnectionPool, connectionString, poo
34
34
  error: err.message
35
35
  }
36
36
  }, 'query error')
37
- }
37
+ },
38
+ schema
38
39
  })
39
40
  return db
40
41
  }
41
42
 
42
- async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {} }) {
43
+ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema }) {
43
44
  // TODO validate config using the schema
44
45
  if (!connectionString) {
45
46
  throw new Error('connectionString is required')
@@ -49,10 +50,15 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
49
50
  let sql
50
51
  let db
51
52
 
53
+ // Specify an empty array must be the same of specifying no schema
54
+ const schemaList = schema?.length > 0 ? schema : null
55
+
52
56
  /* istanbul ignore next */
53
57
  if (connectionString.indexOf('postgres') === 0) {
54
58
  const createConnectionPoolPg = require('@databases/pg')
55
- db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize)
59
+ // We pass schema here so @databases/pg set the schema in the search path. This is not stritly necessary, though,
60
+ // because now we use fully qualified names in all queries.
61
+ db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize, schemaList)
56
62
  sql = createConnectionPoolPg.sql
57
63
  queries = queriesFactory.pg
58
64
  db.isPg = true
@@ -95,9 +101,17 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
95
101
  await onDatabaseLoad(db, sql)
96
102
  }
97
103
 
98
- const tables = await queries.listTables(db, sql)
104
+ const tablesWithSchema = await queries.listTables(db, sql, schemaList)
105
+ const tables = tablesWithSchema.map(({ table }) => table)
106
+ const duplicates = tables.filter((table, index) => tables.indexOf(table) !== index)
99
107
 
100
- for (const table of tables) {
108
+ // Ignored because this never happens in sqlite
109
+ /* istanbul ignore next */
110
+ if (duplicates.length > 0) {
111
+ throw new Error(`Conflicting table names: ${duplicates.join(', ')}`)
112
+ }
113
+
114
+ for (const { table, schema } of tablesWithSchema) {
101
115
  // The following line is a safety net when developing this module,
102
116
  // it should never happen.
103
117
  /* istanbul ignore next */
@@ -107,11 +121,10 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
107
121
  if (ignore[table] === true) {
108
122
  continue
109
123
  }
110
-
111
- const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, ignore[table] || {})
124
+ const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, ignore[table] || {})
112
125
  // Check for primary key of all entities
113
- if (!entity.primaryKey) {
114
- throw new Error(`Cannot find primary key for ${entity.name} entity`)
126
+ if (entity.primaryKeys.size === 0) {
127
+ throw new Error(`Cannot find any primary keys for ${entity.name} entity`)
115
128
  }
116
129
  entities[entity.singularName] = entity
117
130
  if (hooks[entity.name]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -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 } = 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 (
@@ -604,3 +632,88 @@ test('include all fields', async ({ pass, teardown, same, equal }) => {
604
632
  }])
605
633
  }
606
634
  })
635
+
636
+ test('include possible values of enum columns', { skip: isSQLite }, async ({ same, teardown }) => {
637
+ async function onDatabaseLoad (db, sql) {
638
+ await clear(db, sql)
639
+ teardown(() => db.dispose())
640
+
641
+ if (isPg) {
642
+ await db.query(sql`
643
+ CREATE TYPE pagetype as enum ('blank', 'non-blank');
644
+ CREATE TABLE pages (
645
+ id INTEGER PRIMARY KEY,
646
+ title VARCHAR(42),
647
+ type pagetype
648
+ );`)
649
+ } else {
650
+ await db.query(sql`CREATE TABLE pages (
651
+ id INTEGER PRIMARY KEY,
652
+ title VARCHAR(42),
653
+ type ENUM ('blank', 'non-blank')
654
+ );
655
+ `)
656
+ }
657
+ }
658
+ const mapper = await connect({
659
+ connectionString: connInfo.connectionString,
660
+ log: fakeLogger,
661
+ onDatabaseLoad,
662
+ ignore: {},
663
+ hooks: {}
664
+ })
665
+ const pageEntity = mapper.entities.page
666
+ const typeField = pageEntity.fields.type
667
+ same(typeField.enum, ['blank', 'non-blank'])
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' },