@platformatic/sql-mapper 3.66.0 → 3.67.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/index.js CHANGED
@@ -192,7 +192,7 @@ export async function createConnectionPool ({
192
192
  sql = createConnectionPoolMysql.sql
193
193
  const version = (await db.query(sql`SELECT VERSION()`))[0]['VERSION()']
194
194
  db.version = version
195
- db.isMariaDB = version.indexOf('maria') !== -1
195
+ db.isMariaDB = version.toLowerCase().indexOf('maria') !== -1
196
196
  if (!db.isMariaDB) {
197
197
  db.isMySql = true
198
198
  }
@@ -312,6 +312,26 @@ export async function connect ({
312
312
  wrap.constraints = await queries.listConstraints(db, sql, table, schema)
313
313
  wrap.columns = columns
314
314
 
315
+ // MariaDB reports JSON columns as `longtext`; recover the JSON-ness
316
+ // from the auto-generated json_valid(...) CHECK constraint.
317
+ // Fail open: information_schema.CHECK_CONSTRAINTS (and its TABLE_NAME
318
+ // extension) is only present on MariaDB >= 10.2.1. On older MariaDB,
319
+ // or if permissions are restricted, just skip JSON-column detection
320
+ // instead of breaking introspection entirely.
321
+ if (db.isMariaDB && queries.listJsonColumns) {
322
+ try {
323
+ const jsonColumns = await queries.listJsonColumns(db, sql, table, schema)
324
+ for (const jsonColumn of jsonColumns) {
325
+ const column = columns.find(c => c.column_name === jsonColumn.column_name)
326
+ if (column) {
327
+ column.isJson = true
328
+ }
329
+ }
330
+ } catch {
331
+ // information_schema.CHECK_CONSTRAINTS not available/queryable; ignore.
332
+ }
333
+ }
334
+
315
335
  // To get enum values in pg
316
336
  /* istanbul ignore next */
317
337
  if (db.isPg) {
@@ -34,6 +34,7 @@ export async function getConnectionInfo (db, connectionString) {
34
34
  user,
35
35
  isPg: !!db.isPg,
36
36
  isMySql: !!db.isMySql,
37
+ isMariaDB: !!db.isMariaDB,
37
38
  isSQLite: !!db.isSQLite,
38
39
  dbSystem
39
40
  }
package/lib/entity.js CHANGED
@@ -66,7 +66,7 @@ function createMapper (
66
66
  return acc
67
67
  }, {})
68
68
 
69
- const primaryKeysTypes = Array.from(primaryKeys).map(key => {
69
+ const primaryKeysTypes = Array.from(primaryKeys).map((key) => {
70
70
  return {
71
71
  key,
72
72
  sqlType: fields[key].sqlType
@@ -99,25 +99,50 @@ function createMapper (
99
99
  if (!output) {
100
100
  return output
101
101
  }
102
+
102
103
  const newOutput = {}
104
+
103
105
  for (const key of Object.keys(output)) {
104
106
  let value = output[key]
105
107
  const newKey = fieldMapToRetrieve[key]
106
- // Do not convert Date objects: they are serialized according to the
107
- // schema of the field, like other non-stringified columns
108
+
109
+ const isNativeJson =
110
+ fields[key]?.sqlType === 'json' || fields[key]?.sqlType === 'jsonb'
111
+
112
+ // MariaDB has no native JSON type: JSON columns are stored (and
113
+ // introspected) as `longtext`. We mark them at introspection time
114
+ // via the `isJson` flag (see index.js / lib/queries/mysql-shared.js).
115
+ const isJsonText = fields[key]?.isJson === true
116
+
117
+ if (isNativeJson || isJsonText) {
118
+ // The driver may already have deserialized the value (some MariaDB
119
+ // driver versions do this for longtext columns), or it may still be
120
+ // the raw JSON string - normalize to a parsed object either way.
121
+ if (typeof value === 'string') {
122
+ value = JSON.parse(value)
123
+ }
124
+
125
+ newOutput[newKey] = value
126
+ continue
127
+ }
128
+
108
129
  if (
109
130
  (primaryKeys.has(key) || fields[key]?.stringifyOutput) &&
110
131
  value !== null &&
111
132
  value !== undefined &&
112
- !(value instanceof Date)
133
+ !(value instanceof Date) &&
134
+ typeof value !== 'object'
113
135
  ) {
114
136
  value = value.toString()
115
137
  }
138
+
116
139
  if (newKey && isVectorType(fields[key].sqlType)) {
117
140
  value = parseVector(value)
118
141
  }
142
+
119
143
  newOutput[newKey] = value
120
144
  }
145
+
121
146
  return newOutput
122
147
  }
123
148
 
@@ -522,7 +547,8 @@ export function buildEntity (
522
547
  sqlType: column.udt_name,
523
548
  isNullable: column.is_nullable === 'YES',
524
549
  isArray: column.isArray,
525
- vectorDimensions: column.vectorDimensions
550
+ vectorDimensions: column.vectorDimensions,
551
+ isJson: column.isJson === true
526
552
  }
527
553
 
528
554
  // To get enum values in mysql and mariadb
@@ -1,2 +1 @@
1
- export { listColumns, listConstraints, listTables, listViews, updateMany, updateOne } from './mysql-shared.js'
2
- export { deleteAll, insertMany, insertOne } from './shared.js'
1
+ export * from './mysql.js'
@@ -34,6 +34,20 @@ export async function listColumns (db, sql, table, schema) {
34
34
  return db.query(query)
35
35
  }
36
36
 
37
+ // MariaDB has no native JSON type: a `JSON` column is really a LONGTEXT
38
+ // column with an automatically-generated CHECK (json_valid(`col`)) constraint.
39
+ // We use that constraint to recognize which longtext columns are actually JSON.
40
+ export async function listJsonColumns (db, sql, table, schema) {
41
+ const query = sql`
42
+ SELECT CONSTRAINT_NAME as column_name
43
+ FROM information_schema.CHECK_CONSTRAINTS
44
+ WHERE TABLE_NAME = ${table}
45
+ AND CONSTRAINT_SCHEMA = ${schema}
46
+ AND CHECK_CLAUSE LIKE 'json_valid(%'
47
+ `
48
+ return db.query(query)
49
+ }
50
+
37
51
  export async function listConstraints (db, sql, table, schema) {
38
52
  const query = sql`
39
53
  SELECT TABLE_NAME as table_name, TABLE_SCHEMA as table_schema, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_table_schema AS foreign_table_schema, referenced_column_name AS foreign_column_name
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto'
1
2
  import { MissingValueForPrimaryKeyError } from '../errors.js'
2
3
  import { tableName } from '../utils.js'
3
4
  import { insertPrep } from './shared.js'
@@ -5,6 +6,15 @@ import { insertPrep } from './shared.js'
5
6
  export * from './mysql-shared.js'
6
7
 
7
8
  export function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
9
+ // last_insert_id() only reflects AUTO_INCREMENT columns. A single uuid
10
+ // primary key relying on a server-side DEFAULT (e.g. `DEFAULT UUID()` on
11
+ // MariaDB) can't be recovered that way, so generate it here instead and
12
+ // insert it explicitly - this also lets us fetch the row back by its
13
+ // known id, same as the "primary key provided" path below.
14
+ if (primaryKeys.length === 1 && primaryKeys[0].sqlType === 'uuid' && input[primaryKeys[0].key] === undefined) {
15
+ input = { ...input, [primaryKeys[0].key]: randomUUID() }
16
+ }
17
+
8
18
  const keysToSql = Object.keys(input).map(key => sql.ident(key))
9
19
  const keys = sql.join(keysToSql, sql`, `)
10
20
 
package/lib/telemetry.js CHANGED
@@ -9,7 +9,7 @@ export function wrapQuery (app, db, request) {
9
9
  let namePrefix
10
10
  if (connectionInfo.isPg) {
11
11
  namePrefix = 'pg.query:'
12
- } else if (connectionInfo.isMySql) {
12
+ } else if (connectionInfo.isMySql || connectionInfo.isMariaDB) {
13
13
  namePrefix = 'mysql.query:'
14
14
  } else if (connectionInfo.isSQLite) {
15
15
  namePrefix = 'sqlite.query:'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "3.66.0",
3
+ "version": "3.67.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "fastify": "^5.0.0",
23
23
  "neostandard": "^0.12.0",
24
24
  "tstyche": "^6.2.0",
25
- "typescript": "^5.9.3"
25
+ "typescript": "~6.0.3"
26
26
  },
27
27
  "dependencies": {
28
28
  "@databases/mysql": "^7.0.0",
@@ -35,8 +35,8 @@
35
35
  "camelcase": "^6.3.0",
36
36
  "fastify-plugin": "^6.0.0",
37
37
  "inflected": "^2.1.0",
38
- "@platformatic/foundation": "3.66.0",
39
- "@platformatic/telemetry": "3.66.0"
38
+ "@platformatic/foundation": "3.67.0",
39
+ "@platformatic/telemetry": "3.67.0"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=22.19.0"