@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @platformatic/sql-mapper
2
2
 
3
- Check out the full documentation on [our website](https://oss.platformatic.dev/docs/reference/sql-mapper/introduction).
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
- if (operator === '=' && value[key] === null) {
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 res = await db.query(query)
296
- return res.map(fixOutput)
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
@@ -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) => sql.value(input[key])),
26
- sql`, `
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.31.1",
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": "^5.2.1",
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.31.1"
32
+ "@platformatic/types": "0.32.0"
33
33
  },
34
34
  "tsd": {
35
35
  "directory": "test/types"
@@ -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)