@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.
- package/lib/entity.js +100 -28
- package/lib/queries/mysql-shared.js +81 -26
- package/lib/queries/mysql.js +66 -18
- package/lib/queries/pg.js +46 -15
- package/lib/queries/shared.js +33 -9
- package/lib/queries/sqlite.js +51 -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 +117 -4
- package/test/entity_transaction.test.js +0 -1
- package/test/helper.js +26 -0
- package/test/mapper.test.js +10 -86
- package/test/schema.test.js +261 -0
- package/test/updateMany.test.js +318 -0
- package/test/where.test.js +140 -0
package/lib/entity.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
const camelcase = require('camelcase')
|
|
4
4
|
const { singularize } = require('inflected')
|
|
5
5
|
const {
|
|
6
|
-
toSingular
|
|
6
|
+
toSingular,
|
|
7
|
+
tableName
|
|
7
8
|
} = require('./utils')
|
|
8
9
|
|
|
9
|
-
function createMapper (defaultDb, sql, log, table, fields,
|
|
10
|
+
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema) {
|
|
10
11
|
const entityName = toSingular(table)
|
|
11
12
|
|
|
12
13
|
// Fields remapping
|
|
@@ -21,6 +22,13 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
21
22
|
return acc
|
|
22
23
|
}, {})
|
|
23
24
|
|
|
25
|
+
const primaryKeysTypes = Array.from(primaryKeys).map((key) => {
|
|
26
|
+
return {
|
|
27
|
+
key,
|
|
28
|
+
sqlType: fields[key].sqlType
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
24
32
|
function fixInput (input) {
|
|
25
33
|
const newInput = {}
|
|
26
34
|
for (const key of Object.keys(input)) {
|
|
@@ -46,7 +54,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
46
54
|
for (const key of Object.keys(output)) {
|
|
47
55
|
let value = output[key]
|
|
48
56
|
const newKey = fieldMapToRetrieve[key]
|
|
49
|
-
if (key
|
|
57
|
+
if (primaryKeys.has(key) && value !== null && value !== undefined) {
|
|
50
58
|
value = value.toString()
|
|
51
59
|
}
|
|
52
60
|
newOutput[newKey] = value
|
|
@@ -62,23 +70,38 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
62
70
|
// args.input is not array
|
|
63
71
|
const fieldsToRetrieve = computeFields(args.fields).map((f) => sql.ident(f))
|
|
64
72
|
const input = fixInput(args.input)
|
|
73
|
+
|
|
74
|
+
let hasPrimaryKeys = true
|
|
75
|
+
for (const key of primaryKeys) {
|
|
76
|
+
if (input[key] === undefined) {
|
|
77
|
+
hasPrimaryKeys = false
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
let now
|
|
66
83
|
if (autoTimestamp && fields.updated_at) {
|
|
67
84
|
now = new Date()
|
|
68
85
|
input.updated_at = now
|
|
69
86
|
}
|
|
70
|
-
if (
|
|
71
|
-
const res = await queries.updateOne(db, sql, table, input,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (autoTimestamp && fields.inserted_at) {
|
|
75
|
-
/* istanbul ignore next */
|
|
76
|
-
now = now || new Date()
|
|
77
|
-
input.inserted_at = now
|
|
87
|
+
if (hasPrimaryKeys) { // update
|
|
88
|
+
const res = await queries.updateOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
|
|
89
|
+
if (res) {
|
|
90
|
+
return fixOutput(res)
|
|
78
91
|
}
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
// If we are here, the record does not exist, so we create it
|
|
93
|
+
// this is inefficient because it will do 2 queries.
|
|
94
|
+
// TODO there is a way to do it in one query with DB specific syntax.
|
|
81
95
|
}
|
|
96
|
+
|
|
97
|
+
// insert
|
|
98
|
+
if (autoTimestamp && fields.inserted_at) {
|
|
99
|
+
/* istanbul ignore next */
|
|
100
|
+
now = now || new Date()
|
|
101
|
+
input.inserted_at = now
|
|
102
|
+
}
|
|
103
|
+
const res = await queries.insertOne(db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve)
|
|
104
|
+
return fixOutput(res)
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
async function insert (args) {
|
|
@@ -101,14 +124,14 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
101
124
|
/* istanbul ignore next */
|
|
102
125
|
if (queries.insertMany) {
|
|
103
126
|
// We are not fixing the input here because it is done in the query.
|
|
104
|
-
const res = await queries.insertMany(db, sql, table, inputs, inputToFieldMap,
|
|
127
|
+
const res = await queries.insertMany(db, sql, table, schema, inputs, inputToFieldMap, primaryKeysTypes, fieldsToRetrieve, fields)
|
|
105
128
|
return res.map(fixOutput)
|
|
106
129
|
} else {
|
|
107
130
|
// TODO this can be optimized, we can still use a batch insert if we do not want any fields
|
|
108
131
|
const res = []
|
|
109
132
|
for (let input of inputs) {
|
|
110
133
|
input = fixInput(input)
|
|
111
|
-
const resOne = await queries.insertOne(db, sql, table,
|
|
134
|
+
const resOne = await queries.insertOne(db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve)
|
|
112
135
|
res.push(fixOutput(resOne))
|
|
113
136
|
}
|
|
114
137
|
|
|
@@ -116,6 +139,24 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
116
139
|
}
|
|
117
140
|
}
|
|
118
141
|
|
|
142
|
+
async function updateMany (args) {
|
|
143
|
+
const db = args.tx || defaultDb
|
|
144
|
+
const fieldsToRetrieve = computeFields(args.fields).map((f) => sql.ident(f))
|
|
145
|
+
if (args.input === undefined) {
|
|
146
|
+
throw new Error('Input not provided.')
|
|
147
|
+
}
|
|
148
|
+
const input = fixInput(args.input)
|
|
149
|
+
let now
|
|
150
|
+
if (autoTimestamp && fields.updated_at) {
|
|
151
|
+
now = new Date()
|
|
152
|
+
input.updated_at = now
|
|
153
|
+
}
|
|
154
|
+
const criteria = computeCriteria(args)
|
|
155
|
+
|
|
156
|
+
const res = await queries.updateMany(db, sql, table, schema, criteria, input, fieldsToRetrieve)
|
|
157
|
+
return res.map(fixOutput)
|
|
158
|
+
}
|
|
159
|
+
|
|
119
160
|
function computeFields (fields) {
|
|
120
161
|
if (!fields) {
|
|
121
162
|
return Object.values(inputToFieldMap)
|
|
@@ -147,7 +188,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
147
188
|
gt: '>',
|
|
148
189
|
gte: '>=',
|
|
149
190
|
lt: '<',
|
|
150
|
-
lte: '<='
|
|
191
|
+
lte: '<=',
|
|
192
|
+
like: 'LIKE'
|
|
151
193
|
}
|
|
152
194
|
|
|
153
195
|
function computeCriteria (opts) {
|
|
@@ -168,6 +210,14 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
168
210
|
criteria.push(sql`${sql.ident(field)} IS NULL`)
|
|
169
211
|
} else if (operator === '<>' && value[key] === null) {
|
|
170
212
|
criteria.push(sql`${sql.ident(field)} IS NOT NULL`)
|
|
213
|
+
} else if (operator === 'LIKE') {
|
|
214
|
+
let leftHand = sql.ident(field)
|
|
215
|
+
// NOTE: cast fields AS CHAR(64) and TRIM the whitespaces
|
|
216
|
+
// to prevent errors with fields different than VARCHAR & TEXT
|
|
217
|
+
if (!['text', 'varchar'].includes(fieldWrap.sqlType)) {
|
|
218
|
+
leftHand = sql`TRIM(CAST(${sql.ident(field)} AS CHAR(64)))`
|
|
219
|
+
}
|
|
220
|
+
criteria.push(sql`${leftHand} LIKE ${value[key]}`)
|
|
171
221
|
} else {
|
|
172
222
|
criteria.push(sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`)
|
|
173
223
|
}
|
|
@@ -197,9 +247,10 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
197
247
|
const db = opts.tx || defaultDb
|
|
198
248
|
const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
|
|
199
249
|
const criteria = computeCriteria(opts)
|
|
250
|
+
|
|
200
251
|
let query = sql`
|
|
201
252
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
202
|
-
FROM ${sql
|
|
253
|
+
FROM ${tableName(sql, table, schema)}
|
|
203
254
|
`
|
|
204
255
|
|
|
205
256
|
if (criteria.length > 0) {
|
|
@@ -232,7 +283,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
232
283
|
let totalCountQuery = null
|
|
233
284
|
totalCountQuery = sql`
|
|
234
285
|
SELECT COUNT(*) AS total
|
|
235
|
-
FROM ${sql
|
|
286
|
+
FROM ${tableName(sql, table, schema)}
|
|
236
287
|
`
|
|
237
288
|
const criteria = computeCriteria(opts)
|
|
238
289
|
if (criteria.length > 0) {
|
|
@@ -246,7 +297,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
246
297
|
const db = opts.tx || defaultDb
|
|
247
298
|
const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
|
|
248
299
|
const criteria = computeCriteria(opts)
|
|
249
|
-
const res = await queries.deleteAll(db, sql, table, criteria, fieldsToRetrieve)
|
|
300
|
+
const res = await queries.deleteAll(db, sql, table, schema, criteria, fieldsToRetrieve)
|
|
250
301
|
return res.map(fixOutput)
|
|
251
302
|
}
|
|
252
303
|
|
|
@@ -254,8 +305,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
254
305
|
name: entityName,
|
|
255
306
|
singularName: camelcase(singularize(table)),
|
|
256
307
|
pluralName: camelcase(table),
|
|
257
|
-
|
|
308
|
+
primaryKeys,
|
|
258
309
|
table,
|
|
310
|
+
schema,
|
|
259
311
|
fields,
|
|
260
312
|
camelCasedFields,
|
|
261
313
|
fixInput,
|
|
@@ -264,28 +316,48 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
264
316
|
count,
|
|
265
317
|
insert,
|
|
266
318
|
save,
|
|
267
|
-
delete: _delete
|
|
319
|
+
delete: _delete,
|
|
320
|
+
updateMany
|
|
268
321
|
}
|
|
269
322
|
}
|
|
270
323
|
|
|
271
|
-
async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore) {
|
|
324
|
+
async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, ignore) {
|
|
272
325
|
// Compute the columns
|
|
273
|
-
const columns = (await queries.listColumns(db, sql, table)).filter((c) => !ignore[c.column_name])
|
|
326
|
+
const columns = (await queries.listColumns(db, sql, table, schema)).filter((c) => !ignore[c.column_name])
|
|
274
327
|
const fields = columns.reduce((acc, column) => {
|
|
275
328
|
acc[column.column_name] = {
|
|
276
329
|
sqlType: column.udt_name,
|
|
277
330
|
isNullable: column.is_nullable === 'YES'
|
|
278
331
|
}
|
|
332
|
+
|
|
333
|
+
// To get enum values in mysql and mariadb
|
|
334
|
+
/* istanbul ignore next */
|
|
335
|
+
if (column.udt_name === 'enum') {
|
|
336
|
+
acc[column.column_name].enum = column.column_type.match(/'(.+?)'/g).map(enumValue => enumValue.slice(1, enumValue.length - 1))
|
|
337
|
+
}
|
|
338
|
+
|
|
279
339
|
if (autoTimestamp && (column.column_name === 'updated_at' || column.column_name === 'inserted_at')) {
|
|
280
340
|
acc[column.column_name].autoTimestamp = true
|
|
281
341
|
}
|
|
282
342
|
return acc
|
|
283
343
|
}, {})
|
|
284
344
|
|
|
345
|
+
// To get enum values in pg
|
|
346
|
+
/* istanbul ignore next */
|
|
347
|
+
if (db.isPg) {
|
|
348
|
+
const enums = await queries.listEnumValues(db, sql, table, schema)
|
|
349
|
+
for (const enumValue of enums) {
|
|
350
|
+
if (!fields[enumValue.column_name].enum) {
|
|
351
|
+
fields[enumValue.column_name].enum = [enumValue.enumlabel]
|
|
352
|
+
} else {
|
|
353
|
+
fields[enumValue.column_name].enum.push(enumValue.enumlabel)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
285
357
|
const currentRelations = []
|
|
286
358
|
|
|
287
|
-
const constraintsList = await queries.listConstraints(db, sql, table)
|
|
288
|
-
|
|
359
|
+
const constraintsList = await queries.listConstraints(db, sql, table, schema)
|
|
360
|
+
const primaryKeys = new Set()
|
|
289
361
|
|
|
290
362
|
for (const constraint of constraintsList) {
|
|
291
363
|
const field = fields[constraint.column_name]
|
|
@@ -300,12 +372,12 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
300
372
|
}
|
|
301
373
|
|
|
302
374
|
if (constraint.constraint_type === 'PRIMARY KEY') {
|
|
303
|
-
|
|
375
|
+
primaryKeys.add(constraint.column_name)
|
|
304
376
|
// Check for SQLite typeless PK
|
|
305
377
|
/* istanbul ignore next */
|
|
306
378
|
if (db.isSQLite) {
|
|
307
379
|
const validTypes = ['integer', 'uuid', 'serial']
|
|
308
|
-
const pkType = fields[
|
|
380
|
+
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
309
381
|
if (!validTypes.includes(pkType)) {
|
|
310
382
|
throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
|
|
311
383
|
}
|
|
@@ -319,7 +391,7 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
319
391
|
}
|
|
320
392
|
}
|
|
321
393
|
|
|
322
|
-
const entity = createMapper(db, sql, log, table, fields,
|
|
394
|
+
const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema)
|
|
323
395
|
entity.relations = currentRelations
|
|
324
396
|
|
|
325
397
|
return entity
|
|
@@ -1,62 +1,117 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const { tableName } = require('../utils')
|
|
4
|
+
|
|
5
|
+
async function listTables (db, sql, schemas) {
|
|
6
|
+
if (schemas) {
|
|
7
|
+
const schemaList = sql.__dangerous__rawValue(schemas.map(s => `'${s}'`))
|
|
8
|
+
const res = await db.query(sql`
|
|
9
|
+
SELECT TABLE_SCHEMA, TABLE_NAME
|
|
10
|
+
FROM information_schema.tables
|
|
11
|
+
WHERE table_schema in (${schemaList})
|
|
12
|
+
`)
|
|
13
|
+
return res.map(r => ({ schema: r.TABLE_SCHEMA, table: r.TABLE_NAME }))
|
|
14
|
+
} else {
|
|
15
|
+
const res = await db.query(sql`
|
|
16
|
+
SELECT TABLE_SCHEMA, TABLE_NAME
|
|
17
|
+
FROM information_schema.tables
|
|
18
|
+
WHERE table_schema = (SELECT DATABASE())
|
|
19
|
+
`)
|
|
20
|
+
return res.map(r => ({ schema: r.TABLE_SCHEMA, table: r.TABLE_NAME }))
|
|
21
|
+
}
|
|
10
22
|
}
|
|
11
23
|
|
|
12
|
-
async function listColumns (db, sql, table) {
|
|
13
|
-
const
|
|
14
|
-
SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable
|
|
24
|
+
async function listColumns (db, sql, table, schema) {
|
|
25
|
+
const query = sql`
|
|
26
|
+
SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type
|
|
15
27
|
FROM information_schema.columns
|
|
16
28
|
WHERE table_name = ${table}
|
|
17
|
-
AND table_schema =
|
|
18
|
-
`
|
|
19
|
-
return
|
|
29
|
+
AND table_schema = ${schema}
|
|
30
|
+
`
|
|
31
|
+
return db.query(query)
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
async function listConstraints (db, sql, table) {
|
|
23
|
-
const
|
|
34
|
+
async function listConstraints (db, sql, table, schema) {
|
|
35
|
+
const query = sql`
|
|
24
36
|
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
37
|
FROM information_schema.table_constraints t
|
|
26
38
|
JOIN information_schema.key_column_usage k
|
|
27
39
|
USING (constraint_name, table_schema, table_name)
|
|
28
40
|
WHERE t.table_name = ${table}
|
|
29
|
-
AND t.table_schema =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return res
|
|
41
|
+
AND t.table_schema = ${schema}
|
|
42
|
+
`
|
|
43
|
+
return db.query(query)
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
async function updateOne (db, sql, table, input,
|
|
46
|
+
async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
|
|
36
47
|
const pairs = Object.keys(input).map((key) => {
|
|
37
|
-
|
|
48
|
+
let value = input[key]
|
|
49
|
+
/* istanbul ignore next */
|
|
50
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
51
|
+
value = JSON.stringify(value)
|
|
52
|
+
}
|
|
38
53
|
return sql`${sql.ident(key)} = ${value}`
|
|
39
54
|
})
|
|
55
|
+
const where = []
|
|
56
|
+
for (const key of primaryKeys) {
|
|
57
|
+
where.push(sql`${sql.ident(key)} = ${input[key]}`)
|
|
58
|
+
}
|
|
40
59
|
const update = sql`
|
|
41
|
-
UPDATE ${sql
|
|
60
|
+
UPDATE ${tableName(sql, table, schema)}
|
|
42
61
|
SET ${sql.join(pairs, sql`, `)}
|
|
43
|
-
WHERE ${sql.
|
|
62
|
+
WHERE ${sql.join(where, sql` AND `)}
|
|
44
63
|
`
|
|
45
64
|
await db.query(update)
|
|
46
65
|
|
|
47
66
|
const select = sql`
|
|
48
67
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
49
|
-
FROM ${sql
|
|
50
|
-
WHERE ${sql.
|
|
68
|
+
FROM ${tableName(sql, table, schema)}
|
|
69
|
+
WHERE ${sql.join(where, sql` AND `)}
|
|
51
70
|
`
|
|
52
71
|
|
|
53
72
|
const res = await db.query(select)
|
|
54
73
|
return res[0]
|
|
55
74
|
}
|
|
56
75
|
|
|
76
|
+
async function updateMany (db, sql, table, schema, criteria, input, fieldsToRetrieve) {
|
|
77
|
+
const pairs = Object.keys(input).map((key) => {
|
|
78
|
+
let value = input[key]
|
|
79
|
+
/* istanbul ignore next */
|
|
80
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
81
|
+
value = JSON.stringify(value)
|
|
82
|
+
}
|
|
83
|
+
return sql`${sql.ident(key)} = ${value}`
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const selectIds = sql`
|
|
87
|
+
SELECT id
|
|
88
|
+
FROM ${tableName(sql, table, schema)}
|
|
89
|
+
WHERE ${sql.join(criteria, sql` AND `)}
|
|
90
|
+
`
|
|
91
|
+
const resp = await db.query(selectIds)
|
|
92
|
+
const ids = resp.map(({ id }) => id)
|
|
93
|
+
|
|
94
|
+
const update = sql`
|
|
95
|
+
UPDATE ${tableName(sql, table, schema)}
|
|
96
|
+
SET ${sql.join(pairs, sql`, `)}
|
|
97
|
+
WHERE ${sql.join(criteria, sql` AND `)}
|
|
98
|
+
`
|
|
99
|
+
|
|
100
|
+
await db.query(update)
|
|
101
|
+
|
|
102
|
+
const select = sql`
|
|
103
|
+
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
104
|
+
FROM ${tableName(sql, table, schema)}
|
|
105
|
+
WHERE id IN (${ids});
|
|
106
|
+
`
|
|
107
|
+
const res = await db.query(select)
|
|
108
|
+
return res
|
|
109
|
+
}
|
|
110
|
+
|
|
57
111
|
module.exports = {
|
|
58
112
|
listTables,
|
|
59
113
|
listColumns,
|
|
60
114
|
listConstraints,
|
|
61
|
-
updateOne
|
|
115
|
+
updateOne,
|
|
116
|
+
updateMany
|
|
62
117
|
}
|
package/lib/queries/mysql.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const { insertPrep } = require('./shared')
|
|
4
4
|
const shared = require('./mysql-shared')
|
|
5
|
+
const { tableName } = require('../utils')
|
|
5
6
|
|
|
6
|
-
function insertOne (db, sql, table,
|
|
7
|
+
function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
|
|
7
8
|
const keysToSql = Object.keys(input).map((key) => sql.ident(key))
|
|
8
9
|
const keys = sql.join(
|
|
9
10
|
keysToSql,
|
|
@@ -11,6 +12,11 @@ function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRetrieve
|
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
const valuesToSql = Object.keys(input).map((key) => {
|
|
15
|
+
/* istanbul ignore next */
|
|
16
|
+
if (input[key] && typeof input[key] === 'object' && !(input[key] instanceof Date)) {
|
|
17
|
+
// This is a JSON field
|
|
18
|
+
return sql.value(JSON.stringify(input[key]))
|
|
19
|
+
}
|
|
14
20
|
return sql.value(input[key])
|
|
15
21
|
})
|
|
16
22
|
const values = sql.join(
|
|
@@ -18,55 +24,97 @@ function insertOne (db, sql, table, input, primaryKey, useUUID, fieldsToRetrieve
|
|
|
18
24
|
sql`, `
|
|
19
25
|
)
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
if (primaryKeys.length === 1 && input[primaryKeys[0].key] === undefined) {
|
|
28
|
+
return db.tx(async function (db) {
|
|
29
|
+
const insert = sql`
|
|
30
|
+
INSERT INTO ${tableName(sql, table, schema)} (${keys})
|
|
24
31
|
VALUES(${values})
|
|
25
32
|
`
|
|
26
|
-
|
|
33
|
+
await db.query(insert)
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
const res2 = await db.query(sql`
|
|
29
36
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
30
|
-
FROM ${sql
|
|
31
|
-
WHERE ${sql.ident(
|
|
37
|
+
FROM ${tableName(sql, table, schema)}
|
|
38
|
+
WHERE ${sql.ident(primaryKeys[0].key)} = (
|
|
32
39
|
SELECT last_insert_id()
|
|
33
40
|
)
|
|
34
41
|
`)
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
return res2[0]
|
|
44
|
+
})
|
|
45
|
+
} else {
|
|
46
|
+
const where = []
|
|
47
|
+
for (const { key } of primaryKeys) {
|
|
48
|
+
// TODO write a test that cover this
|
|
49
|
+
/* istanbul ignore next */
|
|
50
|
+
if (!input[key]) {
|
|
51
|
+
throw new Error(`Missing value for primary key ${key}`)
|
|
52
|
+
}
|
|
53
|
+
where.push(sql`${sql.ident(key)} = ${input[key]}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return db.tx(async function (db) {
|
|
57
|
+
const insert = sql`
|
|
58
|
+
INSERT INTO ${tableName(sql, table, schema)} (${keys})
|
|
59
|
+
VALUES(${values})
|
|
60
|
+
`
|
|
61
|
+
await db.query(insert)
|
|
62
|
+
|
|
63
|
+
const res2 = await db.query(sql`
|
|
64
|
+
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
65
|
+
FROM ${tableName(sql, table, schema)}
|
|
66
|
+
WHERE ${sql.join(where, sql` AND `)}
|
|
67
|
+
`)
|
|
68
|
+
|
|
69
|
+
return res2[0]
|
|
70
|
+
})
|
|
71
|
+
}
|
|
38
72
|
}
|
|
39
73
|
|
|
40
|
-
function insertMany (db, sql, table, inputs, inputToFieldMap,
|
|
74
|
+
function insertMany (db, sql, table, schema, inputs, inputToFieldMap, primaryKeys, fieldsToRetrieve, fields) {
|
|
41
75
|
return db.tx(async function (db) {
|
|
42
76
|
const { keys, values } = insertPrep(inputs, inputToFieldMap, fields, sql)
|
|
43
77
|
const insert = sql`
|
|
44
|
-
insert into ${sql
|
|
78
|
+
insert into ${tableName(sql, table, schema)} (${keys})
|
|
45
79
|
values ${sql.join(values, sql`, `)}
|
|
46
80
|
`
|
|
47
81
|
|
|
48
82
|
await db.query(insert)
|
|
49
83
|
|
|
84
|
+
const orderBy = []
|
|
85
|
+
for (const { key } of primaryKeys) {
|
|
86
|
+
orderBy.push(sql`${sql.ident(key)} DESC`)
|
|
87
|
+
}
|
|
88
|
+
|
|
50
89
|
const res = await db.query(sql`
|
|
51
90
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
52
|
-
FROM ${sql
|
|
53
|
-
ORDER BY ${sql.
|
|
91
|
+
FROM ${tableName(sql, table, schema)}
|
|
92
|
+
ORDER BY ${sql.join(orderBy, sql`, `)}
|
|
54
93
|
LIMIT ${inputs.length}
|
|
55
94
|
`)
|
|
56
95
|
|
|
57
96
|
// To make consistent with shared.insertMany
|
|
58
97
|
res.sort(function (a, b) {
|
|
59
|
-
|
|
98
|
+
let val = 0
|
|
99
|
+
for (const { key } of primaryKeys) {
|
|
100
|
+
val = a[key] - b[key]
|
|
101
|
+
if (val !== 0) {
|
|
102
|
+
return val
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// The following should never happen
|
|
106
|
+
/* istanbul ignore next */
|
|
107
|
+
return val
|
|
60
108
|
})
|
|
61
109
|
return res
|
|
62
110
|
})
|
|
63
111
|
}
|
|
64
112
|
|
|
65
|
-
function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
|
|
113
|
+
function deleteAll (db, sql, table, schema, criteria, fieldsToRetrieve) {
|
|
66
114
|
return db.tx(async function (db) {
|
|
67
115
|
let selectQuery = sql`
|
|
68
116
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
69
|
-
FROM ${sql
|
|
117
|
+
FROM ${tableName(sql, table, schema)}
|
|
70
118
|
`
|
|
71
119
|
/* istanbul ignore else */
|
|
72
120
|
if (criteria.length > 0) {
|
|
@@ -79,7 +127,7 @@ function deleteAll (db, sql, table, criteria, fieldsToRetrieve) {
|
|
|
79
127
|
const res = await db.query(selectQuery)
|
|
80
128
|
|
|
81
129
|
let deleteQuery = sql`
|
|
82
|
-
DELETE FROM ${sql
|
|
130
|
+
DELETE FROM ${tableName(sql, table, schema)}
|
|
83
131
|
`
|
|
84
132
|
|
|
85
133
|
/* istanbul ignore else */
|
package/lib/queries/pg.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const shared = require('./shared')
|
|
4
|
+
const { tableName } = require('../utils')
|
|
4
5
|
|
|
5
|
-
async function insertOne (db, sql, table,
|
|
6
|
+
async function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
|
|
6
7
|
const inputKeys = Object.keys(input)
|
|
7
8
|
if (inputKeys.length === 0) {
|
|
8
9
|
const insert = sql`
|
|
9
|
-
INSERT INTO ${sql
|
|
10
|
+
INSERT INTO ${tableName(sql, table, schema)}
|
|
10
11
|
DEFAULT VALUES
|
|
11
12
|
RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
12
13
|
`
|
|
@@ -14,36 +15,46 @@ async function insertOne (db, sql, table, input, primaryKey, isUuid, fieldsToRet
|
|
|
14
15
|
return res[0]
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
return shared.insertOne(db, sql, table,
|
|
18
|
+
return shared.insertOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
module.exports.insertOne = insertOne
|
|
21
22
|
module.exports.deleteAll = shared.deleteAll
|
|
22
23
|
module.exports.insertMany = shared.insertMany
|
|
23
24
|
|
|
24
|
-
async function listTables (db, sql) {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
async function listTables (db, sql, schemas) {
|
|
26
|
+
if (schemas) {
|
|
27
|
+
const schemaList = sql.__dangerous__rawValue(schemas.map(s => `'${s}'`))
|
|
28
|
+
const res = await db.query(sql`
|
|
29
|
+
SELECT tablename, schemaname
|
|
30
|
+
FROM pg_catalog.pg_tables
|
|
31
|
+
WHERE
|
|
32
|
+
schemaname in (${schemaList})`)
|
|
33
|
+
return res.map(r => ({ schema: r.schemaname, table: r.tablename }))
|
|
34
|
+
}
|
|
35
|
+
const res = await db.query(sql`
|
|
36
|
+
SELECT tablename, schemaname
|
|
27
37
|
FROM pg_catalog.pg_tables
|
|
28
38
|
WHERE
|
|
29
39
|
schemaname = current_schema()
|
|
30
|
-
|
|
40
|
+
`)
|
|
41
|
+
return res.map(r => ({ schema: r.schemaname, table: r.tablename }))
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
module.exports.listTables = listTables
|
|
34
45
|
|
|
35
|
-
async function listColumns (db, sql, table) {
|
|
46
|
+
async function listColumns (db, sql, table, schema) {
|
|
36
47
|
return db.query(sql`
|
|
37
48
|
SELECT column_name, udt_name, is_nullable
|
|
38
49
|
FROM information_schema.columns
|
|
39
50
|
WHERE table_name = ${table}
|
|
40
|
-
AND table_schema =
|
|
51
|
+
AND table_schema = ${schema}
|
|
41
52
|
`)
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
module.exports.listColumns = listColumns
|
|
45
56
|
|
|
46
|
-
async function listConstraints (db, sql, table) {
|
|
57
|
+
async function listConstraints (db, sql, table, schema) {
|
|
47
58
|
const query = sql`
|
|
48
59
|
SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
|
|
49
60
|
FROM information_schema.table_constraints constraints
|
|
@@ -52,24 +63,28 @@ async function listConstraints (db, sql, table) {
|
|
|
52
63
|
AND constraints.table_name = ${table}
|
|
53
64
|
JOIN information_schema.constraint_column_usage usage2
|
|
54
65
|
ON usage.constraint_name = usage2.constraint_name
|
|
55
|
-
AND usage.table_name = ${table}
|
|
66
|
+
AND ( usage.table_name = ${table}
|
|
67
|
+
AND usage.table_schema = ${schema} )
|
|
56
68
|
`
|
|
57
|
-
|
|
58
69
|
const constraintsList = await db.query(query)
|
|
59
70
|
return constraintsList
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
module.exports.listConstraints = listConstraints
|
|
63
74
|
|
|
64
|
-
async function updateOne (db, sql, table, input,
|
|
75
|
+
async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
|
|
65
76
|
const pairs = Object.keys(input).map((key) => {
|
|
66
77
|
const value = input[key]
|
|
67
78
|
return sql`${sql.ident(key)} = ${value}`
|
|
68
79
|
})
|
|
80
|
+
const where = []
|
|
81
|
+
for (const key of primaryKeys) {
|
|
82
|
+
where.push(sql`${sql.ident(key)} = ${input[key]}`)
|
|
83
|
+
}
|
|
69
84
|
const update = sql`
|
|
70
|
-
UPDATE ${sql
|
|
85
|
+
UPDATE ${tableName(sql, table, schema)}
|
|
71
86
|
SET ${sql.join(pairs, sql`, `)}
|
|
72
|
-
WHERE ${sql.
|
|
87
|
+
WHERE ${sql.join(where, sql` AND `)}
|
|
73
88
|
RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
74
89
|
`
|
|
75
90
|
const res = await db.query(update)
|
|
@@ -77,3 +92,19 @@ async function updateOne (db, sql, table, input, primaryKey, fieldsToRetrieve) {
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
module.exports.updateOne = updateOne
|
|
95
|
+
|
|
96
|
+
module.exports.updateMany = shared.updateMany
|
|
97
|
+
|
|
98
|
+
async function listEnumValues (db, sql, table, schema) {
|
|
99
|
+
const query = sql`
|
|
100
|
+
SELECT udt_name, enumlabel, column_name
|
|
101
|
+
FROM pg_enum e
|
|
102
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
103
|
+
JOIN information_schema.columns c on c.udt_name = t.typname
|
|
104
|
+
WHERE table_name = ${table}
|
|
105
|
+
AND table_schema = ${schema};
|
|
106
|
+
`
|
|
107
|
+
return db.query(query)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports.listEnumValues = listEnumValues
|