@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 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, primaryKey, relations, queries, autoTimestamp) {
10
- const entityName = toSingular(table)
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 === primaryKey && value !== null && value !== undefined) {
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 (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
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
- const res = await queries.insertOne(db, sql, table, input, primaryKey, fields[primaryKey].sqlType.toLowerCase() === 'uuid', fieldsToRetrieve)
80
- return fixOutput(res)
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, primaryKey, fieldsToRetrieve, fields)
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, input, primaryKey, fields[primaryKey].sqlType.toLowerCase() === 'uuid', fieldsToRetrieve)
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.ident(table)}
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
- if (opts.limit || opts.offset !== undefined) {
236
- // Use Number.MAX_SAFE_INTEGER as default value for limit because in the sql query you cannot add OFFSET without LIMIT
237
- const limit = (opts.limit !== undefined) ? opts.limit : Number.MAX_SAFE_INTEGER
238
- query = sql`${query} LIMIT ${limit}`
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.ident(table)}
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: camelcase(singularize(table)),
274
- pluralName: camelcase(table),
275
- primaryKey,
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
- let primaryKey
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
- primaryKey = constraint.column_name
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[primaryKey].sqlType.toLowerCase()
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, primaryKey, currentRelations, queries, autoTimestamp)
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
- 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)
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 res = await db.query(sql`
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 = (SELECT DATABASE())
18
- `)
19
- return res
29
+ AND table_schema = ${schema}
30
+ `
31
+ return db.query(query)
20
32
  }
21
33
 
22
- async function listConstraints (db, sql, table) {
23
- const res = await db.query(sql`
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 = (SELECT DATABASE())
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, primaryKey, fieldsToRetrieve) {
46
+ async function updateOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
36
47
  const pairs = Object.keys(input).map((key) => {
37
- const value = input[key]
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.ident(table)}
60
+ UPDATE ${tableName(sql, table, schema)}
42
61
  SET ${sql.join(pairs, sql`, `)}
43
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
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.ident(table)}
50
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
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
- const value = input[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
+ }
60
83
  return sql`${sql.ident(key)} = ${value}`
61
84
  })
62
85
 
63
86
  const selectIds = sql`
64
87
  SELECT id
65
- FROM ${sql.ident(table)}
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.ident(table)}
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.ident(table)}
104
+ FROM ${tableName(sql, table, schema)}
82
105
  WHERE id IN (${ids});
83
106
  `
84
107
  const res = await db.query(select)
@@ -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, input, primaryKey, useUUID, fieldsToRetrieve) {
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
- return db.tx(async function (db) {
22
- const insert = sql`
23
- INSERT INTO ${sql.ident(table)} (${keys})
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
- await db.query(insert)
33
+ await db.query(insert)
27
34
 
28
- const res2 = await db.query(sql`
35
+ const res2 = await db.query(sql`
29
36
  SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
30
- FROM ${sql.ident(table)}
31
- WHERE ${sql.ident(primaryKey)} = (
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
- return res2[0]
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, primaryKey, fieldsToRetrieve, fields) {
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.ident(table)} (${keys})
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.ident(table)}
53
- ORDER BY ${sql.ident(primaryKey)} DESC
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
- return a.id - b.id
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.ident(table)}
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.ident(table)}
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, input, primaryKey, isUuid, fieldsToRetrieve) {
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.ident(table)}
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
- return (await db.query(sql`
26
- SELECT tablename
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
- `)).map(t => t.tablename)
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 = current_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, primaryKey, fieldsToRetrieve) {
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.ident(table)}
84
+ UPDATE ${tableName(sql, table, schema)}
71
85
  SET ${sql.join(pairs, sql`, `)}
72
- WHERE ${sql.ident(primaryKey)} = ${sql.value(input[primaryKey])}
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
- return (await db.query(sql`
85
- SELECT udt_name, enumlabel, column_name
86
- FROM pg_enum e
87
- JOIN pg_type t ON e.enumtypid = t.oid
88
- JOIN information_schema.columns c on c.udt_name = t.typname
89
- WHERE table_name = ${table};
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