@pgpmjs/core 4.13.0 → 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.
- package/esm/export/export-meta.js +108 -5
- package/export/export-meta.js +108 -5
- package/package.json +3 -3
|
@@ -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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
|
885
|
+
const parsed = await parser.parse(result.rows);
|
|
783
886
|
if (parsed) {
|
|
784
887
|
sql[key] = parsed;
|
|
785
888
|
}
|
package/export/export-meta.js
CHANGED
|
@@ -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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
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.
|
|
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",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@pgpmjs/logger": "^1.3.7",
|
|
53
53
|
"@pgpmjs/server-utils": "^2.8.14",
|
|
54
54
|
"@pgpmjs/types": "^2.14.0",
|
|
55
|
-
"csv-to-pg": "^3.4.
|
|
55
|
+
"csv-to-pg": "^3.4.1",
|
|
56
56
|
"genomic": "^5.2.3",
|
|
57
57
|
"glob": "^13.0.0",
|
|
58
58
|
"komoji": "^0.7.14",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"pgsql-parser": "^17.9.11",
|
|
65
65
|
"yanse": "^0.1.11"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "6a66f8849db582924e8a24e938a6a38ae3122467"
|
|
68
68
|
}
|