@platformatic/sql-mapper 0.0.23

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.
Files changed (36) hide show
  1. package/.nyc_output/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
  2. package/.nyc_output/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
  3. package/.nyc_output/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
  4. package/.nyc_output/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
  5. package/.nyc_output/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
  6. package/.nyc_output/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
  7. package/.nyc_output/processinfo/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
  8. package/.nyc_output/processinfo/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
  9. package/.nyc_output/processinfo/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
  10. package/.nyc_output/processinfo/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
  11. package/.nyc_output/processinfo/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
  12. package/.nyc_output/processinfo/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
  13. package/.nyc_output/processinfo/index.json +1 -0
  14. package/.taprc +1 -0
  15. package/LICENSE +201 -0
  16. package/NOTICE +13 -0
  17. package/README.md +13 -0
  18. package/lib/entity.js +287 -0
  19. package/lib/queries/index.js +23 -0
  20. package/lib/queries/mariadb.js +11 -0
  21. package/lib/queries/mysql-shared.js +62 -0
  22. package/lib/queries/mysql.js +104 -0
  23. package/lib/queries/pg.js +79 -0
  24. package/lib/queries/shared.js +100 -0
  25. package/lib/queries/sqlite.js +169 -0
  26. package/lib/utils.js +14 -0
  27. package/mapper.d.ts +308 -0
  28. package/mapper.js +155 -0
  29. package/package.json +44 -0
  30. package/test/entity.test.js +344 -0
  31. package/test/helper.js +66 -0
  32. package/test/hooks.test.js +325 -0
  33. package/test/inserted_at_updated_at.test.js +132 -0
  34. package/test/mapper.test.js +288 -0
  35. package/test/types/mapper.test-d.ts +64 -0
  36. package/test/where.test.js +316 -0
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @platformatic/sql-mapper
2
+
3
+ Check out the full documentation on [our website](https://oss.platformatic.dev/docs/reference/sql-mapper/introduction).
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @platformatic/sql-mapper
9
+ ```
10
+
11
+ ## License
12
+
13
+ Apache 2.0
package/lib/entity.js ADDED
@@ -0,0 +1,287 @@
1
+ 'use strict'
2
+
3
+ const camelcase = require('camelcase')
4
+ const { singularize } = require('inflected')
5
+ const {
6
+ toSingular
7
+ } = require('./utils')
8
+
9
+ function createMapper (db, sql, log, table, fields, primaryKey, relations, queries, autoTimestamp) {
10
+ const entityName = toSingular(table)
11
+
12
+ // Fields remapping
13
+ const fieldMapToRetrieve = {}
14
+ const inputToFieldMap = {}
15
+ const camelCasedFields = Object.keys(fields).reduce((acc, key) => {
16
+ const camel = camelcase(key)
17
+ acc[camel] = fields[key]
18
+ fieldMapToRetrieve[key] = camel
19
+ inputToFieldMap[camel] = key
20
+ fields[key].camelcase = camel
21
+ return acc
22
+ }, {})
23
+ const alwaysRetrieve = relations.map((relation) => relation.column_name)
24
+
25
+ function fixInput (input) {
26
+ const newInput = {}
27
+ for (const key of Object.keys(input)) {
28
+ const value = input[key]
29
+ let newKey = inputToFieldMap[key]
30
+ if (newKey === undefined) {
31
+ if (fields[key] !== undefined) {
32
+ newKey = key
33
+ } else {
34
+ throw new Error(`Unknown field ${key}`)
35
+ }
36
+ }
37
+ newInput[newKey] = value
38
+ }
39
+ return newInput
40
+ }
41
+
42
+ function fixOutput (output) {
43
+ if (!output) {
44
+ return output
45
+ }
46
+ const newOutput = {}
47
+ for (const key of Object.keys(output)) {
48
+ let value = output[key]
49
+ const newKey = fieldMapToRetrieve[key]
50
+ if (key === primaryKey && value !== null && value !== undefined) {
51
+ value = value.toString()
52
+ }
53
+ newOutput[newKey] = value
54
+ }
55
+ return newOutput
56
+ }
57
+
58
+ async function save (args) {
59
+ if (args.input === undefined) {
60
+ throw new Error('Input not provided.')
61
+ }
62
+ // args.input is not array
63
+ const fieldsToRetrieve = computeFields(args.fields).map((f) => sql.ident(f))
64
+ const input = fixInput(args.input)
65
+ let now
66
+ if (autoTimestamp && fields.updated_at) {
67
+ now = new Date()
68
+ input.updated_at = now
69
+ }
70
+ if (input[primaryKey]) { // update
71
+ const res = await queries.updateOne(db, sql, table, input, primaryKey, fieldsToRetrieve)
72
+ return fixOutput(res)
73
+ } else { // insert
74
+ if (autoTimestamp && fields.inserted_at) {
75
+ /* istanbul ignore next */
76
+ now = now || new Date()
77
+ input.inserted_at = now
78
+ }
79
+ const res = await queries.insertOne(db, sql, table, input, primaryKey, fields[primaryKey].sqlType.toLowerCase() === 'uuid', fieldsToRetrieve)
80
+ return fixOutput(res)
81
+ }
82
+ }
83
+
84
+ async function insert (args) {
85
+ const fieldsToRetrieve = computeFields(args.fields).map((f) => sql.ident(f))
86
+ const inputs = args.inputs
87
+ // This else is skipped on MySQL because of https://github.com/ForbesLindesay/atdatabases/issues/221
88
+ /* istanbul ignore else */
89
+ if (autoTimestamp) {
90
+ const now = new Date()
91
+ for (const input of inputs) {
92
+ if (fields.inserted_at) {
93
+ input.insertedAt = now
94
+ }
95
+ if (fields.updated_at) {
96
+ input.updatedAt = now
97
+ }
98
+ }
99
+ }
100
+ /* istanbul ignore next */
101
+ if (queries.insertMany) {
102
+ // We are not fixing the input here because it is done in the query.
103
+ const res = await queries.insertMany(db, sql, table, inputs, inputToFieldMap, primaryKey, fieldsToRetrieve, fields)
104
+ return res.map(fixOutput)
105
+ } else {
106
+ // TODO this can be optimized, we can still use a batch insert if we do not want any fields
107
+ const res = []
108
+ for (let input of inputs) {
109
+ input = fixInput(input)
110
+ const resOne = await queries.insertOne(db, sql, table, input, primaryKey, fields[primaryKey].sqlType.toLowerCase() === 'uuid', fieldsToRetrieve)
111
+ res.push(fixOutput(resOne))
112
+ }
113
+
114
+ return res
115
+ }
116
+ }
117
+
118
+ function computeFields (fields) {
119
+ if (!fields) {
120
+ return Object.values(inputToFieldMap)
121
+ }
122
+
123
+ const requestedFields = fields.map((field) => inputToFieldMap[field])
124
+ const set = new Set([...alwaysRetrieve, ...requestedFields])
125
+ set.delete(undefined)
126
+ const fieldsToRetrieve = [...set]
127
+ return fieldsToRetrieve
128
+ }
129
+
130
+ const whereMap = {
131
+ eq: '=',
132
+ in: 'IN',
133
+ nin: 'NOT IN',
134
+ neq: '<>',
135
+ gt: '>',
136
+ gte: '>=',
137
+ lt: '<',
138
+ lte: '<='
139
+ }
140
+
141
+ function computeCriteria (opts) {
142
+ const where = opts.where || {}
143
+ const criteria = []
144
+ for (const key of Object.keys(where)) {
145
+ const value = where[key]
146
+ const field = inputToFieldMap[key]
147
+ for (const key of Object.keys(value)) {
148
+ const operator = whereMap[key]
149
+ /* istanbul ignore next */
150
+ if (!operator) {
151
+ // This should never happen
152
+ throw new Error(`Unsupported where clause ${JSON.stringify(where[key])}`)
153
+ }
154
+ const fieldWrap = fields[field]
155
+ criteria.push(sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`)
156
+ }
157
+ }
158
+ return criteria
159
+ }
160
+
161
+ function computeCriteriaValue (fieldWrap, value) {
162
+ if (Array.isArray(value)) {
163
+ return sql`(${sql.join(
164
+ value.map((v) => computeCriteriaValue(fieldWrap, v)),
165
+ sql`, `
166
+ )})`
167
+ }
168
+
169
+ /* istanbul ignore next */
170
+ if (fieldWrap.sqlType === 'int4' || fieldWrap.sqlType === 'int2' || fieldWrap.sqlType === 'float8' || fieldWrap.sqlType === 'float4') {
171
+ // This cat is needed in PostgreSQL
172
+ return sql`${Number(value)}`
173
+ } else {
174
+ return sql`${value}`
175
+ }
176
+ }
177
+
178
+ async function find (opts = {}) {
179
+ const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
180
+ const criteria = computeCriteria(opts)
181
+ let query = sql`
182
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
183
+ FROM ${sql.ident(table)}
184
+ `
185
+
186
+ if (criteria.length > 0) {
187
+ query = sql`${query} WHERE ${sql.join(criteria, sql` AND `)}`
188
+ }
189
+
190
+ if (opts.orderBy && opts.orderBy.length > 0) {
191
+ const orderBy = opts.orderBy.map((order) => {
192
+ const field = inputToFieldMap[order.field]
193
+ return sql`${sql.ident(field)} ${sql.__dangerous__rawValue(order.direction)}`
194
+ })
195
+ query = sql`${query} ORDER BY ${sql.join(orderBy, sql`, `)}`
196
+ }
197
+
198
+ if (opts.limit && opts.offset !== undefined) {
199
+ query = sql`${query} LIMIT ${opts.limit} OFFSET ${opts.offset}`
200
+ }
201
+
202
+ const res = await db.query(query)
203
+ return res.map(fixOutput)
204
+ }
205
+
206
+ async function _delete (opts) {
207
+ const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
208
+ const criteria = computeCriteria(opts)
209
+ const res = await queries.deleteAll(db, sql, table, criteria, fieldsToRetrieve)
210
+ return res.map(fixOutput)
211
+ }
212
+
213
+ return {
214
+ name: entityName,
215
+ singularName: camelcase(singularize(table)),
216
+ pluralName: camelcase(table),
217
+ primaryKey,
218
+ table,
219
+ fields,
220
+ camelCasedFields,
221
+ fixInput,
222
+ fixOutput,
223
+ find,
224
+ insert,
225
+ save,
226
+ delete: _delete
227
+ }
228
+ }
229
+
230
+ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore) {
231
+ // Compute the columns
232
+ const columns = (await queries.listColumns(db, sql, table)).filter((c) => !ignore[c.column_name])
233
+ const fields = columns.reduce((acc, column) => {
234
+ acc[column.column_name] = {
235
+ sqlType: column.udt_name,
236
+ isNullable: column.is_nullable === 'YES'
237
+ }
238
+ if (autoTimestamp && (column.column_name === 'updated_at' || column.column_name === 'inserted_at')) {
239
+ acc[column.column_name].autoTimestamp = true
240
+ }
241
+ return acc
242
+ }, {})
243
+
244
+ const currentRelations = []
245
+
246
+ const constraintsList = await queries.listConstraints(db, sql, table)
247
+ let primaryKey
248
+
249
+ for (const constraint of constraintsList) {
250
+ const field = fields[constraint.column_name]
251
+
252
+ /* istanbul ignore next */
253
+ if (!field) {
254
+ // This should never happen
255
+ log.warn({
256
+ constraint
257
+ }, `No field for ${constraint.column_name}`)
258
+ continue
259
+ }
260
+
261
+ if (constraint.constraint_type === 'PRIMARY KEY') {
262
+ primaryKey = constraint.column_name
263
+ // Check for SQLite typeless PK
264
+ /* istanbul ignore next */
265
+ if (db.isSQLite) {
266
+ const validTypes = ['integer', 'uuid', 'serial']
267
+ const pkType = fields[primaryKey].sqlType.toLowerCase()
268
+ if (!validTypes.includes(pkType)) {
269
+ throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
270
+ }
271
+ }
272
+ field.primaryKey = true
273
+ }
274
+
275
+ if (constraint.constraint_type === 'FOREIGN KEY') {
276
+ field.foreignKey = true
277
+ currentRelations.push(constraint)
278
+ }
279
+ }
280
+
281
+ const entity = createMapper(db, sql, log, table, fields, primaryKey, currentRelations, queries, autoTimestamp)
282
+ entity.relations = currentRelations
283
+
284
+ return entity
285
+ }
286
+
287
+ module.exports = buildEntity
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ /* istanbul ignore file */
4
+
5
+ const obj = {}
6
+
7
+ Object.defineProperty(obj, 'pg', {
8
+ get: () => require('./pg')
9
+ })
10
+
11
+ Object.defineProperty(obj, 'mysql', {
12
+ get: () => require('./mysql')
13
+ })
14
+
15
+ Object.defineProperty(obj, 'mariadb', {
16
+ get: () => require('./mariadb')
17
+ })
18
+
19
+ Object.defineProperty(obj, 'sqlite', {
20
+ get: () => require('./sqlite')
21
+ })
22
+
23
+ module.exports = obj
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const shared = require('./shared')
4
+ const mysql = require('./mysql-shared')
5
+
6
+ module.exports = {
7
+ ...mysql,
8
+ insertOne: shared.insertOne,
9
+ insertMany: shared.insertMany,
10
+ deleteAll: shared.deleteAll
11
+ }
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ async function listTables (db, sql) {
4
+ const res = await db.query(sql`
5
+ SELECT TABLE_NAME
6
+ FROM information_schema.tables
7
+ WHERE table_schema = (SELECT DATABASE())
8
+ `)
9
+ return res.map(r => r.TABLE_NAME)
10
+ }
11
+
12
+ async function listColumns (db, sql, table) {
13
+ const res = await db.query(sql`
14
+ SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable
15
+ FROM information_schema.columns
16
+ WHERE table_name = ${table}
17
+ AND table_schema = (SELECT DATABASE())
18
+ `)
19
+ return res
20
+ }
21
+
22
+ async function listConstraints (db, sql, table) {
23
+ const res = await db.query(sql`
24
+ SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_column_name AS foreign_column_name
25
+ FROM information_schema.table_constraints t
26
+ JOIN information_schema.key_column_usage k
27
+ USING (constraint_name, table_schema, table_name)
28
+ WHERE t.table_name = ${table}
29
+ AND t.table_schema = (SELECT DATABASE())
30
+ `)
31
+
32
+ return res
33
+ }
34
+
35
+ async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
36
+ const pairs = Object.keys(input).map((key) => {
37
+ const value = input[key]
38
+ return sql`${sql.ident(key)} = ${value}`
39
+ })
40
+ const update = sql`
41
+ UPDATE ${sql.ident(table)}
42
+ SET ${sql.join(pairs, sql`, `)}
43
+ WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
44
+ `
45
+ await db.query(update)
46
+
47
+ const select = sql`
48
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
49
+ FROM ${sql.ident(table)}
50
+ WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
51
+ `
52
+
53
+ const res = await db.query(select)
54
+ return res[0]
55
+ }
56
+
57
+ module.exports = {
58
+ listTables,
59
+ listColumns,
60
+ listConstraints,
61
+ updateOne
62
+ }
@@ -0,0 +1,104 @@
1
+ 'use strict'
2
+
3
+ const { insertPrep } = require('./shared')
4
+ const shared = require('./mysql-shared')
5
+
6
+ function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRetrieve) {
7
+ const keysToSql = Object.keys(input).map((key) => sql.ident(key))
8
+ const keys = sql.join(
9
+ keysToSql,
10
+ sql`, `
11
+ )
12
+
13
+ const valuesToSql = Object.keys(input).map((key) => {
14
+ return sql.value(input[key])
15
+ })
16
+ const values = sql.join(
17
+ valuesToSql,
18
+ sql`, `
19
+ )
20
+
21
+ return db.tx(async function (db) {
22
+ const insert = sql`
23
+ INSERT INTO ${sql.ident(table)} (${keys})
24
+ VALUES(${values})
25
+ `
26
+ await db.query(insert)
27
+
28
+ const res2 = await db.query(sql`
29
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
30
+ FROM ${sql.ident(table)}
31
+ WHERE ${sql.ident(primaryKey)} = (
32
+ SELECT last_insert_id()
33
+ )
34
+ `)
35
+
36
+ return res2[0]
37
+ })
38
+ }
39
+
40
+ function insertMany (db, sql, table, inputs, inputToFieldMap, primaryKey, fieldsToRetrieve, fields) {
41
+ return db.tx(async function (db) {
42
+ const { keys, values } = insertPrep(inputs, inputToFieldMap, fields, sql)
43
+ const insert = sql`
44
+ insert into ${sql.ident(table)} (${keys})
45
+ values ${sql.join(values, sql`, `)}
46
+ `
47
+
48
+ await db.query(insert)
49
+
50
+ const res = await db.query(sql`
51
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
52
+ FROM ${sql.ident(table)}
53
+ ORDER BY ${sql.ident(primaryKey)} DESC
54
+ LIMIT ${inputs.length}
55
+ `)
56
+
57
+ // To make consistent with shared.insertMany
58
+ res.sort(function (a, b) {
59
+ return a.id - b.id
60
+ })
61
+ return res
62
+ })
63
+ }
64
+
65
+ function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
66
+ return db.tx(async function (db) {
67
+ let selectQuery = sql`
68
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
69
+ FROM ${sql.ident(table)}
70
+ `
71
+ /* istanbul ignore else */
72
+ if (criteria.length > 0) {
73
+ selectQuery = sql`
74
+ ${selectQuery}
75
+ WHERE ${sql.join(criteria, sql` AND `)}
76
+ `
77
+ }
78
+
79
+ const res = await db.query(selectQuery)
80
+
81
+ let deleteQuery = sql`
82
+ DELETE FROM ${sql.ident(table)}
83
+ `
84
+
85
+ /* istanbul ignore else */
86
+ if (criteria.length > 0) {
87
+ deleteQuery = sql`
88
+ ${deleteQuery}
89
+ WHERE ${sql.join(criteria, sql` AND `)}
90
+ `
91
+ }
92
+
93
+ await db.query(deleteQuery)
94
+
95
+ return res
96
+ })
97
+ }
98
+
99
+ module.exports = {
100
+ ...shared,
101
+ insertOne,
102
+ insertMany,
103
+ deleteAll
104
+ }
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ const shared = require('./shared')
4
+
5
+ async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRetrieve) {
6
+ const inputKeys = Object.keys(input)
7
+ if (inputKeys.length === 0) {
8
+ const insert = sql`
9
+ INSERT INTO ${sql.ident(table)}
10
+ DEFAULT VALUES
11
+ RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
12
+ `
13
+ const res = await db.query(insert)
14
+ return res[0]
15
+ }
16
+
17
+ return shared.insertOne(db, sql, table, input, primaryKey, isUuid, fieldsToRetrieve)
18
+ }
19
+
20
+ module.exports.insertOne = insertOne
21
+ module.exports.deleteAll = shared.deleteAll
22
+ module.exports.insertMany = shared.insertMany
23
+
24
+ async function listTables (db, sql) {
25
+ return (await db.query(sql`
26
+ SELECT tablename
27
+ FROM pg_catalog.pg_tables
28
+ WHERE
29
+ schemaname = current_schema()
30
+ `)).map(t => t.tablename)
31
+ }
32
+
33
+ module.exports.listTables = listTables
34
+
35
+ async function listColumns (db, sql, table) {
36
+ return db.query(sql`
37
+ SELECT column_name, udt_name, is_nullable
38
+ FROM information_schema.columns
39
+ WHERE table_name = ${table}
40
+ AND table_schema = current_schema()
41
+ `)
42
+ }
43
+
44
+ module.exports.listColumns = listColumns
45
+
46
+ async function listConstraints (db, sql, table) {
47
+ const query = sql`
48
+ SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
49
+ FROM information_schema.table_constraints constraints
50
+ JOIN information_schema.key_column_usage usage
51
+ ON constraints.constraint_name = usage.constraint_name
52
+ AND constraints.table_name = ${table}
53
+ JOIN information_schema.constraint_column_usage usage2
54
+ ON usage.constraint_name = usage2.constraint_name
55
+ AND usage.table_name = ${table}
56
+ `
57
+
58
+ const constraintsList = await db.query(query)
59
+ return constraintsList
60
+ }
61
+
62
+ module.exports.listConstraints = listConstraints
63
+
64
+ async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
65
+ const pairs = Object.keys(input).map((key) => {
66
+ const value = input[key]
67
+ return sql`${sql.ident(key)} = ${value}`
68
+ })
69
+ const update = sql`
70
+ UPDATE ${sql.ident(table)}
71
+ SET ${sql.join(pairs, sql`, `)}
72
+ WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
73
+ RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
74
+ `
75
+ const res = await db.query(update)
76
+ return res[0]
77
+ }
78
+
79
+ module.exports.updateOne = updateOne
@@ -0,0 +1,100 @@
1
+ 'use strict'
2
+
3
+ /* istanbul ignore file */
4
+
5
+ async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRetrieve) {
6
+ const inputKeys = Object.keys(input)
7
+ if (inputKeys.length === 0) {
8
+ const insert = sql`
9
+ INSERT INTO ${sql.ident(table)}
10
+ ()
11
+ VALUES ()
12
+ RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
13
+ `
14
+ const res = await db.query(insert)
15
+ return res[0]
16
+ }
17
+
18
+ const keys = sql.join(
19
+ inputKeys.map((key) => sql.ident(key)),
20
+ sql`, `
21
+ )
22
+ const values = sql.join(
23
+ Object.keys(input).map((key) => sql.value(input[key])),
24
+ sql`, `
25
+ )
26
+ const insert = sql`
27
+ INSERT INTO ${sql.ident(table)} (${keys})
28
+ VALUES (${values})
29
+ RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
30
+ `
31
+ const res = await db.query(insert)
32
+ return res[0]
33
+ }
34
+
35
+ async function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
36
+ let query = sql`
37
+ DELETE FROM ${sql.ident(table)}
38
+ `
39
+
40
+ if (criteria.length > 0) {
41
+ query = sql`${query} WHERE ${sql.join(criteria, sql` AND `)}`
42
+ }
43
+
44
+ query = sql`${query} RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}`
45
+ const res = await db.query(query)
46
+ return res
47
+ }
48
+
49
+ async function insertMany (db, sql, table, inputs, inputToFieldMap, primaryKey, fieldsToRetrieve, fields) {
50
+ const { keys, values } = insertPrep(inputs, inputToFieldMap, fields, sql)
51
+ const insert = sql`
52
+ insert into ${sql.ident(table)} (${keys})
53
+ values ${sql.join(values, sql`, `)}
54
+ returning ${sql.join(fieldsToRetrieve, sql`, `)}
55
+ `
56
+
57
+ const res = await db.query(insert)
58
+ return res
59
+ }
60
+
61
+ function insertPrep (inputs, inputToFieldMap, fields, sql) {
62
+ const inputSet = new Set()
63
+ const values = []
64
+ for (const input of inputs) {
65
+ const inputValues = []
66
+ for (const key of Object.keys(input)) {
67
+ let newKey = key
68
+ if (inputToFieldMap[key] === undefined) {
69
+ if (fields[key] === undefined) {
70
+ throw new Error('Unknown field ' + key)
71
+ }
72
+ } else {
73
+ newKey = inputToFieldMap[key]
74
+ }
75
+
76
+ inputSet.add(newKey)
77
+
78
+ const value = input[key] || input[newKey]
79
+ inputValues.push(sql.value(value))
80
+ }
81
+
82
+ values.push(sql` (${sql.join(
83
+ inputValues,
84
+ sql`, `
85
+ )})`)
86
+ }
87
+ const inputKeys = Array.from(inputSet)
88
+ const keys = sql.join(
89
+ inputKeys.map((key) => sql.ident(key)),
90
+ sql`, `
91
+ )
92
+ return { keys, values }
93
+ }
94
+
95
+ module.exports = {
96
+ insertOne,
97
+ insertPrep,
98
+ deleteAll,
99
+ insertMany
100
+ }