@platformatic/sql-mapper 0.7.0 → 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.
- package/lib/entity.js +64 -29
- package/lib/queries/mysql-shared.js +52 -29
- package/lib/queries/mysql.js +66 -18
- package/lib/queries/pg.js +40 -23
- package/lib/queries/shared.js +17 -9
- package/lib/queries/sqlite.js +48 -29
- package/lib/utils.js +7 -1
- package/mapper.d.ts +4 -0
- package/mapper.js +23 -10
- package/package.json +1 -1
- package/test/composite.test.js +162 -0
- package/test/entity.test.js +83 -4
- package/test/entity_transaction.test.js +0 -1
- package/test/helper.js +21 -0
- package/test/mapper.test.js +10 -86
- package/test/schema.test.js +261 -0
- package/test/where.test.js +140 -0
package/lib/queries/shared.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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}`
|
package/lib/queries/sqlite.js
CHANGED
|
@@ -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
|
|
7
|
+
const res = await db.query(sql`
|
|
8
8
|
SELECT name FROM sqlite_master
|
|
9
9
|
WHERE type='table'
|
|
10
10
|
`)
|
|
11
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
71
|
-
const
|
|
72
|
-
keysToSql.
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
@@ -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
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
114
|
-
throw new Error(`Cannot find primary
|
|
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
|
@@ -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
|
+
})
|
package/test/entity.test.js
CHANGED
|
@@ -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
|
-
|
|
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('[
|
|
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' },
|
package/test/helper.js
CHANGED
|
@@ -21,6 +21,7 @@ if (!process.env.DB || process.env.DB === 'postgresql') {
|
|
|
21
21
|
connInfo.connectionString = 'mysql://root@127.0.0.1:3308/graph'
|
|
22
22
|
connInfo.poolSize = 10
|
|
23
23
|
module.exports.isMysql = true
|
|
24
|
+
module.exports.isMysql8 = true
|
|
24
25
|
} else if (process.env.DB === 'sqlite') {
|
|
25
26
|
connInfo.connectionString = 'sqlite://:memory:'
|
|
26
27
|
module.exports.isSQLite = true
|
|
@@ -29,6 +30,11 @@ if (!process.env.DB || process.env.DB === 'postgresql') {
|
|
|
29
30
|
module.exports.connInfo = connInfo
|
|
30
31
|
|
|
31
32
|
module.exports.clear = async function (db, sql) {
|
|
33
|
+
try {
|
|
34
|
+
await db.query(sql`DROP TABLE editors`)
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
try {
|
|
33
39
|
await db.query(sql`DROP TABLE pages`)
|
|
34
40
|
} catch (err) {
|
|
@@ -68,4 +74,19 @@ module.exports.clear = async function (db, sql) {
|
|
|
68
74
|
await db.query(sql`DROP TYPE pagetype`)
|
|
69
75
|
} catch {
|
|
70
76
|
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await db.query(sql`DROP TABLE test1.pages`)
|
|
80
|
+
} catch (err) {
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await db.query(sql`DROP TABLE test2.users`)
|
|
85
|
+
} catch (err) {
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await db.query(sql`DROP TABLE test2.pages`)
|
|
90
|
+
} catch (err) {
|
|
91
|
+
}
|
|
71
92
|
}
|