@pgpmjs/core 4.13.3 → 4.15.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/README.md +31 -0
- package/esm/export/export-meta.js +55 -1
- package/esm/export/export-migrations.js +4 -0
- package/esm/migrate/utils/errors.js +169 -0
- package/esm/migrate/utils/transaction.js +9 -10
- package/export/export-meta.js +55 -1
- package/export/export-migrations.js +4 -0
- package/migrate/utils/errors.d.ts +18 -0
- package/migrate/utils/errors.js +175 -0
- package/migrate/utils/transaction.d.ts +1 -0
- package/migrate/utils/transaction.js +12 -10
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -22,6 +22,37 @@ pgpm Core is the main package for pgpm, providing tools for database migrations,
|
|
|
22
22
|
- Reading and writing SQL scripts
|
|
23
23
|
- Resolving dependencies between migrations
|
|
24
24
|
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
### Error Output Configuration
|
|
28
|
+
|
|
29
|
+
When migrations fail, pgpm provides detailed error output including query history. For large deployments with many migrations, this output can be verbose. The following environment variables control error output formatting:
|
|
30
|
+
|
|
31
|
+
| Variable | Default | Description |
|
|
32
|
+
|----------|---------|-------------|
|
|
33
|
+
| `PGPM_ERROR_QUERY_HISTORY_LIMIT` | `30` | Maximum number of queries to show in error output. Earlier queries are omitted with a count. |
|
|
34
|
+
| `PGPM_ERROR_MAX_LENGTH` | `10000` | Maximum characters for error output. Output exceeding this is truncated. |
|
|
35
|
+
| `PGPM_ERROR_VERBOSE` | `false` | Set to `true` to disable all limiting and show full error output. |
|
|
36
|
+
|
|
37
|
+
#### Smart Query Collapsing
|
|
38
|
+
|
|
39
|
+
Error output automatically collapses consecutive identical queries (like repeated `pgpm_migrate.deploy` calls) into a summary showing:
|
|
40
|
+
- The range of query numbers (e.g., "2-57")
|
|
41
|
+
- The total count (e.g., "56 calls")
|
|
42
|
+
- For deploy calls: the first and last change names for context
|
|
43
|
+
|
|
44
|
+
Example collapsed output:
|
|
45
|
+
```
|
|
46
|
+
Query history for this transaction:
|
|
47
|
+
1. BEGIN
|
|
48
|
+
2-57. CALL pgpm_migrate.deploy($1::TEXT, ...) (56 calls)
|
|
49
|
+
First: schemas/metaschema_public/tables/extension/table
|
|
50
|
+
Last: schemas/metaschema_modules_public/tables/permissions_module/table
|
|
51
|
+
58. ROLLBACK
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To see full uncompressed output, set `PGPM_ERROR_VERBOSE=true`.
|
|
55
|
+
|
|
25
56
|
---
|
|
26
57
|
|
|
27
58
|
## Education and Tutorials
|
|
@@ -149,7 +149,7 @@ const config = {
|
|
|
149
149
|
privilege: 'text',
|
|
150
150
|
permissive: 'boolean',
|
|
151
151
|
disabled: 'boolean',
|
|
152
|
-
|
|
152
|
+
policy_type: 'text',
|
|
153
153
|
data: 'jsonb'
|
|
154
154
|
}
|
|
155
155
|
},
|
|
@@ -822,11 +822,65 @@ const config = {
|
|
|
822
822
|
private_schema_id: 'uuid',
|
|
823
823
|
table_id: 'uuid',
|
|
824
824
|
field_id: 'uuid',
|
|
825
|
+
node_type: 'text',
|
|
825
826
|
data: 'jsonb',
|
|
826
827
|
triggers: 'text[]',
|
|
827
828
|
functions: 'text[]'
|
|
828
829
|
}
|
|
829
830
|
},
|
|
831
|
+
table_module: {
|
|
832
|
+
schema: 'metaschema_modules_public',
|
|
833
|
+
table: 'table_module',
|
|
834
|
+
fields: {
|
|
835
|
+
id: 'uuid',
|
|
836
|
+
database_id: 'uuid',
|
|
837
|
+
private_schema_id: 'uuid',
|
|
838
|
+
table_id: 'uuid',
|
|
839
|
+
node_type: 'text',
|
|
840
|
+
data: 'jsonb',
|
|
841
|
+
fields: 'uuid[]'
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
user_profiles_module: {
|
|
845
|
+
schema: 'metaschema_modules_public',
|
|
846
|
+
table: 'user_profiles_module',
|
|
847
|
+
fields: {
|
|
848
|
+
id: 'uuid',
|
|
849
|
+
database_id: 'uuid',
|
|
850
|
+
schema_id: 'uuid',
|
|
851
|
+
private_schema_id: 'uuid',
|
|
852
|
+
table_id: 'uuid',
|
|
853
|
+
table_name: 'text',
|
|
854
|
+
users_table_id: 'uuid'
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
user_settings_module: {
|
|
858
|
+
schema: 'metaschema_modules_public',
|
|
859
|
+
table: 'user_settings_module',
|
|
860
|
+
fields: {
|
|
861
|
+
id: 'uuid',
|
|
862
|
+
database_id: 'uuid',
|
|
863
|
+
schema_id: 'uuid',
|
|
864
|
+
private_schema_id: 'uuid',
|
|
865
|
+
table_id: 'uuid',
|
|
866
|
+
table_name: 'text',
|
|
867
|
+
users_table_id: 'uuid'
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
organization_settings_module: {
|
|
871
|
+
schema: 'metaschema_modules_public',
|
|
872
|
+
table: 'organization_settings_module',
|
|
873
|
+
fields: {
|
|
874
|
+
id: 'uuid',
|
|
875
|
+
database_id: 'uuid',
|
|
876
|
+
schema_id: 'uuid',
|
|
877
|
+
private_schema_id: 'uuid',
|
|
878
|
+
table_id: 'uuid',
|
|
879
|
+
table_name: 'text',
|
|
880
|
+
entity_table_id: 'uuid',
|
|
881
|
+
membership_type: 'int'
|
|
882
|
+
}
|
|
883
|
+
},
|
|
830
884
|
uuid_module: {
|
|
831
885
|
schema: 'metaschema_modules_public',
|
|
832
886
|
table: 'uuid_module',
|
|
@@ -260,6 +260,10 @@ SET session_replication_role TO DEFAULT;`;
|
|
|
260
260
|
'crypto_addresses_module',
|
|
261
261
|
'crypto_auth_module',
|
|
262
262
|
'field_module',
|
|
263
|
+
'table_module',
|
|
264
|
+
'user_profiles_module',
|
|
265
|
+
'user_settings_module',
|
|
266
|
+
'organization_settings_module',
|
|
263
267
|
'uuid_module',
|
|
264
268
|
'default_ids_module',
|
|
265
269
|
'denormalized_table_field'
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { getEnvVars } from '@pgpmjs/env';
|
|
2
|
+
import { pgpmDefaults } from '@pgpmjs/types';
|
|
3
|
+
/**
|
|
4
|
+
* Get error output configuration from environment variables with defaults.
|
|
5
|
+
* Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
|
|
6
|
+
*/
|
|
7
|
+
export const getErrorOutputConfig = () => {
|
|
8
|
+
const envVars = getEnvVars();
|
|
9
|
+
const defaults = pgpmDefaults.errorOutput;
|
|
10
|
+
return {
|
|
11
|
+
queryHistoryLimit: envVars.errorOutput?.queryHistoryLimit ?? defaults.queryHistoryLimit,
|
|
12
|
+
maxLength: envVars.errorOutput?.maxLength ?? defaults.maxLength,
|
|
13
|
+
verbose: envVars.errorOutput?.verbose ?? defaults.verbose,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const errorConfig = getErrorOutputConfig();
|
|
17
|
+
/**
|
|
18
|
+
* Extract a friendly name from pgpm_migrate.deploy params for better error context
|
|
19
|
+
*/
|
|
20
|
+
function extractDeployChangeName(params) {
|
|
21
|
+
if (!params || params.length < 2)
|
|
22
|
+
return null;
|
|
23
|
+
// params[1] is the change name (e.g., "schemas/metaschema_public/tables/extension/table")
|
|
24
|
+
return typeof params[1] === 'string' ? params[1] : null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Group consecutive queries by their query template (ignoring params)
|
|
28
|
+
*/
|
|
29
|
+
function groupConsecutiveQueries(history) {
|
|
30
|
+
if (history.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
const groups = [];
|
|
33
|
+
let currentGroup = {
|
|
34
|
+
query: history[0].query,
|
|
35
|
+
startIndex: 0,
|
|
36
|
+
endIndex: 0,
|
|
37
|
+
count: 1,
|
|
38
|
+
entries: [history[0]]
|
|
39
|
+
};
|
|
40
|
+
for (let i = 1; i < history.length; i++) {
|
|
41
|
+
const entry = history[i];
|
|
42
|
+
if (entry.query === currentGroup.query) {
|
|
43
|
+
// Same query template, extend the group
|
|
44
|
+
currentGroup.endIndex = i;
|
|
45
|
+
currentGroup.count++;
|
|
46
|
+
currentGroup.entries.push(entry);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Different query, start a new group
|
|
50
|
+
groups.push(currentGroup);
|
|
51
|
+
currentGroup = {
|
|
52
|
+
query: entry.query,
|
|
53
|
+
startIndex: i,
|
|
54
|
+
endIndex: i,
|
|
55
|
+
count: 1,
|
|
56
|
+
entries: [entry]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
groups.push(currentGroup);
|
|
61
|
+
return groups;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format a single query entry for display
|
|
65
|
+
*/
|
|
66
|
+
function formatQueryEntry(entry, index) {
|
|
67
|
+
const duration = entry.duration ? ` (${entry.duration}ms)` : '';
|
|
68
|
+
const params = entry.params && entry.params.length > 0
|
|
69
|
+
? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
|
|
70
|
+
: '';
|
|
71
|
+
return ` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Format a group of queries for display, collapsing repetitive queries
|
|
75
|
+
*/
|
|
76
|
+
function formatQueryGroup(group) {
|
|
77
|
+
const lines = [];
|
|
78
|
+
const queryPreview = group.query.split('\n')[0].trim();
|
|
79
|
+
if (group.count === 1) {
|
|
80
|
+
// Single query, format normally
|
|
81
|
+
lines.push(formatQueryEntry(group.entries[0], group.startIndex));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Multiple consecutive identical queries, collapse them
|
|
85
|
+
const isPgpmDeploy = queryPreview.includes('pgpm_migrate.deploy');
|
|
86
|
+
// Show range and count
|
|
87
|
+
lines.push(` ${group.startIndex + 1}-${group.endIndex + 1}. ${queryPreview} (${group.count} calls)`);
|
|
88
|
+
// For pgpm_migrate.deploy, show first and last change names for context
|
|
89
|
+
if (isPgpmDeploy) {
|
|
90
|
+
const firstChange = extractDeployChangeName(group.entries[0].params);
|
|
91
|
+
const lastChange = extractDeployChangeName(group.entries[group.entries.length - 1].params);
|
|
92
|
+
if (firstChange) {
|
|
93
|
+
lines.push(` First: ${firstChange}`);
|
|
94
|
+
}
|
|
95
|
+
if (lastChange && lastChange !== firstChange) {
|
|
96
|
+
lines.push(` Last: ${lastChange}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// For other queries, show first and last params
|
|
101
|
+
const firstParams = group.entries[0].params;
|
|
102
|
+
const lastParams = group.entries[group.entries.length - 1].params;
|
|
103
|
+
if (firstParams && firstParams.length > 0) {
|
|
104
|
+
lines.push(` First params: ${JSON.stringify(firstParams.slice(0, 2))}${firstParams.length > 2 ? '...' : ''}`);
|
|
105
|
+
}
|
|
106
|
+
if (lastParams && lastParams.length > 0 && JSON.stringify(lastParams) !== JSON.stringify(firstParams)) {
|
|
107
|
+
lines.push(` Last params: ${JSON.stringify(lastParams.slice(0, 2))}${lastParams.length > 2 ? '...' : ''}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return lines;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format query history with smart collapsing and limiting
|
|
115
|
+
*/
|
|
116
|
+
export function formatQueryHistory(history) {
|
|
117
|
+
if (history.length === 0)
|
|
118
|
+
return [];
|
|
119
|
+
// In verbose mode, show everything without collapsing
|
|
120
|
+
if (errorConfig.verbose) {
|
|
121
|
+
return history.map((entry, index) => formatQueryEntry(entry, index));
|
|
122
|
+
}
|
|
123
|
+
// Group consecutive identical queries
|
|
124
|
+
const groups = groupConsecutiveQueries(history);
|
|
125
|
+
// Apply limit - keep last N queries worth of groups
|
|
126
|
+
let totalQueries = 0;
|
|
127
|
+
let startGroupIndex = groups.length;
|
|
128
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
129
|
+
totalQueries += groups[i].count;
|
|
130
|
+
if (totalQueries > errorConfig.queryHistoryLimit) {
|
|
131
|
+
startGroupIndex = i + 1;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
startGroupIndex = i;
|
|
135
|
+
}
|
|
136
|
+
const lines = [];
|
|
137
|
+
// If we're truncating, show how many were omitted
|
|
138
|
+
if (startGroupIndex > 0) {
|
|
139
|
+
let omittedCount = 0;
|
|
140
|
+
for (let i = 0; i < startGroupIndex; i++) {
|
|
141
|
+
omittedCount += groups[i].count;
|
|
142
|
+
}
|
|
143
|
+
lines.push(` ... (${omittedCount} earlier queries omitted, set PGPM_ERROR_VERBOSE=true to see all)`);
|
|
144
|
+
}
|
|
145
|
+
// Format the remaining groups
|
|
146
|
+
for (let i = startGroupIndex; i < groups.length; i++) {
|
|
147
|
+
lines.push(...formatQueryGroup(groups[i]));
|
|
148
|
+
}
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Truncate error output if it exceeds the max length
|
|
153
|
+
*/
|
|
154
|
+
export function truncateErrorOutput(lines) {
|
|
155
|
+
if (errorConfig.verbose)
|
|
156
|
+
return lines;
|
|
157
|
+
const joined = lines.join('\n');
|
|
158
|
+
if (joined.length <= errorConfig.maxLength)
|
|
159
|
+
return lines;
|
|
160
|
+
// Truncate and add notice
|
|
161
|
+
const truncated = joined.slice(0, errorConfig.maxLength);
|
|
162
|
+
const lastNewline = truncated.lastIndexOf('\n');
|
|
163
|
+
const cleanTruncated = lastNewline > 0 ? truncated.slice(0, lastNewline) : truncated;
|
|
164
|
+
return [
|
|
165
|
+
...cleanTruncated.split('\n'),
|
|
166
|
+
`... (output truncated at ${errorConfig.maxLength} chars, total was ${joined.length} chars)`,
|
|
167
|
+
` Set PGPM_ERROR_VERBOSE=true to see full output`
|
|
168
|
+
];
|
|
169
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Logger } from '@pgpmjs/logger';
|
|
2
2
|
import { extractPgErrorFields, formatPgErrorFields } from '@pgpmjs/types';
|
|
3
|
+
import { formatQueryHistory, truncateErrorOutput } from './errors';
|
|
4
|
+
// Re-export error formatting functions for backward compatibility
|
|
5
|
+
export { formatQueryHistory, truncateErrorOutput } from './errors';
|
|
3
6
|
const log = new Logger('migrate:transaction');
|
|
4
7
|
/**
|
|
5
8
|
* Execute a function within a transaction context
|
|
@@ -60,16 +63,11 @@ export async function withTransaction(pool, options, fn) {
|
|
|
60
63
|
errorLines.push(...fieldLines);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
// Log query history for debugging
|
|
66
|
+
// Log query history for debugging (with smart collapsing and limiting)
|
|
64
67
|
if (queryHistory.length > 0) {
|
|
65
68
|
errorLines.push('Query history for this transaction:');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const params = entry.params && entry.params.length > 0
|
|
69
|
-
? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
|
|
70
|
-
: '';
|
|
71
|
-
errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
|
|
72
|
-
});
|
|
69
|
+
const historyLines = formatQueryHistory(queryHistory);
|
|
70
|
+
errorLines.push(...historyLines);
|
|
73
71
|
}
|
|
74
72
|
// For transaction aborted errors, provide additional context
|
|
75
73
|
if (error.code === '25P02') {
|
|
@@ -77,8 +75,9 @@ export async function withTransaction(pool, options, fn) {
|
|
|
77
75
|
errorLines.push(' This usually means a previous command in the transaction failed.');
|
|
78
76
|
errorLines.push(' Check the query history above to identify the failing command.');
|
|
79
77
|
}
|
|
80
|
-
//
|
|
81
|
-
|
|
78
|
+
// Apply total output length limit and log the consolidated error message
|
|
79
|
+
const finalLines = truncateErrorOutput(errorLines);
|
|
80
|
+
log.error(finalLines.join('\n'));
|
|
82
81
|
throw error;
|
|
83
82
|
}
|
|
84
83
|
finally {
|
package/export/export-meta.js
CHANGED
|
@@ -152,7 +152,7 @@ const config = {
|
|
|
152
152
|
privilege: 'text',
|
|
153
153
|
permissive: 'boolean',
|
|
154
154
|
disabled: 'boolean',
|
|
155
|
-
|
|
155
|
+
policy_type: 'text',
|
|
156
156
|
data: 'jsonb'
|
|
157
157
|
}
|
|
158
158
|
},
|
|
@@ -825,11 +825,65 @@ const config = {
|
|
|
825
825
|
private_schema_id: 'uuid',
|
|
826
826
|
table_id: 'uuid',
|
|
827
827
|
field_id: 'uuid',
|
|
828
|
+
node_type: 'text',
|
|
828
829
|
data: 'jsonb',
|
|
829
830
|
triggers: 'text[]',
|
|
830
831
|
functions: 'text[]'
|
|
831
832
|
}
|
|
832
833
|
},
|
|
834
|
+
table_module: {
|
|
835
|
+
schema: 'metaschema_modules_public',
|
|
836
|
+
table: 'table_module',
|
|
837
|
+
fields: {
|
|
838
|
+
id: 'uuid',
|
|
839
|
+
database_id: 'uuid',
|
|
840
|
+
private_schema_id: 'uuid',
|
|
841
|
+
table_id: 'uuid',
|
|
842
|
+
node_type: 'text',
|
|
843
|
+
data: 'jsonb',
|
|
844
|
+
fields: 'uuid[]'
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
user_profiles_module: {
|
|
848
|
+
schema: 'metaschema_modules_public',
|
|
849
|
+
table: 'user_profiles_module',
|
|
850
|
+
fields: {
|
|
851
|
+
id: 'uuid',
|
|
852
|
+
database_id: 'uuid',
|
|
853
|
+
schema_id: 'uuid',
|
|
854
|
+
private_schema_id: 'uuid',
|
|
855
|
+
table_id: 'uuid',
|
|
856
|
+
table_name: 'text',
|
|
857
|
+
users_table_id: 'uuid'
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
user_settings_module: {
|
|
861
|
+
schema: 'metaschema_modules_public',
|
|
862
|
+
table: 'user_settings_module',
|
|
863
|
+
fields: {
|
|
864
|
+
id: 'uuid',
|
|
865
|
+
database_id: 'uuid',
|
|
866
|
+
schema_id: 'uuid',
|
|
867
|
+
private_schema_id: 'uuid',
|
|
868
|
+
table_id: 'uuid',
|
|
869
|
+
table_name: 'text',
|
|
870
|
+
users_table_id: 'uuid'
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
organization_settings_module: {
|
|
874
|
+
schema: 'metaschema_modules_public',
|
|
875
|
+
table: 'organization_settings_module',
|
|
876
|
+
fields: {
|
|
877
|
+
id: 'uuid',
|
|
878
|
+
database_id: 'uuid',
|
|
879
|
+
schema_id: 'uuid',
|
|
880
|
+
private_schema_id: 'uuid',
|
|
881
|
+
table_id: 'uuid',
|
|
882
|
+
table_name: 'text',
|
|
883
|
+
entity_table_id: 'uuid',
|
|
884
|
+
membership_type: 'int'
|
|
885
|
+
}
|
|
886
|
+
},
|
|
833
887
|
uuid_module: {
|
|
834
888
|
schema: 'metaschema_modules_public',
|
|
835
889
|
table: 'uuid_module',
|
|
@@ -266,6 +266,10 @@ SET session_replication_role TO DEFAULT;`;
|
|
|
266
266
|
'crypto_addresses_module',
|
|
267
267
|
'crypto_auth_module',
|
|
268
268
|
'field_module',
|
|
269
|
+
'table_module',
|
|
270
|
+
'user_profiles_module',
|
|
271
|
+
'user_settings_module',
|
|
272
|
+
'organization_settings_module',
|
|
269
273
|
'uuid_module',
|
|
270
274
|
'default_ids_module',
|
|
271
275
|
'denormalized_table_field'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QueryHistoryEntry } from './transaction';
|
|
2
|
+
/**
|
|
3
|
+
* Get error output configuration from environment variables with defaults.
|
|
4
|
+
* Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getErrorOutputConfig: () => {
|
|
7
|
+
queryHistoryLimit: number;
|
|
8
|
+
maxLength: number;
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Format query history with smart collapsing and limiting
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatQueryHistory(history: QueryHistoryEntry[]): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Truncate error output if it exceeds the max length
|
|
17
|
+
*/
|
|
18
|
+
export declare function truncateErrorOutput(lines: string[]): string[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getErrorOutputConfig = void 0;
|
|
4
|
+
exports.formatQueryHistory = formatQueryHistory;
|
|
5
|
+
exports.truncateErrorOutput = truncateErrorOutput;
|
|
6
|
+
const env_1 = require("@pgpmjs/env");
|
|
7
|
+
const types_1 = require("@pgpmjs/types");
|
|
8
|
+
/**
|
|
9
|
+
* Get error output configuration from environment variables with defaults.
|
|
10
|
+
* Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
|
|
11
|
+
*/
|
|
12
|
+
const getErrorOutputConfig = () => {
|
|
13
|
+
const envVars = (0, env_1.getEnvVars)();
|
|
14
|
+
const defaults = types_1.pgpmDefaults.errorOutput;
|
|
15
|
+
return {
|
|
16
|
+
queryHistoryLimit: envVars.errorOutput?.queryHistoryLimit ?? defaults.queryHistoryLimit,
|
|
17
|
+
maxLength: envVars.errorOutput?.maxLength ?? defaults.maxLength,
|
|
18
|
+
verbose: envVars.errorOutput?.verbose ?? defaults.verbose,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.getErrorOutputConfig = getErrorOutputConfig;
|
|
22
|
+
const errorConfig = (0, exports.getErrorOutputConfig)();
|
|
23
|
+
/**
|
|
24
|
+
* Extract a friendly name from pgpm_migrate.deploy params for better error context
|
|
25
|
+
*/
|
|
26
|
+
function extractDeployChangeName(params) {
|
|
27
|
+
if (!params || params.length < 2)
|
|
28
|
+
return null;
|
|
29
|
+
// params[1] is the change name (e.g., "schemas/metaschema_public/tables/extension/table")
|
|
30
|
+
return typeof params[1] === 'string' ? params[1] : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Group consecutive queries by their query template (ignoring params)
|
|
34
|
+
*/
|
|
35
|
+
function groupConsecutiveQueries(history) {
|
|
36
|
+
if (history.length === 0)
|
|
37
|
+
return [];
|
|
38
|
+
const groups = [];
|
|
39
|
+
let currentGroup = {
|
|
40
|
+
query: history[0].query,
|
|
41
|
+
startIndex: 0,
|
|
42
|
+
endIndex: 0,
|
|
43
|
+
count: 1,
|
|
44
|
+
entries: [history[0]]
|
|
45
|
+
};
|
|
46
|
+
for (let i = 1; i < history.length; i++) {
|
|
47
|
+
const entry = history[i];
|
|
48
|
+
if (entry.query === currentGroup.query) {
|
|
49
|
+
// Same query template, extend the group
|
|
50
|
+
currentGroup.endIndex = i;
|
|
51
|
+
currentGroup.count++;
|
|
52
|
+
currentGroup.entries.push(entry);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Different query, start a new group
|
|
56
|
+
groups.push(currentGroup);
|
|
57
|
+
currentGroup = {
|
|
58
|
+
query: entry.query,
|
|
59
|
+
startIndex: i,
|
|
60
|
+
endIndex: i,
|
|
61
|
+
count: 1,
|
|
62
|
+
entries: [entry]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
groups.push(currentGroup);
|
|
67
|
+
return groups;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Format a single query entry for display
|
|
71
|
+
*/
|
|
72
|
+
function formatQueryEntry(entry, index) {
|
|
73
|
+
const duration = entry.duration ? ` (${entry.duration}ms)` : '';
|
|
74
|
+
const params = entry.params && entry.params.length > 0
|
|
75
|
+
? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
|
|
76
|
+
: '';
|
|
77
|
+
return ` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format a group of queries for display, collapsing repetitive queries
|
|
81
|
+
*/
|
|
82
|
+
function formatQueryGroup(group) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
const queryPreview = group.query.split('\n')[0].trim();
|
|
85
|
+
if (group.count === 1) {
|
|
86
|
+
// Single query, format normally
|
|
87
|
+
lines.push(formatQueryEntry(group.entries[0], group.startIndex));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Multiple consecutive identical queries, collapse them
|
|
91
|
+
const isPgpmDeploy = queryPreview.includes('pgpm_migrate.deploy');
|
|
92
|
+
// Show range and count
|
|
93
|
+
lines.push(` ${group.startIndex + 1}-${group.endIndex + 1}. ${queryPreview} (${group.count} calls)`);
|
|
94
|
+
// For pgpm_migrate.deploy, show first and last change names for context
|
|
95
|
+
if (isPgpmDeploy) {
|
|
96
|
+
const firstChange = extractDeployChangeName(group.entries[0].params);
|
|
97
|
+
const lastChange = extractDeployChangeName(group.entries[group.entries.length - 1].params);
|
|
98
|
+
if (firstChange) {
|
|
99
|
+
lines.push(` First: ${firstChange}`);
|
|
100
|
+
}
|
|
101
|
+
if (lastChange && lastChange !== firstChange) {
|
|
102
|
+
lines.push(` Last: ${lastChange}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// For other queries, show first and last params
|
|
107
|
+
const firstParams = group.entries[0].params;
|
|
108
|
+
const lastParams = group.entries[group.entries.length - 1].params;
|
|
109
|
+
if (firstParams && firstParams.length > 0) {
|
|
110
|
+
lines.push(` First params: ${JSON.stringify(firstParams.slice(0, 2))}${firstParams.length > 2 ? '...' : ''}`);
|
|
111
|
+
}
|
|
112
|
+
if (lastParams && lastParams.length > 0 && JSON.stringify(lastParams) !== JSON.stringify(firstParams)) {
|
|
113
|
+
lines.push(` Last params: ${JSON.stringify(lastParams.slice(0, 2))}${lastParams.length > 2 ? '...' : ''}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Format query history with smart collapsing and limiting
|
|
121
|
+
*/
|
|
122
|
+
function formatQueryHistory(history) {
|
|
123
|
+
if (history.length === 0)
|
|
124
|
+
return [];
|
|
125
|
+
// In verbose mode, show everything without collapsing
|
|
126
|
+
if (errorConfig.verbose) {
|
|
127
|
+
return history.map((entry, index) => formatQueryEntry(entry, index));
|
|
128
|
+
}
|
|
129
|
+
// Group consecutive identical queries
|
|
130
|
+
const groups = groupConsecutiveQueries(history);
|
|
131
|
+
// Apply limit - keep last N queries worth of groups
|
|
132
|
+
let totalQueries = 0;
|
|
133
|
+
let startGroupIndex = groups.length;
|
|
134
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
135
|
+
totalQueries += groups[i].count;
|
|
136
|
+
if (totalQueries > errorConfig.queryHistoryLimit) {
|
|
137
|
+
startGroupIndex = i + 1;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
startGroupIndex = i;
|
|
141
|
+
}
|
|
142
|
+
const lines = [];
|
|
143
|
+
// If we're truncating, show how many were omitted
|
|
144
|
+
if (startGroupIndex > 0) {
|
|
145
|
+
let omittedCount = 0;
|
|
146
|
+
for (let i = 0; i < startGroupIndex; i++) {
|
|
147
|
+
omittedCount += groups[i].count;
|
|
148
|
+
}
|
|
149
|
+
lines.push(` ... (${omittedCount} earlier queries omitted, set PGPM_ERROR_VERBOSE=true to see all)`);
|
|
150
|
+
}
|
|
151
|
+
// Format the remaining groups
|
|
152
|
+
for (let i = startGroupIndex; i < groups.length; i++) {
|
|
153
|
+
lines.push(...formatQueryGroup(groups[i]));
|
|
154
|
+
}
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Truncate error output if it exceeds the max length
|
|
159
|
+
*/
|
|
160
|
+
function truncateErrorOutput(lines) {
|
|
161
|
+
if (errorConfig.verbose)
|
|
162
|
+
return lines;
|
|
163
|
+
const joined = lines.join('\n');
|
|
164
|
+
if (joined.length <= errorConfig.maxLength)
|
|
165
|
+
return lines;
|
|
166
|
+
// Truncate and add notice
|
|
167
|
+
const truncated = joined.slice(0, errorConfig.maxLength);
|
|
168
|
+
const lastNewline = truncated.lastIndexOf('\n');
|
|
169
|
+
const cleanTruncated = lastNewline > 0 ? truncated.slice(0, lastNewline) : truncated;
|
|
170
|
+
return [
|
|
171
|
+
...cleanTruncated.split('\n'),
|
|
172
|
+
`... (output truncated at ${errorConfig.maxLength} chars, total was ${joined.length} chars)`,
|
|
173
|
+
` Set PGPM_ERROR_VERBOSE=true to see full output`
|
|
174
|
+
];
|
|
175
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truncateErrorOutput = exports.formatQueryHistory = void 0;
|
|
3
4
|
exports.withTransaction = withTransaction;
|
|
4
5
|
exports.executeQuery = executeQuery;
|
|
5
6
|
const logger_1 = require("@pgpmjs/logger");
|
|
6
7
|
const types_1 = require("@pgpmjs/types");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
// Re-export error formatting functions for backward compatibility
|
|
10
|
+
var errors_2 = require("./errors");
|
|
11
|
+
Object.defineProperty(exports, "formatQueryHistory", { enumerable: true, get: function () { return errors_2.formatQueryHistory; } });
|
|
12
|
+
Object.defineProperty(exports, "truncateErrorOutput", { enumerable: true, get: function () { return errors_2.truncateErrorOutput; } });
|
|
7
13
|
const log = new logger_1.Logger('migrate:transaction');
|
|
8
14
|
/**
|
|
9
15
|
* Execute a function within a transaction context
|
|
@@ -64,16 +70,11 @@ async function withTransaction(pool, options, fn) {
|
|
|
64
70
|
errorLines.push(...fieldLines);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
|
-
// Log query history for debugging
|
|
73
|
+
// Log query history for debugging (with smart collapsing and limiting)
|
|
68
74
|
if (queryHistory.length > 0) {
|
|
69
75
|
errorLines.push('Query history for this transaction:');
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const params = entry.params && entry.params.length > 0
|
|
73
|
-
? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
|
|
74
|
-
: '';
|
|
75
|
-
errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
|
|
76
|
-
});
|
|
76
|
+
const historyLines = (0, errors_1.formatQueryHistory)(queryHistory);
|
|
77
|
+
errorLines.push(...historyLines);
|
|
77
78
|
}
|
|
78
79
|
// For transaction aborted errors, provide additional context
|
|
79
80
|
if (error.code === '25P02') {
|
|
@@ -81,8 +82,9 @@ async function withTransaction(pool, options, fn) {
|
|
|
81
82
|
errorLines.push(' This usually means a previous command in the transaction failed.');
|
|
82
83
|
errorLines.push(' Check the query history above to identify the failing command.');
|
|
83
84
|
}
|
|
84
|
-
//
|
|
85
|
-
|
|
85
|
+
// Apply total output length limit and log the consolidated error message
|
|
86
|
+
const finalLines = (0, errors_1.truncateErrorOutput)(errorLines);
|
|
87
|
+
log.error(finalLines.join('\n'));
|
|
86
88
|
throw error;
|
|
87
89
|
}
|
|
88
90
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpmjs/core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PGPM Package and Migration Tools",
|
|
6
6
|
"main": "index.js",
|
|
@@ -48,21 +48,21 @@
|
|
|
48
48
|
"makage": "^0.1.10"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@pgpmjs/env": "^2.9.
|
|
51
|
+
"@pgpmjs/env": "^2.9.4",
|
|
52
52
|
"@pgpmjs/logger": "^1.3.7",
|
|
53
|
-
"@pgpmjs/server-utils": "^2.8.
|
|
54
|
-
"@pgpmjs/types": "^2.14.
|
|
53
|
+
"@pgpmjs/server-utils": "^2.8.15",
|
|
54
|
+
"@pgpmjs/types": "^2.14.1",
|
|
55
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",
|
|
59
59
|
"parse-package-name": "^1.0.0",
|
|
60
60
|
"pg": "^8.16.3",
|
|
61
|
-
"pg-cache": "^1.6.
|
|
61
|
+
"pg-cache": "^1.6.15",
|
|
62
62
|
"pg-env": "^1.2.5",
|
|
63
63
|
"pgsql-deparser": "^17.17.2",
|
|
64
64
|
"pgsql-parser": "^17.9.11",
|
|
65
65
|
"yanse": "^0.1.11"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "15e8f92ee6ec7bfebd6647dec743e4235b0362ac"
|
|
68
68
|
}
|