@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.
- package/esm/export-data.js +71 -0
- package/export-data.d.ts +35 -0
- package/export-data.js +73 -1
- package/package.json +3 -3
package/esm/export-data.js
CHANGED
|
@@ -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
|
+
"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.
|
|
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": "
|
|
59
|
+
"gitHead": "0e3ee42c44c9b35ffac4bf1faa6775eb69311ec4"
|
|
60
60
|
}
|