@platformatic/sql-mapper 0.7.0 → 0.9.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 +78 -39
- package/lib/queries/mysql-shared.js +52 -29
- package/lib/queries/mysql.js +66 -18
- package/lib/queries/pg.js +40 -24
- package/lib/queries/shared.js +17 -9
- package/lib/queries/sqlite.js +48 -29
- package/lib/utils.js +39 -2
- package/mapper.d.ts +4 -0
- package/mapper.js +17 -11
- package/package.json +2 -2
- 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 +320 -0
- package/test/where.test.js +304 -0
package/lib/entity.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const camelcase = require('camelcase')
|
|
4
|
-
const { singularize } = require('inflected')
|
|
5
4
|
const {
|
|
6
|
-
toSingular
|
|
5
|
+
toSingular,
|
|
6
|
+
toUpperFirst,
|
|
7
|
+
tableName,
|
|
8
|
+
sanitizeLimit
|
|
7
9
|
} = require('./utils')
|
|
8
10
|
|
|
9
|
-
function createMapper (defaultDb, sql, log, table, fields,
|
|
10
|
-
|
|
11
|
+
function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
|
|
12
|
+
/* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
|
|
13
|
+
const entityName = useSchemaInName ? toUpperFirst(`${schema}${toSingular(table)}`) : toSingular(table)
|
|
14
|
+
/* istanbul ignore next */
|
|
15
|
+
const pluralName = camelcase(useSchemaInName ? camelcase(`${schema} ${table}`) : table)
|
|
16
|
+
const singularName = camelcase(entityName)
|
|
11
17
|
|
|
12
18
|
// Fields remapping
|
|
13
19
|
const fieldMapToRetrieve = {}
|
|
@@ -21,6 +27,13 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
21
27
|
return acc
|
|
22
28
|
}, {})
|
|
23
29
|
|
|
30
|
+
const primaryKeysTypes = Array.from(primaryKeys).map((key) => {
|
|
31
|
+
return {
|
|
32
|
+
key,
|
|
33
|
+
sqlType: fields[key].sqlType
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
24
37
|
function fixInput (input) {
|
|
25
38
|
const newInput = {}
|
|
26
39
|
for (const key of Object.keys(input)) {
|
|
@@ -46,7 +59,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
46
59
|
for (const key of Object.keys(output)) {
|
|
47
60
|
let value = output[key]
|
|
48
61
|
const newKey = fieldMapToRetrieve[key]
|
|
49
|
-
if (key
|
|
62
|
+
if (primaryKeys.has(key) && value !== null && value !== undefined) {
|
|
50
63
|
value = value.toString()
|
|
51
64
|
}
|
|
52
65
|
newOutput[newKey] = value
|
|
@@ -62,23 +75,38 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
62
75
|
// args.input is not array
|
|
63
76
|
const fieldsToRetrieve = computeFields(args.fields).map((f) => sql.ident(f))
|
|
64
77
|
const input = fixInput(args.input)
|
|
78
|
+
|
|
79
|
+
let hasPrimaryKeys = true
|
|
80
|
+
for (const key of primaryKeys) {
|
|
81
|
+
if (input[key] === undefined) {
|
|
82
|
+
hasPrimaryKeys = false
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
65
87
|
let now
|
|
66
88
|
if (autoTimestamp && fields.updated_at) {
|
|
67
89
|
now = new Date()
|
|
68
90
|
input.updated_at = now
|
|
69
91
|
}
|
|
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
|
|
92
|
+
if (hasPrimaryKeys) { // update
|
|
93
|
+
const res = await queries.updateOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
|
|
94
|
+
if (res) {
|
|
95
|
+
return fixOutput(res)
|
|
78
96
|
}
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
// If we are here, the record does not exist, so we create it
|
|
98
|
+
// this is inefficient because it will do 2 queries.
|
|
99
|
+
// TODO there is a way to do it in one query with DB specific syntax.
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// insert
|
|
103
|
+
if (autoTimestamp && fields.inserted_at) {
|
|
104
|
+
/* istanbul ignore next */
|
|
105
|
+
now = now || new Date()
|
|
106
|
+
input.inserted_at = now
|
|
81
107
|
}
|
|
108
|
+
const res = await queries.insertOne(db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve)
|
|
109
|
+
return fixOutput(res)
|
|
82
110
|
}
|
|
83
111
|
|
|
84
112
|
async function insert (args) {
|
|
@@ -101,14 +129,14 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
101
129
|
/* istanbul ignore next */
|
|
102
130
|
if (queries.insertMany) {
|
|
103
131
|
// 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,
|
|
132
|
+
const res = await queries.insertMany(db, sql, table, schema, inputs, inputToFieldMap, primaryKeysTypes, fieldsToRetrieve, fields)
|
|
105
133
|
return res.map(fixOutput)
|
|
106
134
|
} else {
|
|
107
135
|
// TODO this can be optimized, we can still use a batch insert if we do not want any fields
|
|
108
136
|
const res = []
|
|
109
137
|
for (let input of inputs) {
|
|
110
138
|
input = fixInput(input)
|
|
111
|
-
const resOne = await queries.insertOne(db, sql, table,
|
|
139
|
+
const resOne = await queries.insertOne(db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve)
|
|
112
140
|
res.push(fixOutput(resOne))
|
|
113
141
|
}
|
|
114
142
|
|
|
@@ -130,7 +158,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
130
158
|
}
|
|
131
159
|
const criteria = computeCriteria(args)
|
|
132
160
|
|
|
133
|
-
const res = await queries.updateMany(db, sql, table, criteria, input, fieldsToRetrieve)
|
|
161
|
+
const res = await queries.updateMany(db, sql, table, schema, criteria, input, fieldsToRetrieve)
|
|
134
162
|
return res.map(fixOutput)
|
|
135
163
|
}
|
|
136
164
|
|
|
@@ -165,7 +193,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
165
193
|
gt: '>',
|
|
166
194
|
gte: '>=',
|
|
167
195
|
lt: '<',
|
|
168
|
-
lte: '<='
|
|
196
|
+
lte: '<=',
|
|
197
|
+
like: 'LIKE'
|
|
169
198
|
}
|
|
170
199
|
|
|
171
200
|
function computeCriteria (opts) {
|
|
@@ -186,6 +215,14 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
186
215
|
criteria.push(sql`${sql.ident(field)} IS NULL`)
|
|
187
216
|
} else if (operator === '<>' && value[key] === null) {
|
|
188
217
|
criteria.push(sql`${sql.ident(field)} IS NOT NULL`)
|
|
218
|
+
} else if (operator === 'LIKE') {
|
|
219
|
+
let leftHand = sql.ident(field)
|
|
220
|
+
// NOTE: cast fields AS CHAR(64) and TRIM the whitespaces
|
|
221
|
+
// to prevent errors with fields different than VARCHAR & TEXT
|
|
222
|
+
if (!['text', 'varchar'].includes(fieldWrap.sqlType)) {
|
|
223
|
+
leftHand = sql`TRIM(CAST(${sql.ident(field)} AS CHAR(64)))`
|
|
224
|
+
}
|
|
225
|
+
criteria.push(sql`${leftHand} LIKE ${value[key]}`)
|
|
189
226
|
} else {
|
|
190
227
|
criteria.push(sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`)
|
|
191
228
|
}
|
|
@@ -215,9 +252,10 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
215
252
|
const db = opts.tx || defaultDb
|
|
216
253
|
const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
|
|
217
254
|
const criteria = computeCriteria(opts)
|
|
255
|
+
|
|
218
256
|
let query = sql`
|
|
219
257
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
220
|
-
FROM ${sql
|
|
258
|
+
FROM ${tableName(sql, table, schema)}
|
|
221
259
|
`
|
|
222
260
|
|
|
223
261
|
if (criteria.length > 0) {
|
|
@@ -232,13 +270,12 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
232
270
|
query = sql`${query} ORDER BY ${sql.join(orderBy, sql`, `)}`
|
|
233
271
|
}
|
|
234
272
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (opts.offset !== undefined) {
|
|
240
|
-
query = sql`${query} OFFSET ${opts.offset}`
|
|
273
|
+
query = sql`${query} LIMIT ${sanitizeLimit(opts.limit, limitConfig)}`
|
|
274
|
+
if (opts.offset !== undefined) {
|
|
275
|
+
if (opts.offset < 0) {
|
|
276
|
+
throw new Error(`Param offset=${opts.offset} not allowed. It must be not negative value.`)
|
|
241
277
|
}
|
|
278
|
+
query = sql`${query} OFFSET ${opts.offset}`
|
|
242
279
|
}
|
|
243
280
|
|
|
244
281
|
const res = await db.query(query)
|
|
@@ -250,7 +287,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
250
287
|
let totalCountQuery = null
|
|
251
288
|
totalCountQuery = sql`
|
|
252
289
|
SELECT COUNT(*) AS total
|
|
253
|
-
FROM ${sql
|
|
290
|
+
FROM ${tableName(sql, table, schema)}
|
|
254
291
|
`
|
|
255
292
|
const criteria = computeCriteria(opts)
|
|
256
293
|
if (criteria.length > 0) {
|
|
@@ -264,16 +301,17 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
264
301
|
const db = opts.tx || defaultDb
|
|
265
302
|
const fieldsToRetrieve = computeFields(opts.fields).map((f) => sql.ident(f))
|
|
266
303
|
const criteria = computeCriteria(opts)
|
|
267
|
-
const res = await queries.deleteAll(db, sql, table, criteria, fieldsToRetrieve)
|
|
304
|
+
const res = await queries.deleteAll(db, sql, table, schema, criteria, fieldsToRetrieve)
|
|
268
305
|
return res.map(fixOutput)
|
|
269
306
|
}
|
|
270
307
|
|
|
271
308
|
return {
|
|
272
309
|
name: entityName,
|
|
273
|
-
singularName
|
|
274
|
-
pluralName
|
|
275
|
-
|
|
310
|
+
singularName,
|
|
311
|
+
pluralName,
|
|
312
|
+
primaryKeys,
|
|
276
313
|
table,
|
|
314
|
+
schema,
|
|
277
315
|
fields,
|
|
278
316
|
camelCasedFields,
|
|
279
317
|
fixInput,
|
|
@@ -287,9 +325,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKey, relations
|
|
|
287
325
|
}
|
|
288
326
|
}
|
|
289
327
|
|
|
290
|
-
async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore) {
|
|
328
|
+
async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig) {
|
|
291
329
|
// Compute the columns
|
|
292
|
-
const columns = (await queries.listColumns(db, sql, table)).filter((c) => !ignore[c.column_name])
|
|
330
|
+
const columns = (await queries.listColumns(db, sql, table, schema)).filter((c) => !ignore[c.column_name])
|
|
293
331
|
const fields = columns.reduce((acc, column) => {
|
|
294
332
|
acc[column.column_name] = {
|
|
295
333
|
sqlType: column.udt_name,
|
|
@@ -307,10 +345,11 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
307
345
|
}
|
|
308
346
|
return acc
|
|
309
347
|
}, {})
|
|
348
|
+
|
|
310
349
|
// To get enum values in pg
|
|
311
350
|
/* istanbul ignore next */
|
|
312
351
|
if (db.isPg) {
|
|
313
|
-
const enums = await queries.listEnumValues(db, sql, table)
|
|
352
|
+
const enums = await queries.listEnumValues(db, sql, table, schema)
|
|
314
353
|
for (const enumValue of enums) {
|
|
315
354
|
if (!fields[enumValue.column_name].enum) {
|
|
316
355
|
fields[enumValue.column_name].enum = [enumValue.enumlabel]
|
|
@@ -321,8 +360,8 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
321
360
|
}
|
|
322
361
|
const currentRelations = []
|
|
323
362
|
|
|
324
|
-
const constraintsList = await queries.listConstraints(db, sql, table)
|
|
325
|
-
|
|
363
|
+
const constraintsList = await queries.listConstraints(db, sql, table, schema)
|
|
364
|
+
const primaryKeys = new Set()
|
|
326
365
|
|
|
327
366
|
for (const constraint of constraintsList) {
|
|
328
367
|
const field = fields[constraint.column_name]
|
|
@@ -337,12 +376,12 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
337
376
|
}
|
|
338
377
|
|
|
339
378
|
if (constraint.constraint_type === 'PRIMARY KEY') {
|
|
340
|
-
|
|
379
|
+
primaryKeys.add(constraint.column_name)
|
|
341
380
|
// Check for SQLite typeless PK
|
|
342
381
|
/* istanbul ignore next */
|
|
343
382
|
if (db.isSQLite) {
|
|
344
383
|
const validTypes = ['integer', 'uuid', 'serial']
|
|
345
|
-
const pkType = fields[
|
|
384
|
+
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
346
385
|
if (!validTypes.includes(pkType)) {
|
|
347
386
|
throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
|
|
348
387
|
}
|
|
@@ -356,7 +395,7 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, ignore)
|
|
|
356
395
|
}
|
|
357
396
|
}
|
|
358
397
|
|
|
359
|
-
const entity = createMapper(db, sql, log, table, fields,
|
|
398
|
+
const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema, useSchemaInName, limitConfig)
|
|
360
399
|
entity.relations = currentRelations
|
|
361
400
|
|
|
362
401
|
return entity
|
|
@@ -1,75 +1,98 @@
|
|
|
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
|
|
24
|
+
async function listColumns (db, sql, table, schema) {
|
|
25
|
+
const query = sql`
|
|
14
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
|
|
|
57
|
-
async function updateMany (db, sql, table, criteria, input, fieldsToRetrieve) {
|
|
76
|
+
async function updateMany (db, sql, table, schema, criteria, input, fieldsToRetrieve) {
|
|
58
77
|
const pairs = Object.keys(input).map((key) => {
|
|
59
|
-
|
|
78
|
+
let value = input[key]
|
|
79
|
+
/* istanbul ignore next */
|
|
80
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
81
|
+
value = JSON.stringify(value)
|
|
82
|
+
}
|
|
60
83
|
return sql`${sql.ident(key)} = ${value}`
|
|
61
84
|
})
|
|
62
85
|
|
|
63
86
|
const selectIds = sql`
|
|
64
87
|
SELECT id
|
|
65
|
-
FROM ${sql
|
|
88
|
+
FROM ${tableName(sql, table, schema)}
|
|
66
89
|
WHERE ${sql.join(criteria, sql` AND `)}
|
|
67
90
|
`
|
|
68
91
|
const resp = await db.query(selectIds)
|
|
69
92
|
const ids = resp.map(({ id }) => id)
|
|
70
93
|
|
|
71
94
|
const update = sql`
|
|
72
|
-
UPDATE ${sql
|
|
95
|
+
UPDATE ${tableName(sql, table, schema)}
|
|
73
96
|
SET ${sql.join(pairs, sql`, `)}
|
|
74
97
|
WHERE ${sql.join(criteria, sql` AND `)}
|
|
75
98
|
`
|
|
@@ -78,7 +101,7 @@ async function updateMany (db, sql, table, criteria, input, fieldsToRetrieve) {
|
|
|
78
101
|
|
|
79
102
|
const select = sql`
|
|
80
103
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
81
|
-
FROM ${sql
|
|
104
|
+
FROM ${tableName(sql, table, schema)}
|
|
82
105
|
WHERE id IN (${ids});
|
|
83
106
|
`
|
|
84
107
|
const res = await db.query(select)
|
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,49 +1,59 @@
|
|
|
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
|
`
|
|
13
14
|
const res = await db.query(insert)
|
|
14
15
|
return res[0]
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
return shared.insertOne(db, sql, table, input, primaryKey, isUuid, fieldsToRetrieve)
|
|
17
|
+
return shared.insertOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
module.exports.insertOne = insertOne
|
|
21
21
|
module.exports.deleteAll = shared.deleteAll
|
|
22
22
|
module.exports.insertMany = shared.insertMany
|
|
23
23
|
|
|
24
|
-
async function listTables (db, sql) {
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
async function listTables (db, sql, schemas) {
|
|
25
|
+
if (schemas) {
|
|
26
|
+
const schemaList = sql.__dangerous__rawValue(schemas.map(s => `'${s}'`))
|
|
27
|
+
const res = await db.query(sql`
|
|
28
|
+
SELECT tablename, schemaname
|
|
29
|
+
FROM pg_catalog.pg_tables
|
|
30
|
+
WHERE
|
|
31
|
+
schemaname in (${schemaList})`)
|
|
32
|
+
return res.map(r => ({ schema: r.schemaname, table: r.tablename }))
|
|
33
|
+
}
|
|
34
|
+
const res = await db.query(sql`
|
|
35
|
+
SELECT tablename, schemaname
|
|
27
36
|
FROM pg_catalog.pg_tables
|
|
28
37
|
WHERE
|
|
29
38
|
schemaname = current_schema()
|
|
30
|
-
|
|
39
|
+
`)
|
|
40
|
+
return res.map(r => ({ schema: r.schemaname, table: r.tablename }))
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
module.exports.listTables = listTables
|
|
34
44
|
|
|
35
|
-
async function listColumns (db, sql, table) {
|
|
45
|
+
async function listColumns (db, sql, table, schema) {
|
|
36
46
|
return db.query(sql`
|
|
37
47
|
SELECT column_name, udt_name, is_nullable
|
|
38
48
|
FROM information_schema.columns
|
|
39
49
|
WHERE table_name = ${table}
|
|
40
|
-
AND table_schema =
|
|
50
|
+
AND table_schema = ${schema}
|
|
41
51
|
`)
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
module.exports.listColumns = listColumns
|
|
45
55
|
|
|
46
|
-
async function listConstraints (db, sql, table) {
|
|
56
|
+
async function listConstraints (db, sql, table, schema) {
|
|
47
57
|
const query = sql`
|
|
48
58
|
SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
|
|
49
59
|
FROM information_schema.table_constraints constraints
|
|
@@ -52,24 +62,28 @@ async function listConstraints (db, sql, table) {
|
|
|
52
62
|
AND constraints.table_name = ${table}
|
|
53
63
|
JOIN information_schema.constraint_column_usage usage2
|
|
54
64
|
ON usage.constraint_name = usage2.constraint_name
|
|
55
|
-
AND usage.table_name = ${table}
|
|
65
|
+
AND ( usage.table_name = ${table}
|
|
66
|
+
AND usage.table_schema = ${schema} )
|
|
56
67
|
`
|
|
57
|
-
|
|
58
68
|
const constraintsList = await db.query(query)
|
|
59
69
|
return constraintsList
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
module.exports.listConstraints = listConstraints
|
|
63
73
|
|
|
64
|
-
async function updateOne (db, sql, table, input,
|
|
74
|
+
async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
|
|
65
75
|
const pairs = Object.keys(input).map((key) => {
|
|
66
76
|
const value = input[key]
|
|
67
77
|
return sql`${sql.ident(key)} = ${value}`
|
|
68
78
|
})
|
|
79
|
+
const where = []
|
|
80
|
+
for (const key of primaryKeys) {
|
|
81
|
+
where.push(sql`${sql.ident(key)} = ${input[key]}`)
|
|
82
|
+
}
|
|
69
83
|
const update = sql`
|
|
70
|
-
UPDATE ${sql
|
|
84
|
+
UPDATE ${tableName(sql, table, schema)}
|
|
71
85
|
SET ${sql.join(pairs, sql`, `)}
|
|
72
|
-
WHERE ${sql.
|
|
86
|
+
WHERE ${sql.join(where, sql` AND `)}
|
|
73
87
|
RETURNING ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
74
88
|
`
|
|
75
89
|
const res = await db.query(update)
|
|
@@ -80,14 +94,16 @@ module.exports.updateOne = updateOne
|
|
|
80
94
|
|
|
81
95
|
module.exports.updateMany = shared.updateMany
|
|
82
96
|
|
|
83
|
-
async function listEnumValues (db, sql, table) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
async function listEnumValues (db, sql, table, schema) {
|
|
98
|
+
const query = sql`
|
|
99
|
+
SELECT udt_name, enumlabel, column_name
|
|
100
|
+
FROM pg_enum e
|
|
101
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
102
|
+
JOIN information_schema.columns c on c.udt_name = t.typname
|
|
103
|
+
WHERE table_name = ${table}
|
|
104
|
+
AND table_schema = ${schema};
|
|
105
|
+
`
|
|
106
|
+
return db.query(query)
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
module.exports.listEnumValues = listEnumValues
|