@pgpmjs/core 4.13.1 → 4.13.2

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.
@@ -1,5 +1,82 @@
1
1
  import { Parser } from 'csv-to-pg';
2
2
  import { getPgPool } from 'pg-cache';
3
+ /**
4
+ * Map PostgreSQL data types to FieldType values.
5
+ * Uses udt_name from information_schema which gives the base type name.
6
+ */
7
+ const mapPgTypeToFieldType = (udtName) => {
8
+ switch (udtName) {
9
+ case 'uuid':
10
+ return 'uuid';
11
+ case '_uuid':
12
+ return 'uuid[]';
13
+ case 'text':
14
+ case 'varchar':
15
+ case 'bpchar':
16
+ case 'name':
17
+ return 'text';
18
+ case '_text':
19
+ case '_varchar':
20
+ return 'text[]';
21
+ case 'bool':
22
+ return 'boolean';
23
+ case 'jsonb':
24
+ case 'json':
25
+ return 'jsonb';
26
+ case 'int4':
27
+ case 'int8':
28
+ case 'int2':
29
+ case 'numeric':
30
+ return 'int';
31
+ case 'interval':
32
+ return 'interval';
33
+ case 'timestamptz':
34
+ case 'timestamp':
35
+ return 'timestamptz';
36
+ default:
37
+ return 'text';
38
+ }
39
+ };
40
+ /**
41
+ * Query actual columns from information_schema for a given table.
42
+ * Returns a map of column_name -> udt_name (PostgreSQL type).
43
+ */
44
+ const getTableColumns = async (pool, schemaName, tableName) => {
45
+ const result = await pool.query(`
46
+ SELECT column_name, udt_name
47
+ FROM information_schema.columns
48
+ WHERE table_schema = $1 AND table_name = $2
49
+ ORDER BY ordinal_position
50
+ `, [schemaName, tableName]);
51
+ const columns = new Map();
52
+ for (const row of result.rows) {
53
+ columns.set(row.column_name, row.udt_name);
54
+ }
55
+ return columns;
56
+ };
57
+ /**
58
+ * Build dynamic fields config by intersecting the hardcoded config with actual database columns.
59
+ * - Only includes columns that exist in the database
60
+ * - Preserves special type hints from config (image, upload, url) for columns that exist
61
+ * - Infers types from PostgreSQL for columns not in config
62
+ */
63
+ const buildDynamicFields = async (pool, tableConfig) => {
64
+ const actualColumns = await getTableColumns(pool, tableConfig.schema, tableConfig.table);
65
+ if (actualColumns.size === 0) {
66
+ // Table doesn't exist, return empty fields
67
+ return {};
68
+ }
69
+ const dynamicFields = {};
70
+ // For each column in the hardcoded config, check if it exists in the database
71
+ for (const [fieldName, fieldType] of Object.entries(tableConfig.fields)) {
72
+ if (actualColumns.has(fieldName)) {
73
+ // Column exists - use the config's type hint (preserves special types like 'image', 'upload', 'url')
74
+ dynamicFields[fieldName] = fieldType;
75
+ }
76
+ // If column doesn't exist in database, skip it (this fixes the bug)
77
+ }
78
+ return dynamicFields;
79
+ };
3
80
  const config = {
4
81
  // =============================================================================
5
82
  // metaschema_public tables
@@ -771,15 +848,41 @@ export const exportMeta = async ({ opts, dbname, database_id }) => {
771
848
  database: dbname
772
849
  });
773
850
  const sql = {};
774
- const parsers = Object.entries(config).reduce((m, [name, config]) => {
775
- m[name] = new Parser(config);
776
- return m;
777
- }, {});
851
+ // Cache for dynamically built parsers
852
+ const parsers = {};
853
+ // Build parser dynamically by querying actual columns from the database
854
+ const getParser = async (key) => {
855
+ if (parsers[key]) {
856
+ return parsers[key];
857
+ }
858
+ const tableConfig = config[key];
859
+ if (!tableConfig) {
860
+ return null;
861
+ }
862
+ // Build fields dynamically based on actual database columns
863
+ const dynamicFields = await buildDynamicFields(pool, tableConfig);
864
+ if (Object.keys(dynamicFields).length === 0) {
865
+ // No columns found (table doesn't exist or no matching columns)
866
+ return null;
867
+ }
868
+ const parser = new Parser({
869
+ schema: tableConfig.schema,
870
+ table: tableConfig.table,
871
+ conflictDoNothing: tableConfig.conflictDoNothing,
872
+ fields: dynamicFields
873
+ });
874
+ parsers[key] = parser;
875
+ return parser;
876
+ };
778
877
  const queryAndParse = async (key, query) => {
779
878
  try {
879
+ const parser = await getParser(key);
880
+ if (!parser) {
881
+ return;
882
+ }
780
883
  const result = await pool.query(query, [database_id]);
781
884
  if (result.rows.length) {
782
- const parsed = await parsers[key].parse(result.rows);
885
+ const parsed = await parser.parse(result.rows);
783
886
  if (parsed) {
784
887
  sql[key] = parsed;
785
888
  }
@@ -3,6 +3,83 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exportMeta = void 0;
4
4
  const csv_to_pg_1 = require("csv-to-pg");
5
5
  const pg_cache_1 = require("pg-cache");
6
+ /**
7
+ * Map PostgreSQL data types to FieldType values.
8
+ * Uses udt_name from information_schema which gives the base type name.
9
+ */
10
+ const mapPgTypeToFieldType = (udtName) => {
11
+ switch (udtName) {
12
+ case 'uuid':
13
+ return 'uuid';
14
+ case '_uuid':
15
+ return 'uuid[]';
16
+ case 'text':
17
+ case 'varchar':
18
+ case 'bpchar':
19
+ case 'name':
20
+ return 'text';
21
+ case '_text':
22
+ case '_varchar':
23
+ return 'text[]';
24
+ case 'bool':
25
+ return 'boolean';
26
+ case 'jsonb':
27
+ case 'json':
28
+ return 'jsonb';
29
+ case 'int4':
30
+ case 'int8':
31
+ case 'int2':
32
+ case 'numeric':
33
+ return 'int';
34
+ case 'interval':
35
+ return 'interval';
36
+ case 'timestamptz':
37
+ case 'timestamp':
38
+ return 'timestamptz';
39
+ default:
40
+ return 'text';
41
+ }
42
+ };
43
+ /**
44
+ * Query actual columns from information_schema for a given table.
45
+ * Returns a map of column_name -> udt_name (PostgreSQL type).
46
+ */
47
+ const getTableColumns = async (pool, schemaName, tableName) => {
48
+ const result = await pool.query(`
49
+ SELECT column_name, udt_name
50
+ FROM information_schema.columns
51
+ WHERE table_schema = $1 AND table_name = $2
52
+ ORDER BY ordinal_position
53
+ `, [schemaName, tableName]);
54
+ const columns = new Map();
55
+ for (const row of result.rows) {
56
+ columns.set(row.column_name, row.udt_name);
57
+ }
58
+ return columns;
59
+ };
60
+ /**
61
+ * Build dynamic fields config by intersecting the hardcoded config with actual database columns.
62
+ * - Only includes columns that exist in the database
63
+ * - Preserves special type hints from config (image, upload, url) for columns that exist
64
+ * - Infers types from PostgreSQL for columns not in config
65
+ */
66
+ const buildDynamicFields = async (pool, tableConfig) => {
67
+ const actualColumns = await getTableColumns(pool, tableConfig.schema, tableConfig.table);
68
+ if (actualColumns.size === 0) {
69
+ // Table doesn't exist, return empty fields
70
+ return {};
71
+ }
72
+ const dynamicFields = {};
73
+ // For each column in the hardcoded config, check if it exists in the database
74
+ for (const [fieldName, fieldType] of Object.entries(tableConfig.fields)) {
75
+ if (actualColumns.has(fieldName)) {
76
+ // Column exists - use the config's type hint (preserves special types like 'image', 'upload', 'url')
77
+ dynamicFields[fieldName] = fieldType;
78
+ }
79
+ // If column doesn't exist in database, skip it (this fixes the bug)
80
+ }
81
+ return dynamicFields;
82
+ };
6
83
  const config = {
7
84
  // =============================================================================
8
85
  // metaschema_public tables
@@ -774,15 +851,41 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
774
851
  database: dbname
775
852
  });
776
853
  const sql = {};
777
- const parsers = Object.entries(config).reduce((m, [name, config]) => {
778
- m[name] = new csv_to_pg_1.Parser(config);
779
- return m;
780
- }, {});
854
+ // Cache for dynamically built parsers
855
+ const parsers = {};
856
+ // Build parser dynamically by querying actual columns from the database
857
+ const getParser = async (key) => {
858
+ if (parsers[key]) {
859
+ return parsers[key];
860
+ }
861
+ const tableConfig = config[key];
862
+ if (!tableConfig) {
863
+ return null;
864
+ }
865
+ // Build fields dynamically based on actual database columns
866
+ const dynamicFields = await buildDynamicFields(pool, tableConfig);
867
+ if (Object.keys(dynamicFields).length === 0) {
868
+ // No columns found (table doesn't exist or no matching columns)
869
+ return null;
870
+ }
871
+ const parser = new csv_to_pg_1.Parser({
872
+ schema: tableConfig.schema,
873
+ table: tableConfig.table,
874
+ conflictDoNothing: tableConfig.conflictDoNothing,
875
+ fields: dynamicFields
876
+ });
877
+ parsers[key] = parser;
878
+ return parser;
879
+ };
781
880
  const queryAndParse = async (key, query) => {
782
881
  try {
882
+ const parser = await getParser(key);
883
+ if (!parser) {
884
+ return;
885
+ }
783
886
  const result = await pool.query(query, [database_id]);
784
887
  if (result.rows.length) {
785
- const parsed = await parsers[key].parse(result.rows);
888
+ const parsed = await parser.parse(result.rows);
786
889
  if (parsed) {
787
890
  sql[key] = parsed;
788
891
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "4.13.1",
3
+ "version": "4.13.2",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -64,5 +64,5 @@
64
64
  "pgsql-parser": "^17.9.11",
65
65
  "yanse": "^0.1.11"
66
66
  },
67
- "gitHead": "cf0d7de6f89b52e1e3dcd2cda068e7be27296615"
67
+ "gitHead": "6a66f8849db582924e8a24e938a6a38ae3122467"
68
68
  }