@platformatic/sql-mapper 0.31.1 → 0.32.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/lib/entity.js +21 -5
- package/lib/queries/pg.js +20 -0
- package/lib/queries/shared.js +6 -4
- package/mapper.d.ts +2 -2
- package/package.json +3 -3
- package/test/entity.test.js +84 -1
- package/test/types/mapper.test-d.ts +12 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @platformatic/sql-mapper
|
|
2
2
|
|
|
3
|
-
Check out the full documentation on [our website](https://
|
|
3
|
+
Check out the full documentation on [our website](https://docs.platformatic.dev/docs/reference/sql-mapper/introduction).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
package/lib/entity.js
CHANGED
|
@@ -199,7 +199,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
199
199
|
gte: '>=',
|
|
200
200
|
lt: '<',
|
|
201
201
|
lte: '<=',
|
|
202
|
-
like: 'LIKE'
|
|
202
|
+
like: 'LIKE',
|
|
203
|
+
any: 'ANY',
|
|
204
|
+
all: 'ALL'
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
function computeCriteria (opts) {
|
|
@@ -225,7 +227,17 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
225
227
|
throw new Error(`Unsupported where clause ${JSON.stringify(where[key])}`)
|
|
226
228
|
}
|
|
227
229
|
const fieldWrap = fields[field]
|
|
228
|
-
|
|
230
|
+
|
|
231
|
+
/* istanbul ignore next */
|
|
232
|
+
if (fieldWrap.isArray) {
|
|
233
|
+
if (operator === 'ANY') {
|
|
234
|
+
criteria.push(sql`${value[key]} = ANY (${sql.ident(field)})`)
|
|
235
|
+
} else if (operator === 'ALL') {
|
|
236
|
+
criteria.push(sql`${value[key]} = ALL (${sql.ident(field)})`)
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error('Unsupported operator for Array field')
|
|
239
|
+
}
|
|
240
|
+
} else if (operator === '=' && value[key] === null) {
|
|
229
241
|
criteria.push(sql`${sql.ident(field)} IS NULL`)
|
|
230
242
|
} else if (operator === '<>' && value[key] === null) {
|
|
231
243
|
criteria.push(sql`${sql.ident(field)} IS NOT NULL`)
|
|
@@ -237,6 +249,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
237
249
|
leftHand = sql`TRIM(CAST(${sql.ident(field)} AS CHAR(64)))`
|
|
238
250
|
}
|
|
239
251
|
criteria.push(sql`${leftHand} LIKE ${value[key]}`)
|
|
252
|
+
} else if (operator === 'ANY' || operator === 'ALL') {
|
|
253
|
+
throw new Error('Unsupported operator for non Array field')
|
|
240
254
|
} else {
|
|
241
255
|
criteria.push(sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`)
|
|
242
256
|
}
|
|
@@ -292,8 +306,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
292
306
|
query = sql`${query} OFFSET ${opts.offset}`
|
|
293
307
|
}
|
|
294
308
|
|
|
295
|
-
const
|
|
296
|
-
|
|
309
|
+
const rows = await db.query(query)
|
|
310
|
+
const res = rows.map(fixOutput)
|
|
311
|
+
return res
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
async function count (opts = {}) {
|
|
@@ -345,7 +360,8 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
345
360
|
const fields = columns.reduce((acc, column) => {
|
|
346
361
|
acc[column.column_name] = {
|
|
347
362
|
sqlType: column.udt_name,
|
|
348
|
-
isNullable: column.is_nullable === 'YES'
|
|
363
|
+
isNullable: column.is_nullable === 'YES',
|
|
364
|
+
isArray: column.isArray
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
// To get enum values in mysql and mariadb
|
package/lib/queries/pg.js
CHANGED
|
@@ -43,12 +43,32 @@ async function listTables (db, sql, schemas) {
|
|
|
43
43
|
module.exports.listTables = listTables
|
|
44
44
|
|
|
45
45
|
async function listColumns (db, sql, table, schema) {
|
|
46
|
+
/*
|
|
46
47
|
return db.query(sql`
|
|
47
48
|
SELECT column_name, udt_name, is_nullable, is_generated
|
|
48
49
|
FROM information_schema.columns
|
|
49
50
|
WHERE table_name = ${table}
|
|
50
51
|
AND table_schema = ${schema}
|
|
51
52
|
`)
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
const res = await db.query(sql`
|
|
56
|
+
SELECT column_name, udt_name, is_nullable, is_generated, data_type
|
|
57
|
+
FROM information_schema.columns
|
|
58
|
+
WHERE table_name = ${table}
|
|
59
|
+
AND table_schema = ${schema}
|
|
60
|
+
`)
|
|
61
|
+
|
|
62
|
+
for (const col of res) {
|
|
63
|
+
if (col.data_type === 'ARRAY') {
|
|
64
|
+
col.udt_name = col.udt_name.replace(/^_/, '')
|
|
65
|
+
col.isArray = true
|
|
66
|
+
} else {
|
|
67
|
+
col.isArray = false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return res
|
|
52
72
|
}
|
|
53
73
|
|
|
54
74
|
module.exports.listColumns = listColumns
|
package/lib/queries/shared.js
CHANGED
|
@@ -22,9 +22,11 @@ async function insertOne (db, sql, table, schema, input, primaryKeysTypes, field
|
|
|
22
22
|
sql`, `
|
|
23
23
|
)
|
|
24
24
|
const values = sql.join(
|
|
25
|
-
Object.keys(input).map((key) =>
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Object.keys(input).map((key) => {
|
|
26
|
+
const val = input[key]
|
|
27
|
+
return sql.value(val)
|
|
28
|
+
}), sql`, `)
|
|
29
|
+
|
|
28
30
|
const insert = sql`
|
|
29
31
|
INSERT INTO ${tableName(sql, table, schema)} (${keys})
|
|
30
32
|
VALUES (${values})
|
|
@@ -79,7 +81,7 @@ function insertPrep (inputs, inputToFieldMap, fields, sql) {
|
|
|
79
81
|
|
|
80
82
|
let value = input[key] ?? input[newKey]
|
|
81
83
|
|
|
82
|
-
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
84
|
+
if (value && !fields[newKey].isArray && typeof value === 'object' && !(value instanceof Date)) {
|
|
83
85
|
// This is a JSON field
|
|
84
86
|
value = JSON.stringify(value)
|
|
85
87
|
}
|
package/mapper.d.ts
CHANGED
|
@@ -305,7 +305,7 @@ export interface SQLMapperPluginInterface {
|
|
|
305
305
|
/**
|
|
306
306
|
* Adds hooks to the entity.
|
|
307
307
|
*/
|
|
308
|
-
addEntityHooks(entityName: string, hooks: EntityHooks): any
|
|
308
|
+
addEntityHooks<EntityFields>(entityName: string, hooks: EntityHooks<EntityFields>): any
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
// Extend the PlatformaticApp interface,
|
|
@@ -327,7 +327,7 @@ declare module '@platformatic/types' {
|
|
|
327
327
|
/**
|
|
328
328
|
* Adds hooks to the entity.
|
|
329
329
|
*/
|
|
330
|
-
addEntityHooks(entityName: string, hooks: EntityHooks): any
|
|
330
|
+
addEntityHooks<EntityFields>(entityName: string, hooks: EntityHooks<EntityFields>): any
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-mapper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "A data mapper utility for SQL databases",
|
|
5
5
|
"main": "mapper.js",
|
|
6
6
|
"types": "mapper.d.ts",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"tsd": "^0.28.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@databases/mysql": "^
|
|
25
|
+
"@databases/mysql": "^6.0.0",
|
|
26
26
|
"@databases/pg": "^5.4.1",
|
|
27
27
|
"@databases/sql": "^3.3.0",
|
|
28
28
|
"@matteo.collina/sqlite-pool": "^0.3.0",
|
|
29
29
|
"camelcase": "^6.3.0",
|
|
30
30
|
"fastify-plugin": "^4.5.0",
|
|
31
31
|
"inflected": "^2.1.0",
|
|
32
|
-
"@platformatic/types": "0.
|
|
32
|
+
"@platformatic/types": "0.32.0"
|
|
33
33
|
},
|
|
34
34
|
"tsd": {
|
|
35
35
|
"directory": "test/types"
|
package/test/entity.test.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
-
|
|
5
4
|
const { clear, connInfo, isSQLite, isMysql, isPg, isMysql8 } = require('./helper')
|
|
6
5
|
const { connect } = require('..')
|
|
7
6
|
const fakeLogger = {
|
|
7
|
+
// trace: (...args) => { console.log(JSON.stringify(args, null, 2)) },
|
|
8
8
|
trace: () => {},
|
|
9
9
|
error: () => {}
|
|
10
10
|
}
|
|
@@ -1019,3 +1019,86 @@ test('nested transactions', async ({ equal, same, teardown, rejects }) => {
|
|
|
1019
1019
|
same(insertResult, { id: '1', theTitle: null })
|
|
1020
1020
|
})
|
|
1021
1021
|
})
|
|
1022
|
+
|
|
1023
|
+
test('array support (PG)', { skip: !(isPg) }, async ({ teardown, same, rejects }) => {
|
|
1024
|
+
async function onDatabaseLoad (db, sql) {
|
|
1025
|
+
await clear(db, sql)
|
|
1026
|
+
teardown(() => db.dispose())
|
|
1027
|
+
|
|
1028
|
+
await db.query(sql`CREATE TABLE generated_test (
|
|
1029
|
+
id SERIAL PRIMARY KEY,
|
|
1030
|
+
checkmark BOOLEAN NOT NULL DEFAULT true,
|
|
1031
|
+
test INTEGER[]
|
|
1032
|
+
);`)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const mapper = await connect({
|
|
1036
|
+
connectionString: connInfo.connectionString,
|
|
1037
|
+
log: fakeLogger,
|
|
1038
|
+
onDatabaseLoad,
|
|
1039
|
+
ignore: {},
|
|
1040
|
+
hooks: {}
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
const generatedTest = mapper.entities.generatedTest
|
|
1044
|
+
|
|
1045
|
+
// save - new record
|
|
1046
|
+
same(await generatedTest.save({
|
|
1047
|
+
input: { test: [1, 2, 3], checkmark: true }
|
|
1048
|
+
}), { id: 1, test: [1, 2, 3], checkmark: true })
|
|
1049
|
+
|
|
1050
|
+
// save - update
|
|
1051
|
+
same(await generatedTest.save({
|
|
1052
|
+
input: { id: 1, test: [4, 5, 6], checkmark: true }
|
|
1053
|
+
}), { id: 1, test: [4, 5, 6], checkmark: true })
|
|
1054
|
+
|
|
1055
|
+
// insert
|
|
1056
|
+
same(await generatedTest.insert({
|
|
1057
|
+
inputs: [{ test: [4], checkmark: true }]
|
|
1058
|
+
}), [{ id: 2, test: [4], checkmark: true }])
|
|
1059
|
+
|
|
1060
|
+
// where any
|
|
1061
|
+
same(await generatedTest.find({
|
|
1062
|
+
where: {
|
|
1063
|
+
test: { any: 4 }
|
|
1064
|
+
}
|
|
1065
|
+
}), [{ id: 1, test: [4, 5, 6], checkmark: true }, { id: 2, test: [4], checkmark: true }])
|
|
1066
|
+
|
|
1067
|
+
// where all
|
|
1068
|
+
same(await generatedTest.find({
|
|
1069
|
+
where: {
|
|
1070
|
+
test: { all: 4 }
|
|
1071
|
+
}
|
|
1072
|
+
}), [{ id: 2, test: [4], checkmark: true }])
|
|
1073
|
+
|
|
1074
|
+
// where eq
|
|
1075
|
+
await rejects(generatedTest.find({
|
|
1076
|
+
where: {
|
|
1077
|
+
test: { eq: 4 }
|
|
1078
|
+
}
|
|
1079
|
+
}))
|
|
1080
|
+
|
|
1081
|
+
// where any to non-array
|
|
1082
|
+
await rejects(generatedTest.find({
|
|
1083
|
+
where: {
|
|
1084
|
+
checkmark: { any: 4 }
|
|
1085
|
+
}
|
|
1086
|
+
}))
|
|
1087
|
+
|
|
1088
|
+
// where any to non-array
|
|
1089
|
+
await rejects(generatedTest.find({
|
|
1090
|
+
where: {
|
|
1091
|
+
checkmark: { all: 4 }
|
|
1092
|
+
}
|
|
1093
|
+
}))
|
|
1094
|
+
|
|
1095
|
+
// updateMany
|
|
1096
|
+
same(await generatedTest.updateMany({
|
|
1097
|
+
where: {
|
|
1098
|
+
checkmark: { eq: true }
|
|
1099
|
+
},
|
|
1100
|
+
input: {
|
|
1101
|
+
test: [8]
|
|
1102
|
+
}
|
|
1103
|
+
}), [{ id: 1, test: [8], checkmark: true }, { id: 2, test: [8], checkmark: true }])
|
|
1104
|
+
})
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Entity,
|
|
9
9
|
DBEntityField,
|
|
10
10
|
Database,
|
|
11
|
-
WhereCondition,
|
|
12
11
|
SQLMapperPluginInterface,
|
|
13
12
|
EntityHooks,
|
|
14
13
|
} from '../../mapper'
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
const pluginOptions: SQLMapperPluginInterface = await connect({ connectionString: '' })
|
|
17
16
|
expectType<Database>(pluginOptions.db)
|
|
18
17
|
expectType<SQL>(pluginOptions.sql)
|
|
19
|
-
expectType<(entityName: string, hooks: EntityHooks) => any>(pluginOptions.addEntityHooks)
|
|
20
18
|
expectType<{ [entityName: string]: Entity }>(pluginOptions.entities)
|
|
21
19
|
|
|
22
20
|
interface EntityFields {
|
|
@@ -74,6 +72,18 @@ const instance: FastifyInstance = fastify()
|
|
|
74
72
|
instance.register(plugin, { connectionString: '', autoTimestamp: true })
|
|
75
73
|
instance.register((instance) => {
|
|
76
74
|
expectType<SQLMapperPluginInterface>(instance.platformatic)
|
|
75
|
+
|
|
76
|
+
instance.platformatic.addEntityHooks<EntityFields>('something', {
|
|
77
|
+
async find (originalFind, options) {
|
|
78
|
+
expectType<Partial<EntityFields>[]>(await originalFind())
|
|
79
|
+
expectType<Parameters<typeof entity.find>[0]>(options)
|
|
80
|
+
|
|
81
|
+
return [{
|
|
82
|
+
id: 42
|
|
83
|
+
}]
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
77
87
|
instance.get('/', async (request, reply) => {
|
|
78
88
|
const ctx = request.platformaticContext
|
|
79
89
|
expectType<FastifyInstance>(ctx.app)
|