@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.
- package/.nyc_output/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
- package/.nyc_output/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
- package/.nyc_output/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
- package/.nyc_output/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
- package/.nyc_output/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
- package/.nyc_output/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
- package/.nyc_output/processinfo/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
- package/.nyc_output/processinfo/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
- package/.nyc_output/processinfo/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
- package/.nyc_output/processinfo/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
- package/.nyc_output/processinfo/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
- package/.nyc_output/processinfo/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/.taprc +1 -0
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +13 -0
- package/lib/entity.js +287 -0
- package/lib/queries/index.js +23 -0
- package/lib/queries/mariadb.js +11 -0
- package/lib/queries/mysql-shared.js +62 -0
- package/lib/queries/mysql.js +104 -0
- package/lib/queries/pg.js +79 -0
- package/lib/queries/shared.js +100 -0
- package/lib/queries/sqlite.js +169 -0
- package/lib/utils.js +14 -0
- package/mapper.d.ts +308 -0
- package/mapper.js +155 -0
- package/package.json +44 -0
- package/test/entity.test.js +344 -0
- package/test/helper.js +66 -0
- package/test/hooks.test.js +325 -0
- package/test/inserted_at_updated_at.test.js +132 -0
- package/test/mapper.test.js +288 -0
- package/test/types/mapper.test-d.ts +64 -0
- package/test/where.test.js +316 -0
package/README.md
ADDED
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,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
|
+
}
|