@pgpmjs/export 1.3.2 → 1.4.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.
@@ -48,6 +48,77 @@ export const getDataExportColumns = async (db, schema, table) => {
48
48
  * default depends on a non-immutable function (wall-clock defaults).
49
49
  */
50
50
  export const isVolatileTimestampColumn = (column) => column.volatileDefault && /^timestamp/.test(column.type);
51
+ /**
52
+ * Classify columns described by metadata instead of a live physical table.
53
+ *
54
+ * Metadata-only planes describe their tables as schema metadata but are not
55
+ * physically deployed when the export runs, so pg_attrdef-based introspection
56
+ * cannot see them. This builds an ephemeral temp table from the supplied
57
+ * descriptions and runs the exact same provolatile-based classification that
58
+ * getDataExportColumns uses for physical tables — one shared code path — so
59
+ * volatile-default detection (isVolatileTimestampColumn, etc.) behaves
60
+ * identically for both.
61
+ *
62
+ * Results preserve the input order and names. Each column's type must be a
63
+ * type resolvable in the connected database; each non-null default must be a
64
+ * valid default expression for that type. `db` must be a single session
65
+ * (client), not a pool: the classification runs inside a transaction (or a
66
+ * savepoint when the caller is already in one) that is always rolled back, so
67
+ * the temp table never survives — not even on failure — and nothing is ever
68
+ * committed.
69
+ */
70
+ export const getMetadataExportColumns = async (db, columns) => {
71
+ if (columns.length === 0)
72
+ return [];
73
+ const tempTable = 'pgpm_metadata_export_columns';
74
+ // Synthetic column identifiers keep the DDL immune to reserved words and
75
+ // duplicate names; results map back to the caller's columns by ordinal.
76
+ const columnDefs = columns
77
+ .map((c, i) => {
78
+ const def = c.columnDefault != null && c.columnDefault !== ''
79
+ ? ` DEFAULT ${c.columnDefault}`
80
+ : '';
81
+ return `c${i} ${c.type}${def}`;
82
+ })
83
+ .join(', ');
84
+ // Savepoint when already inside a caller transaction, otherwise our own
85
+ // transaction. Either way the work is rolled back below, so the temp table
86
+ // name is deterministic and can never collide or leak.
87
+ let usedSavepoint = true;
88
+ try {
89
+ await db.query(`SAVEPOINT ${tempTable}`);
90
+ }
91
+ catch {
92
+ usedSavepoint = false;
93
+ await db.query('BEGIN');
94
+ }
95
+ try {
96
+ await db.query(`CREATE TEMP TABLE ${tempTable} (${columnDefs})`);
97
+ const tempSchemaRes = await db.query(`SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()`);
98
+ const tempSchema = tempSchemaRes.rows[0]?.nspname;
99
+ const introspected = await getDataExportColumns(db, tempSchema, tempTable);
100
+ // getDataExportColumns orders by attnum, matching the c0..cN creation
101
+ // order, so results align with the input columns by index.
102
+ return columns.map((c, i) => {
103
+ const found = introspected[i];
104
+ return {
105
+ name: c.name,
106
+ type: found ? found.type : c.type,
107
+ columnDefault: c.columnDefault ?? null,
108
+ volatileDefault: found ? found.volatileDefault : false
109
+ };
110
+ });
111
+ }
112
+ finally {
113
+ if (usedSavepoint) {
114
+ await db.query(`ROLLBACK TO SAVEPOINT ${tempTable}`);
115
+ await db.query(`RELEASE SAVEPOINT ${tempTable}`);
116
+ }
117
+ else {
118
+ await db.query('ROLLBACK');
119
+ }
120
+ }
121
+ };
51
122
  const RAW_ID = '__raw_id';
52
123
  /**
53
124
  * Export a table's rows as a single deterministic INSERT statement.
package/export-data.d.ts CHANGED
@@ -33,6 +33,41 @@ export declare const getDataExportColumns: (db: Queryable, schema: string, table
33
33
  * default depends on a non-immutable function (wall-clock defaults).
34
34
  */
35
35
  export declare const isVolatileTimestampColumn: (column: DataExportColumn) => boolean;
36
+ /**
37
+ * A column described by metadata (its name, SQL type, and default expression
38
+ * text) rather than by a live physical table. Fed to getMetadataExportColumns
39
+ * so metadata-only planes (tables that exist as schema metadata but are not
40
+ * physically deployed at export time) get the same volatility classification
41
+ * as physical tables.
42
+ */
43
+ export interface MetadataExportColumn {
44
+ /** Column name. */
45
+ name: string;
46
+ /** SQL type, e.g. 'timestamptz', 'text', 'uuid' — must resolve in the DB. */
47
+ type: string;
48
+ /** The column's default expression text, if any (e.g. 'now()'). */
49
+ columnDefault: string | null;
50
+ }
51
+ /**
52
+ * Classify columns described by metadata instead of a live physical table.
53
+ *
54
+ * Metadata-only planes describe their tables as schema metadata but are not
55
+ * physically deployed when the export runs, so pg_attrdef-based introspection
56
+ * cannot see them. This builds an ephemeral temp table from the supplied
57
+ * descriptions and runs the exact same provolatile-based classification that
58
+ * getDataExportColumns uses for physical tables — one shared code path — so
59
+ * volatile-default detection (isVolatileTimestampColumn, etc.) behaves
60
+ * identically for both.
61
+ *
62
+ * Results preserve the input order and names. Each column's type must be a
63
+ * type resolvable in the connected database; each non-null default must be a
64
+ * valid default expression for that type. `db` must be a single session
65
+ * (client), not a pool: the classification runs inside a transaction (or a
66
+ * savepoint when the caller is already in one) that is always rolled back, so
67
+ * the temp table never survives — not even on failure — and nothing is ever
68
+ * committed.
69
+ */
70
+ export declare const getMetadataExportColumns: (db: Queryable, columns: MetadataExportColumn[]) => Promise<DataExportColumn[]>;
36
71
  export interface DataExportTableSpec {
37
72
  schema: string;
38
73
  table: string;
package/export-data.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildDataVerifyScript = exports.buildDataRevertScript = exports.buildDataDeployScript = exports.exportTablesData = exports.exportTableData = exports.isVolatileTimestampColumn = exports.getDataExportColumns = void 0;
3
+ exports.buildDataVerifyScript = exports.buildDataRevertScript = exports.buildDataDeployScript = exports.exportTablesData = exports.exportTableData = exports.getMetadataExportColumns = exports.isVolatileTimestampColumn = exports.getDataExportColumns = void 0;
4
4
  /**
5
5
  * Generic, deterministic table-data export.
6
6
  *
@@ -53,6 +53,78 @@ exports.getDataExportColumns = getDataExportColumns;
53
53
  */
54
54
  const isVolatileTimestampColumn = (column) => column.volatileDefault && /^timestamp/.test(column.type);
55
55
  exports.isVolatileTimestampColumn = isVolatileTimestampColumn;
56
+ /**
57
+ * Classify columns described by metadata instead of a live physical table.
58
+ *
59
+ * Metadata-only planes describe their tables as schema metadata but are not
60
+ * physically deployed when the export runs, so pg_attrdef-based introspection
61
+ * cannot see them. This builds an ephemeral temp table from the supplied
62
+ * descriptions and runs the exact same provolatile-based classification that
63
+ * getDataExportColumns uses for physical tables — one shared code path — so
64
+ * volatile-default detection (isVolatileTimestampColumn, etc.) behaves
65
+ * identically for both.
66
+ *
67
+ * Results preserve the input order and names. Each column's type must be a
68
+ * type resolvable in the connected database; each non-null default must be a
69
+ * valid default expression for that type. `db` must be a single session
70
+ * (client), not a pool: the classification runs inside a transaction (or a
71
+ * savepoint when the caller is already in one) that is always rolled back, so
72
+ * the temp table never survives — not even on failure — and nothing is ever
73
+ * committed.
74
+ */
75
+ const getMetadataExportColumns = async (db, columns) => {
76
+ if (columns.length === 0)
77
+ return [];
78
+ const tempTable = 'pgpm_metadata_export_columns';
79
+ // Synthetic column identifiers keep the DDL immune to reserved words and
80
+ // duplicate names; results map back to the caller's columns by ordinal.
81
+ const columnDefs = columns
82
+ .map((c, i) => {
83
+ const def = c.columnDefault != null && c.columnDefault !== ''
84
+ ? ` DEFAULT ${c.columnDefault}`
85
+ : '';
86
+ return `c${i} ${c.type}${def}`;
87
+ })
88
+ .join(', ');
89
+ // Savepoint when already inside a caller transaction, otherwise our own
90
+ // transaction. Either way the work is rolled back below, so the temp table
91
+ // name is deterministic and can never collide or leak.
92
+ let usedSavepoint = true;
93
+ try {
94
+ await db.query(`SAVEPOINT ${tempTable}`);
95
+ }
96
+ catch {
97
+ usedSavepoint = false;
98
+ await db.query('BEGIN');
99
+ }
100
+ try {
101
+ await db.query(`CREATE TEMP TABLE ${tempTable} (${columnDefs})`);
102
+ const tempSchemaRes = await db.query(`SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()`);
103
+ const tempSchema = tempSchemaRes.rows[0]?.nspname;
104
+ const introspected = await (0, exports.getDataExportColumns)(db, tempSchema, tempTable);
105
+ // getDataExportColumns orders by attnum, matching the c0..cN creation
106
+ // order, so results align with the input columns by index.
107
+ return columns.map((c, i) => {
108
+ const found = introspected[i];
109
+ return {
110
+ name: c.name,
111
+ type: found ? found.type : c.type,
112
+ columnDefault: c.columnDefault ?? null,
113
+ volatileDefault: found ? found.volatileDefault : false
114
+ };
115
+ });
116
+ }
117
+ finally {
118
+ if (usedSavepoint) {
119
+ await db.query(`ROLLBACK TO SAVEPOINT ${tempTable}`);
120
+ await db.query(`RELEASE SAVEPOINT ${tempTable}`);
121
+ }
122
+ else {
123
+ await db.query('ROLLBACK');
124
+ }
125
+ }
126
+ };
127
+ exports.getMetadataExportColumns = getMetadataExportColumns;
56
128
  const RAW_ID = '__raw_id';
57
129
  /**
58
130
  * Export a table's rows as a single deterministic INSERT statement.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/export",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM export tools for SQL and GraphQL database migration extraction",
6
6
  "main": "index.js",
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@constructive-io/query-builder": "^3.1.2",
48
48
  "@pgpmjs/core": "^7.4.2",
49
- "@pgpmjs/migrate-client": "^1.1.2",
49
+ "@pgpmjs/migrate-client": "^1.1.3",
50
50
  "@pgpmjs/types": "^2.37.2",
51
51
  "csv-to-pg": "^4.1.2",
52
52
  "glob": "^13.0.6",
@@ -56,5 +56,5 @@
56
56
  "pg-cache": "^3.16.2",
57
57
  "pg-env": "^1.19.2"
58
58
  },
59
- "gitHead": "08741ce3a6897192c2dbc7d1da48b9f456ffb59f"
59
+ "gitHead": "0e3ee42c44c9b35ffac4bf1faa6775eb69311ec4"
60
60
  }