@platformatic/sql-mapper 3.4.1 → 3.5.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/README.md +1 -1
- package/eslint.config.js +2 -2
- package/{mapper.d.ts → index.d.ts} +68 -10
- package/{mapper.js → index.js} +164 -75
- package/lib/cache.js +17 -16
- package/lib/clean-up.js +3 -7
- package/lib/connection-info.js +2 -4
- package/lib/cursor.js +94 -0
- package/lib/entity.js +182 -68
- package/lib/errors.js +66 -22
- package/lib/queries/index.js +4 -23
- package/lib/queries/mariadb.js +2 -11
- package/lib/queries/mysql-shared.js +9 -19
- package/lib/queries/mysql.js +12 -26
- package/lib/queries/pg.js +12 -29
- package/lib/queries/shared.js +26 -29
- package/lib/queries/sqlite.js +17 -33
- package/lib/telemetry.js +7 -14
- package/lib/utils.js +12 -23
- package/package.json +18 -15
package/lib/cursor.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MissingOrderByClauseError,
|
|
3
|
+
MissingOrderByFieldForCursorError,
|
|
4
|
+
MissingUniqueFieldInCursorError,
|
|
5
|
+
UnknownFieldError
|
|
6
|
+
} from './errors.js'
|
|
7
|
+
|
|
8
|
+
function sanitizeCursor (cursor, orderBy, inputToFieldMap, fields, primaryKeys) {
|
|
9
|
+
if (!orderBy || orderBy.length === 0) throw new MissingOrderByClauseError()
|
|
10
|
+
let hasUniqueField = false
|
|
11
|
+
const validCursorFields = new Map()
|
|
12
|
+
|
|
13
|
+
for (const [key, value] of Object.entries(cursor)) {
|
|
14
|
+
const dbField = inputToFieldMap[key]
|
|
15
|
+
if (!dbField) throw new UnknownFieldError(key)
|
|
16
|
+
const order = orderBy.find(order => order.field === key)
|
|
17
|
+
if (!order) throw new MissingOrderByFieldForCursorError(key)
|
|
18
|
+
if (primaryKeys.has(dbField)) hasUniqueField = true
|
|
19
|
+
validCursorFields.set(key, {
|
|
20
|
+
dbField,
|
|
21
|
+
value,
|
|
22
|
+
direction: order.direction.toLowerCase(),
|
|
23
|
+
fieldWrap: fields[dbField]
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
if (!hasUniqueField) throw new MissingUniqueFieldInCursorError()
|
|
27
|
+
|
|
28
|
+
// Process fields in orderBy order
|
|
29
|
+
const cursorFields = []
|
|
30
|
+
for (const order of orderBy) {
|
|
31
|
+
if (validCursorFields.has(order.field)) {
|
|
32
|
+
cursorFields.push(validCursorFields.get(order.field))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return cursorFields
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildTupleQuery (cursorFields, sql, computeCriteriaValue, isBackwardPagination) {
|
|
39
|
+
const direction = cursorFields[0].direction
|
|
40
|
+
let operator
|
|
41
|
+
if (isBackwardPagination) {
|
|
42
|
+
operator = direction === 'desc' ? '>' : '<'
|
|
43
|
+
} else {
|
|
44
|
+
operator = direction === 'desc' ? '<' : '>'
|
|
45
|
+
}
|
|
46
|
+
const fields = sql.join(
|
|
47
|
+
cursorFields.map(({ dbField }) => sql.ident(dbField)),
|
|
48
|
+
sql`, `
|
|
49
|
+
)
|
|
50
|
+
const values = sql.join(
|
|
51
|
+
cursorFields.map(({ fieldWrap, value }) => computeCriteriaValue(fieldWrap, value)),
|
|
52
|
+
sql`, `
|
|
53
|
+
)
|
|
54
|
+
return sql`(${fields}) ${sql.__dangerous__rawValue(operator)} (${values})`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildQuery (cursorFields, sql, computeCriteriaValue, isBackwardPagination) {
|
|
58
|
+
const conditions = []
|
|
59
|
+
const equalityParts = []
|
|
60
|
+
for (const { dbField, fieldWrap, value, direction } of cursorFields) {
|
|
61
|
+
let operator
|
|
62
|
+
if (isBackwardPagination) {
|
|
63
|
+
operator = direction === 'desc' ? '>' : '<'
|
|
64
|
+
} else {
|
|
65
|
+
operator = direction === 'desc' ? '<' : '>'
|
|
66
|
+
}
|
|
67
|
+
const inequalityPart = sql`${sql.ident(dbField)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value)}`
|
|
68
|
+
if (equalityParts.length === 0) {
|
|
69
|
+
conditions.push(inequalityPart)
|
|
70
|
+
} else {
|
|
71
|
+
conditions.push(sql`${sql.join(equalityParts, sql` AND `)} AND ${inequalityPart}`)
|
|
72
|
+
}
|
|
73
|
+
equalityParts.push(sql`${sql.ident(dbField)} = ${computeCriteriaValue(fieldWrap, value)}`)
|
|
74
|
+
}
|
|
75
|
+
return sql`(${sql.join(conditions, sql` OR `)})`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function buildCursorCondition (
|
|
79
|
+
sql,
|
|
80
|
+
cursor,
|
|
81
|
+
orderBy,
|
|
82
|
+
inputToFieldMap,
|
|
83
|
+
fields,
|
|
84
|
+
computeCriteriaValue,
|
|
85
|
+
primaryKeys,
|
|
86
|
+
isBackwardPagination
|
|
87
|
+
) {
|
|
88
|
+
if (!cursor || Object.keys(cursor).length === 0) return null
|
|
89
|
+
const cursorFields = sanitizeCursor(cursor, orderBy, inputToFieldMap, fields, primaryKeys)
|
|
90
|
+
const sameSortDirection = cursorFields.every(({ direction }) => direction === cursorFields[0].direction)
|
|
91
|
+
return sameSortDirection
|
|
92
|
+
? buildTupleQuery(cursorFields, sql, computeCriteriaValue, isBackwardPagination)
|
|
93
|
+
: buildQuery(cursorFields, sql, computeCriteriaValue, isBackwardPagination)
|
|
94
|
+
}
|
package/lib/entity.js
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { findNearestString } from '@platformatic/foundation'
|
|
2
|
+
import camelcase from 'camelcase'
|
|
3
|
+
import { singularize } from 'inflected'
|
|
4
|
+
import { buildCursorCondition } from './cursor.js'
|
|
5
|
+
import {
|
|
6
|
+
InputNotProvidedError,
|
|
7
|
+
InvalidPrimaryKeyTypeError,
|
|
8
|
+
MissingWhereClauseError,
|
|
9
|
+
ParamNotAllowedError,
|
|
10
|
+
UnknownFieldError,
|
|
11
|
+
UnsupportedOperatorForArrayFieldError,
|
|
12
|
+
UnsupportedOperatorForNonArrayFieldError,
|
|
13
|
+
UnsupportedWhereClauseError
|
|
14
|
+
} from './errors.js'
|
|
15
|
+
import { wrapDB } from './telemetry.js'
|
|
16
|
+
import { sanitizeLimit, tableName, toLowerFirst, toSingular, toUpperFirst } from './utils.js'
|
|
17
|
+
|
|
18
|
+
function createMapper (
|
|
19
|
+
defaultDb,
|
|
20
|
+
sql,
|
|
21
|
+
log,
|
|
22
|
+
table,
|
|
23
|
+
fields,
|
|
24
|
+
primaryKeys,
|
|
25
|
+
relations,
|
|
26
|
+
queries,
|
|
27
|
+
autoTimestamp,
|
|
28
|
+
schema,
|
|
29
|
+
useSchemaInName,
|
|
30
|
+
limitConfig,
|
|
31
|
+
columns,
|
|
32
|
+
constraintsList
|
|
33
|
+
) {
|
|
17
34
|
/* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
|
|
18
35
|
const entityName = useSchemaInName ? toUpperFirst(`${schema}${toSingular(table)}`) : toSingular(table)
|
|
19
36
|
/* istanbul ignore next */
|
|
@@ -22,7 +39,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
22
39
|
|
|
23
40
|
// If the db is in the opts, uses it, otherwise uses the defaultDb
|
|
24
41
|
// if telemetry is enabled, wraps the db with telemetry
|
|
25
|
-
const getDB =
|
|
42
|
+
const getDB = opts => {
|
|
26
43
|
let db = opts?.tx || defaultDb
|
|
27
44
|
if (opts?.ctx?.app?.openTelemetry && opts?.ctx?.reply?.request) {
|
|
28
45
|
const req = opts.ctx.reply.request
|
|
@@ -43,10 +60,10 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
43
60
|
return acc
|
|
44
61
|
}, {})
|
|
45
62
|
|
|
46
|
-
const primaryKeysTypes = Array.from(primaryKeys).map(
|
|
63
|
+
const primaryKeysTypes = Array.from(primaryKeys).map(key => {
|
|
47
64
|
return {
|
|
48
65
|
key,
|
|
49
|
-
sqlType: fields[key].sqlType
|
|
66
|
+
sqlType: fields[key].sqlType
|
|
50
67
|
}
|
|
51
68
|
})
|
|
52
69
|
|
|
@@ -59,7 +76,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
59
76
|
if (fields[key] !== undefined) {
|
|
60
77
|
newKey = key
|
|
61
78
|
} else {
|
|
62
|
-
throw new
|
|
79
|
+
throw new UnknownFieldError(key)
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
newInput[newKey] = value
|
|
@@ -86,10 +103,10 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
86
103
|
async function save (args) {
|
|
87
104
|
const db = getDB(args)
|
|
88
105
|
if (args.input === undefined) {
|
|
89
|
-
throw new
|
|
106
|
+
throw new InputNotProvidedError()
|
|
90
107
|
}
|
|
91
108
|
// args.input is not array
|
|
92
|
-
const fieldsToRetrieve = computeFields(args.fields).map(
|
|
109
|
+
const fieldsToRetrieve = computeFields(args.fields).map(f => sql.ident(f))
|
|
93
110
|
const input = fixInput(args.input)
|
|
94
111
|
|
|
95
112
|
let hasPrimaryKeys = true
|
|
@@ -105,7 +122,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
105
122
|
now = new Date()
|
|
106
123
|
input[autoTimestamp.updatedAt] = now
|
|
107
124
|
}
|
|
108
|
-
if (hasPrimaryKeys) {
|
|
125
|
+
if (hasPrimaryKeys) {
|
|
126
|
+
// update
|
|
109
127
|
const res = await queries.updateOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
|
|
110
128
|
if (res) {
|
|
111
129
|
return fixOutput(res)
|
|
@@ -127,7 +145,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
127
145
|
|
|
128
146
|
async function insert (args) {
|
|
129
147
|
const db = getDB(args)
|
|
130
|
-
const fieldsToRetrieve = computeFields(args.fields).map(
|
|
148
|
+
const fieldsToRetrieve = computeFields(args.fields).map(f => sql.ident(f))
|
|
131
149
|
const inputs = args.inputs
|
|
132
150
|
// This else is skipped on MySQL because of https://github.com/ForbesLindesay/atdatabases/issues/221
|
|
133
151
|
/* istanbul ignore else */
|
|
@@ -145,7 +163,17 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
145
163
|
/* istanbul ignore next */
|
|
146
164
|
if (queries.insertMany) {
|
|
147
165
|
// We are not fixing the input here because it is done in the query.
|
|
148
|
-
const res = await queries.insertMany(
|
|
166
|
+
const res = await queries.insertMany(
|
|
167
|
+
db,
|
|
168
|
+
sql,
|
|
169
|
+
table,
|
|
170
|
+
schema,
|
|
171
|
+
inputs,
|
|
172
|
+
inputToFieldMap,
|
|
173
|
+
primaryKeysTypes,
|
|
174
|
+
fieldsToRetrieve,
|
|
175
|
+
fields
|
|
176
|
+
)
|
|
149
177
|
return res.map(fixOutput)
|
|
150
178
|
} else {
|
|
151
179
|
// TODO this can be optimized, we can still use a batch insert if we do not want any fields
|
|
@@ -162,13 +190,13 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
162
190
|
|
|
163
191
|
async function updateMany (args) {
|
|
164
192
|
if (args.input === undefined) {
|
|
165
|
-
throw new
|
|
193
|
+
throw new InputNotProvidedError()
|
|
166
194
|
}
|
|
167
195
|
if (args.where === undefined || Object.keys(args.where).length === 0) {
|
|
168
|
-
throw new
|
|
196
|
+
throw new MissingWhereClauseError()
|
|
169
197
|
}
|
|
170
198
|
const db = getDB(args)
|
|
171
|
-
const fieldsToRetrieve = computeFields(args.fields).map(
|
|
199
|
+
const fieldsToRetrieve = computeFields(args.fields).map(f => sql.ident(f))
|
|
172
200
|
const input = fixInput(args.input)
|
|
173
201
|
if (autoTimestamp && fields[autoTimestamp.updatedAt]) {
|
|
174
202
|
const now = new Date()
|
|
@@ -189,9 +217,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
189
217
|
* The 'field' can be a relational field which is undefined
|
|
190
218
|
* in the inputToFieldMap
|
|
191
219
|
* @see sql-graphql
|
|
192
|
-
|
|
193
|
-
const requestedFields = fields.map(
|
|
194
|
-
if (relations.some(
|
|
220
|
+
*/
|
|
221
|
+
const requestedFields = fields.map(field => {
|
|
222
|
+
if (relations.some(relation => field === relation.column_name)) {
|
|
195
223
|
return field
|
|
196
224
|
}
|
|
197
225
|
return inputToFieldMap[field]
|
|
@@ -218,7 +246,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
218
246
|
all: 'ALL',
|
|
219
247
|
contains: '@>',
|
|
220
248
|
contained: '<@',
|
|
221
|
-
overlaps: '&&'
|
|
249
|
+
overlaps: '&&'
|
|
222
250
|
}
|
|
223
251
|
|
|
224
252
|
function computeCriteria (opts) {
|
|
@@ -237,14 +265,14 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
237
265
|
const value = where[key]
|
|
238
266
|
const field = inputToFieldMap[key]
|
|
239
267
|
if (!field) {
|
|
240
|
-
throw new
|
|
268
|
+
throw new UnknownFieldError(key)
|
|
241
269
|
}
|
|
242
270
|
for (const key of Object.keys(value)) {
|
|
243
271
|
const operator = whereMap[key]
|
|
244
272
|
/* istanbul ignore next */
|
|
245
273
|
if (!operator) {
|
|
246
274
|
// This should never happen
|
|
247
|
-
throw new
|
|
275
|
+
throw new UnsupportedWhereClauseError(JSON.stringify(where[key]))
|
|
248
276
|
}
|
|
249
277
|
const fieldWrap = fields[field]
|
|
250
278
|
/* istanbul ignore next */
|
|
@@ -260,7 +288,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
260
288
|
} else if (operator === '&&') {
|
|
261
289
|
criteria.push(sql`${sql.ident(field)} && ${value[key]}`)
|
|
262
290
|
} else {
|
|
263
|
-
throw new
|
|
291
|
+
throw new UnsupportedOperatorForArrayFieldError()
|
|
264
292
|
}
|
|
265
293
|
} else if (operator === '=' && value[key] === null) {
|
|
266
294
|
criteria.push(sql`${sql.ident(field)} IS NULL`)
|
|
@@ -275,10 +303,18 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
275
303
|
}
|
|
276
304
|
const like = operator === 'LIKE' ? sql`LIKE` : queries.hasILIKE ? sql`ILIKE` : sql`LIKE`
|
|
277
305
|
criteria.push(sql`${leftHand} ${like} ${value[key]}`)
|
|
278
|
-
} else if (
|
|
279
|
-
|
|
306
|
+
} else if (
|
|
307
|
+
operator === 'ANY' ||
|
|
308
|
+
operator === 'ALL' ||
|
|
309
|
+
operator === '@>' ||
|
|
310
|
+
operator === '<@' ||
|
|
311
|
+
operator === '&&'
|
|
312
|
+
) {
|
|
313
|
+
throw new UnsupportedOperatorForNonArrayFieldError()
|
|
280
314
|
} else {
|
|
281
|
-
criteria.push(
|
|
315
|
+
criteria.push(
|
|
316
|
+
sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`
|
|
317
|
+
)
|
|
282
318
|
}
|
|
283
319
|
}
|
|
284
320
|
}
|
|
@@ -288,13 +324,18 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
288
324
|
function computeCriteriaValue (fieldWrap, value) {
|
|
289
325
|
if (Array.isArray(value)) {
|
|
290
326
|
return sql`(${sql.join(
|
|
291
|
-
value.map(
|
|
327
|
+
value.map(v => computeCriteriaValue(fieldWrap, v)),
|
|
292
328
|
sql`, `
|
|
293
329
|
)})`
|
|
294
330
|
}
|
|
295
331
|
|
|
296
332
|
/* istanbul ignore next */
|
|
297
|
-
if (
|
|
333
|
+
if (
|
|
334
|
+
fieldWrap.sqlType === 'int4' ||
|
|
335
|
+
fieldWrap.sqlType === 'int2' ||
|
|
336
|
+
fieldWrap.sqlType === 'float8' ||
|
|
337
|
+
fieldWrap.sqlType === 'float4'
|
|
338
|
+
) {
|
|
298
339
|
// This cat is needed in PostgreSQL
|
|
299
340
|
return sql`${Number(value)}`
|
|
300
341
|
} else {
|
|
@@ -304,37 +345,62 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
304
345
|
|
|
305
346
|
async function find (opts = {}) {
|
|
306
347
|
const db = getDB(opts)
|
|
307
|
-
const fieldsToRetrieve = computeFields(opts.fields).map(
|
|
348
|
+
const fieldsToRetrieve = computeFields(opts.fields).map(f => sql.ident(f))
|
|
308
349
|
const criteria = computeCriteria(opts)
|
|
350
|
+
const criteriaExists = criteria.length > 0
|
|
351
|
+
const isBackwardPagination = opts.nextPage === false
|
|
309
352
|
|
|
310
353
|
let query = sql`
|
|
311
354
|
SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
|
|
312
355
|
FROM ${tableName(sql, table, schema)}
|
|
313
356
|
`
|
|
314
357
|
|
|
315
|
-
if (
|
|
358
|
+
if (criteriaExists) {
|
|
316
359
|
query = sql`${query} WHERE ${sql.join(criteria, sql` AND `)}`
|
|
317
360
|
}
|
|
318
361
|
|
|
362
|
+
if (opts.cursor) {
|
|
363
|
+
const cursorCondition = buildCursorCondition(
|
|
364
|
+
sql,
|
|
365
|
+
opts.cursor,
|
|
366
|
+
opts.orderBy,
|
|
367
|
+
inputToFieldMap,
|
|
368
|
+
fields,
|
|
369
|
+
computeCriteriaValue,
|
|
370
|
+
primaryKeys,
|
|
371
|
+
isBackwardPagination
|
|
372
|
+
)
|
|
373
|
+
if (cursorCondition) {
|
|
374
|
+
if (criteriaExists) query = sql`${query} AND ${cursorCondition}`
|
|
375
|
+
else query = sql`${query} WHERE ${cursorCondition}`
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
319
379
|
if (opts.orderBy && opts.orderBy.length > 0) {
|
|
320
|
-
const orderBy = opts.orderBy.map(
|
|
380
|
+
const orderBy = opts.orderBy.map(order => {
|
|
321
381
|
const field = inputToFieldMap[order.field]
|
|
322
|
-
|
|
382
|
+
let direction = order.direction.toLowerCase()
|
|
383
|
+
if (isBackwardPagination) {
|
|
384
|
+
direction = direction === 'asc' ? 'desc' : 'asc'
|
|
385
|
+
}
|
|
386
|
+
return sql`${sql.ident(field)} ${sql.__dangerous__rawValue(direction)}`
|
|
323
387
|
})
|
|
324
388
|
query = sql`${query} ORDER BY ${sql.join(orderBy, sql`, `)}`
|
|
325
389
|
}
|
|
326
390
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (opts.offset
|
|
330
|
-
|
|
391
|
+
if (opts.paginate !== false) {
|
|
392
|
+
query = sql`${query} LIMIT ${sanitizeLimit(opts.limit, limitConfig)}`
|
|
393
|
+
if (opts.offset !== undefined) {
|
|
394
|
+
if (opts.offset < 0) {
|
|
395
|
+
throw new ParamNotAllowedError(opts.offset)
|
|
396
|
+
}
|
|
397
|
+
query = sql`${query} OFFSET ${opts.offset}`
|
|
331
398
|
}
|
|
332
|
-
query = sql`${query} OFFSET ${opts.offset}`
|
|
333
399
|
}
|
|
334
400
|
|
|
335
401
|
const rows = await db.query(query)
|
|
336
402
|
const res = rows.map(fixOutput)
|
|
337
|
-
return res
|
|
403
|
+
return isBackwardPagination ? res.reverse() : res
|
|
338
404
|
}
|
|
339
405
|
|
|
340
406
|
async function count (opts = {}) {
|
|
@@ -354,7 +420,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
354
420
|
|
|
355
421
|
async function _delete (opts) {
|
|
356
422
|
const db = getDB(opts)
|
|
357
|
-
const fieldsToRetrieve = computeFields(opts.fields).map(
|
|
423
|
+
const fieldsToRetrieve = computeFields(opts.fields).map(f => sql.ident(f))
|
|
358
424
|
const criteria = computeCriteria(opts)
|
|
359
425
|
const res = await queries.deleteAll(db, sql, table, schema, criteria, fieldsToRetrieve)
|
|
360
426
|
return res.map(fixOutput)
|
|
@@ -376,11 +442,25 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
376
442
|
insert,
|
|
377
443
|
save,
|
|
378
444
|
delete: _delete,
|
|
379
|
-
updateMany
|
|
445
|
+
updateMany
|
|
380
446
|
}
|
|
381
447
|
}
|
|
382
448
|
|
|
383
|
-
function buildEntity (
|
|
449
|
+
export function buildEntity (
|
|
450
|
+
db,
|
|
451
|
+
sql,
|
|
452
|
+
log,
|
|
453
|
+
table,
|
|
454
|
+
queries,
|
|
455
|
+
autoTimestamp,
|
|
456
|
+
schema,
|
|
457
|
+
useSchemaInName,
|
|
458
|
+
ignore,
|
|
459
|
+
limitConfig,
|
|
460
|
+
schemaList,
|
|
461
|
+
columns,
|
|
462
|
+
constraintsList
|
|
463
|
+
) {
|
|
384
464
|
const columnsNames = columns.map(c => c.column_name)
|
|
385
465
|
for (const ignoredColumn of Object.keys(ignore)) {
|
|
386
466
|
if (!columnsNames.includes(ignoredColumn)) {
|
|
@@ -390,21 +470,26 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
390
470
|
}
|
|
391
471
|
|
|
392
472
|
// Compute the columns
|
|
393
|
-
columns = columns.filter(
|
|
473
|
+
columns = columns.filter(c => !ignore[c.column_name])
|
|
394
474
|
const fields = columns.reduce((acc, column) => {
|
|
395
475
|
acc[column.column_name] = {
|
|
396
476
|
sqlType: column.udt_name,
|
|
397
477
|
isNullable: column.is_nullable === 'YES',
|
|
398
|
-
isArray: column.isArray
|
|
478
|
+
isArray: column.isArray
|
|
399
479
|
}
|
|
400
480
|
|
|
401
481
|
// To get enum values in mysql and mariadb
|
|
402
482
|
/* istanbul ignore next */
|
|
403
483
|
if (column.udt_name === 'enum') {
|
|
404
|
-
acc[column.column_name].enum = column.column_type
|
|
484
|
+
acc[column.column_name].enum = column.column_type
|
|
485
|
+
.match(/'(.+?)'/g)
|
|
486
|
+
.map(enumValue => enumValue.slice(1, enumValue.length - 1))
|
|
405
487
|
}
|
|
406
488
|
|
|
407
|
-
if (
|
|
489
|
+
if (
|
|
490
|
+
autoTimestamp &&
|
|
491
|
+
(column.column_name === autoTimestamp.createdAt || column.column_name === autoTimestamp.updatedAt)
|
|
492
|
+
) {
|
|
408
493
|
acc[column.column_name].autoTimestamp = true
|
|
409
494
|
}
|
|
410
495
|
|
|
@@ -437,7 +522,7 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
437
522
|
const validTypes = ['varchar', 'integer', 'uuid', 'serial']
|
|
438
523
|
const pkType = fields[constraint.column_name].sqlType.toLowerCase()
|
|
439
524
|
if (!validTypes.includes(pkType)) {
|
|
440
|
-
throw new
|
|
525
|
+
throw new InvalidPrimaryKeyTypeError(pkType, validTypes.join(', '))
|
|
441
526
|
}
|
|
442
527
|
}
|
|
443
528
|
}
|
|
@@ -448,9 +533,12 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
448
533
|
/* istanbul ignore next */
|
|
449
534
|
if (!field) {
|
|
450
535
|
// This should never happen
|
|
451
|
-
log.warn(
|
|
452
|
-
|
|
453
|
-
|
|
536
|
+
log.warn(
|
|
537
|
+
{
|
|
538
|
+
constraint
|
|
539
|
+
},
|
|
540
|
+
`No field for ${constraint.column_name}`
|
|
541
|
+
)
|
|
454
542
|
continue
|
|
455
543
|
}
|
|
456
544
|
|
|
@@ -463,13 +551,28 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
463
551
|
|
|
464
552
|
// we need to ignore for coverage here because cannot be covered with sqlite (no schema support)
|
|
465
553
|
// istanbul ignore next
|
|
466
|
-
const isForeignKeySchemaInConfig =
|
|
554
|
+
const isForeignKeySchemaInConfig =
|
|
555
|
+
schemaList?.length > 0 ? schemaList.includes(constraint.foreign_table_schema) : true
|
|
467
556
|
/* istanbul ignore if */
|
|
468
557
|
if (constraint.constraint_type === 'FOREIGN KEY' && isForeignKeySchemaInConfig) {
|
|
469
558
|
field.foreignKey = true
|
|
470
|
-
const foreignEntityName = singularize(
|
|
471
|
-
|
|
472
|
-
|
|
559
|
+
const foreignEntityName = singularize(
|
|
560
|
+
camelcase(
|
|
561
|
+
useSchemaInName
|
|
562
|
+
? camelcase(`${constraint.foreign_table_schema} ${constraint.foreign_table_name}`)
|
|
563
|
+
: constraint.foreign_table_name
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
const entityName = singularize(
|
|
567
|
+
camelcase(
|
|
568
|
+
useSchemaInName ? camelcase(`${constraint.table_schema} ${constraint.table_name}`) : constraint.table_name
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
const loweredTableWithSchemaName = toLowerFirst(
|
|
572
|
+
useSchemaInName
|
|
573
|
+
? camelcase(`${constraint.table_schema} ${camelcase(constraint.table_name)}`)
|
|
574
|
+
: camelcase(constraint.table_name)
|
|
575
|
+
)
|
|
473
576
|
constraint.loweredTableWithSchemaName = loweredTableWithSchemaName
|
|
474
577
|
constraint.foreignEntityName = foreignEntityName
|
|
475
578
|
constraint.entityName = entityName
|
|
@@ -504,10 +607,21 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
504
607
|
}
|
|
505
608
|
}
|
|
506
609
|
|
|
507
|
-
const entity = createMapper(
|
|
610
|
+
const entity = createMapper(
|
|
611
|
+
db,
|
|
612
|
+
sql,
|
|
613
|
+
log,
|
|
614
|
+
table,
|
|
615
|
+
fields,
|
|
616
|
+
primaryKeys,
|
|
617
|
+
currentRelations,
|
|
618
|
+
queries,
|
|
619
|
+
autoTimestamp,
|
|
620
|
+
schema,
|
|
621
|
+
useSchemaInName,
|
|
622
|
+
limitConfig
|
|
623
|
+
)
|
|
508
624
|
entity.relations = currentRelations
|
|
509
625
|
|
|
510
626
|
return entity
|
|
511
627
|
}
|
|
512
|
-
|
|
513
|
-
module.exports = buildEntity
|
package/lib/errors.js
CHANGED
|
@@ -1,24 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
import createError from '@fastify/error'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const ERROR_PREFIX = 'PLT_SQL_MAPPER'
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
export const CannotFindEntityError = createError(`${ERROR_PREFIX}_CANNOT_FIND_ENTITY`, 'Cannot find entity %s')
|
|
6
|
+
export const SpecifyProtocolError = createError(
|
|
7
|
+
`${ERROR_PREFIX}_SPECIFY_PROTOCOLS`,
|
|
8
|
+
'You must specify either postgres, mysql or sqlite as protocols'
|
|
9
|
+
)
|
|
10
|
+
export const ConnectionStringRequiredError = createError(
|
|
11
|
+
`${ERROR_PREFIX}_CONNECTION_STRING_REQUIRED`,
|
|
12
|
+
'connectionString is required'
|
|
13
|
+
)
|
|
14
|
+
export const TableMustBeAStringError = createError(
|
|
15
|
+
`${ERROR_PREFIX}_TABLE_MUST_BE_A_STRING`,
|
|
16
|
+
'Table must be a string, got %s'
|
|
17
|
+
)
|
|
18
|
+
export const UnknownFieldError = createError(`${ERROR_PREFIX}_UNKNOWN_FIELD`, 'Unknown field %s')
|
|
19
|
+
export const InputNotProvidedError = createError(`${ERROR_PREFIX}_INPUT_NOT_PROVIDED`, 'Input not provided.')
|
|
20
|
+
export const UnsupportedWhereClauseError = createError(
|
|
21
|
+
`${ERROR_PREFIX}_UNSUPPORTED_WHERE_CLAUSE`,
|
|
22
|
+
'Unsupported where clause %s'
|
|
23
|
+
)
|
|
24
|
+
export const UnsupportedOperatorForArrayFieldError = createError(
|
|
25
|
+
`${ERROR_PREFIX}_UNSUPPORTED_OPERATOR`,
|
|
26
|
+
'Unsupported operator for Array field'
|
|
27
|
+
)
|
|
28
|
+
export const UnsupportedOperatorForNonArrayFieldError = createError(
|
|
29
|
+
`${ERROR_PREFIX}_UNSUPPORTED_OPERATOR_FOR_NON_ARRAY`,
|
|
30
|
+
'Unsupported operator for non Array field'
|
|
31
|
+
)
|
|
32
|
+
export const ParamNotAllowedError = createError(
|
|
33
|
+
`${ERROR_PREFIX}_PARAM_NOT_ALLOWED`,
|
|
34
|
+
'Param offset=%s not allowed. It must be not negative value.'
|
|
35
|
+
)
|
|
36
|
+
export const InvalidPrimaryKeyTypeError = createError(
|
|
37
|
+
`${ERROR_PREFIX}_INVALID_PRIMARY_KEY_TYPE`,
|
|
38
|
+
'Invalid Primary Key type: "%s". We support the following: %s'
|
|
39
|
+
)
|
|
40
|
+
export const ParamLimitNotAllowedError = createError(
|
|
41
|
+
`${ERROR_PREFIX}_PARAM_LIMIT_NOT_ALLOWED`,
|
|
42
|
+
'Param limit=%s not allowed. Max accepted value %s.'
|
|
43
|
+
)
|
|
44
|
+
export const ParamLimitMustBeNotNegativeError = createError(
|
|
45
|
+
`${ERROR_PREFIX}_PARAM_LIMIT_MUST_BE_NOT_NEGATIVE`,
|
|
46
|
+
'Param limit=%s not allowed. It must be a not negative value.'
|
|
47
|
+
)
|
|
48
|
+
export const MissingValueForPrimaryKeyError = createError(
|
|
49
|
+
`${ERROR_PREFIX}_MISSING_VALUE_FOR_PRIMARY_KEY`,
|
|
50
|
+
'Missing value for primary key %s'
|
|
51
|
+
)
|
|
52
|
+
export const MissingWhereClauseError = createError(`${ERROR_PREFIX}_MISSING_WHERE_CLAUSE`, 'Missing where clause', 400)
|
|
53
|
+
export const SQLiteOnlySupportsAutoIncrementOnOneColumnError = createError(
|
|
54
|
+
`${ERROR_PREFIX}_SQLITE_ONLY_SUPPORTS_AUTO_INCREMENT_ON_ONE_COLUMN`,
|
|
55
|
+
'SQLite only supports autoIncrement on one column'
|
|
56
|
+
)
|
|
57
|
+
export const MissingOrderByClauseError = createError(
|
|
58
|
+
`${ERROR_PREFIX}_MISSING_ORDER_BY_CLAUSE`,
|
|
59
|
+
'Missing orderBy clause'
|
|
60
|
+
)
|
|
61
|
+
export const MissingOrderByFieldForCursorError = createError(
|
|
62
|
+
`${ERROR_PREFIX}_MISSING_ORDER_BY_FIELD_FOR_CURSOR`,
|
|
63
|
+
'Cursor field(s) %s must be included in orderBy'
|
|
64
|
+
)
|
|
65
|
+
export const MissingUniqueFieldInCursorError = createError(
|
|
66
|
+
`${ERROR_PREFIX}_MISSING_UNIQUE_FIELD_IN_CURSOR`,
|
|
67
|
+
'Cursor must contain at least one primary key field'
|
|
68
|
+
)
|
package/lib/queries/index.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
1
|
+
export * as mariadb from './mariadb.js'
|
|
2
|
+
export * as mysql from './mysql.js'
|
|
3
|
+
export * as pg from './pg.js'
|
|
4
|
+
export * as sqlite from './sqlite.js'
|