@platformatic/sql-mapper 3.63.0-alpha.3 → 3.64.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.d.ts CHANGED
@@ -59,6 +59,11 @@ export interface DBEntityField {
59
59
  * An option that is true if field is a foreignKey key.
60
60
  */
61
61
  foreignKey?: boolean,
62
+ /**
63
+ * An option that is true if a foreign key field is stringified in mapper output
64
+ * because it references a primary key.
65
+ */
66
+ stringifyOutput?: boolean,
62
67
  /**
63
68
  * An option that is true if field is nullable.
64
69
  */
package/index.js CHANGED
@@ -80,6 +80,29 @@ const defaultAutoTimestampFields = {
80
80
  updatedAt: 'updated_at'
81
81
  }
82
82
 
83
+ function physicalEntityKey (schema, table) {
84
+ return `${schema ?? ''}\0${table}`
85
+ }
86
+
87
+ function markForeignKeysReferencingPrimaryKeys (entities) {
88
+ const entitiesByTable = new Map()
89
+
90
+ for (const entity of Object.values(entities)) {
91
+ entitiesByTable.set(physicalEntityKey(entity.schema, entity.table), entity)
92
+ }
93
+
94
+ for (const entity of Object.values(entities)) {
95
+ for (const relation of entity.relations) {
96
+ const foreignSchema = relation.foreign_table_schema ?? entity.schema
97
+ const foreignEntity = entitiesByTable.get(physicalEntityKey(foreignSchema, relation.foreign_table_name))
98
+
99
+ if (foreignEntity?.primaryKeys.has(relation.foreign_column_name)) {
100
+ entity.fields[relation.column_name].stringifyOutput = true
101
+ }
102
+ }
103
+ }
104
+ }
105
+
83
106
  async function registerPostgreSQLExtensionTypes (db) {
84
107
  try {
85
108
  await db.registerTypeParser('vector', parseVector)
@@ -375,6 +398,8 @@ export async function connect ({
375
398
  }
376
399
  }
377
400
 
401
+ markForeignKeysReferencingPrimaryKeys(entities)
402
+
378
403
  const res = {
379
404
  db,
380
405
  sql,
package/lib/entity.js CHANGED
@@ -104,8 +104,13 @@ function createMapper (
104
104
  let value = output[key]
105
105
  const newKey = fieldMapToRetrieve[key]
106
106
  // Do not convert Date objects: they are serialized according to the
107
- // schema of the field, like non primary key columns
108
- if (primaryKeys.has(key) && value !== null && value !== undefined && !(value instanceof Date)) {
107
+ // schema of the field, like other non-stringified columns
108
+ if (
109
+ (primaryKeys.has(key) || fields[key]?.stringifyOutput) &&
110
+ value !== null &&
111
+ value !== undefined &&
112
+ !(value instanceof Date)
113
+ ) {
109
114
  value = value.toString()
110
115
  }
111
116
  if (newKey && isVectorType(fields[key].sqlType)) {
@@ -599,6 +604,16 @@ export function buildEntity (
599
604
  schemaList?.length > 0 ? schemaList.includes(constraint.foreign_table_schema) : true
600
605
  /* istanbul ignore if */
601
606
  if (constraint.constraint_type === 'FOREIGN KEY' && isForeignKeySchemaInConfig) {
607
+ /* istanbul ignore next */
608
+ if (!constraint.foreign_table_name) {
609
+ // The referenced side of the foreign key could not be resolved by the introspection
610
+ // query. Ignore the relation rather than failing the whole boot.
611
+ log.warn(
612
+ { constraint },
613
+ `Could not resolve the table referenced by the foreign key "${constraint.constraint_name}" on "${constraint.table_name}.${constraint.column_name}". The relation will be ignored.`
614
+ )
615
+ continue
616
+ }
602
617
  field.foreignKey = true
603
618
  const foreignEntityName = singularize(
604
619
  camelcase(
package/lib/queries/pg.js CHANGED
@@ -80,16 +80,34 @@ export async function listColumns (db, sql, table, schema) {
80
80
 
81
81
  export async function listConstraints (db, sql, table, schema) {
82
82
  const query = sql`
83
- SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name, usage2.table_schema AS foreign_table_schema
83
+ SELECT constraints.*, usage.*, foreign_table.relname AS foreign_table_name,
84
+ foreign_column.attname AS foreign_column_name, foreign_namespace.nspname AS foreign_table_schema
84
85
  FROM information_schema.table_constraints constraints
85
86
  JOIN information_schema.key_column_usage usage
86
- ON constraints.constraint_name = usage.constraint_name
87
- AND constraints.table_name = ${table}
88
- JOIN information_schema.constraint_column_usage usage2
89
- ON usage.constraint_name = usage2.constraint_name
90
- AND ( usage.table_name = ${table}
91
- AND usage.table_schema = ${schema} )
92
- ORDER BY usage.constraint_name, usage.ordinal_position, usage2.table_name, usage2.column_name
87
+ ON constraints.constraint_catalog = usage.constraint_catalog
88
+ AND constraints.constraint_schema = usage.constraint_schema
89
+ AND constraints.constraint_name = usage.constraint_name
90
+ AND constraints.table_schema = usage.table_schema
91
+ AND constraints.table_name = usage.table_name
92
+ LEFT JOIN pg_catalog.pg_namespace local_namespace
93
+ ON local_namespace.nspname = constraints.table_schema
94
+ LEFT JOIN pg_catalog.pg_class local_table
95
+ ON local_table.relname = constraints.table_name
96
+ AND local_table.relnamespace = local_namespace.oid
97
+ LEFT JOIN pg_catalog.pg_constraint foreign_constraint
98
+ ON foreign_constraint.conrelid = local_table.oid
99
+ AND foreign_constraint.conname = constraints.constraint_name
100
+ AND foreign_constraint.contype = 'f'
101
+ LEFT JOIN pg_catalog.pg_class foreign_table
102
+ ON foreign_table.oid = foreign_constraint.confrelid
103
+ LEFT JOIN pg_catalog.pg_namespace foreign_namespace
104
+ ON foreign_namespace.oid = foreign_table.relnamespace
105
+ LEFT JOIN pg_catalog.pg_attribute foreign_column
106
+ ON foreign_column.attrelid = foreign_constraint.confrelid
107
+ AND foreign_column.attnum = foreign_constraint.confkey[usage.ordinal_position]
108
+ WHERE constraints.table_name = ${table}
109
+ AND constraints.table_schema = ${schema}
110
+ ORDER BY usage.constraint_name, usage.ordinal_position
93
111
  `
94
112
  const constraintsList = await db.query(query)
95
113
  return constraintsList
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "3.63.0-alpha.3",
3
+ "version": "3.64.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "@opentelemetry/api": "^1.9.0",
20
20
  "cleaner-spec-reporter": "^0.5.0",
21
21
  "eslint": "9",
22
- "fastify": "^5.7.0",
22
+ "fastify": "^5.0.0",
23
23
  "neostandard": "^0.12.0",
24
24
  "tstyche": "^6.2.0",
25
25
  "typescript": "^5.9.3"
@@ -33,10 +33,10 @@
33
33
  "@matteo.collina/sqlite-pool": "^0.7.0",
34
34
  "async-cache-dedupe": "^2.1.0",
35
35
  "camelcase": "^6.3.0",
36
- "fastify-plugin": "^5.0.0",
36
+ "fastify-plugin": "^6.0.0",
37
37
  "inflected": "^2.1.0",
38
- "@platformatic/foundation": "3.63.0-alpha.3",
39
- "@platformatic/telemetry": "3.63.0-alpha.3"
38
+ "@platformatic/foundation": "3.64.0",
39
+ "@platformatic/telemetry": "3.64.0"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=22.19.0"