Skip to content

Commit 0d28876

Browse files
committed
feat(pgpm): loud compatibility warnings for COPY data blocks and non-preamble psql meta-commands in pgpm import
1 parent ba31717 commit 0d28876

4 files changed

Lines changed: 71 additions & 2 deletions

File tree

pgpm/cli/src/commands/import.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
dumpCompatibilityWarnings,
23
EXPORT_GRANULARITIES,
34
ExportGranularity,
45
importDumpRows,
@@ -129,6 +130,10 @@ export default async (
129130
log.info(`concatenated ${source.files.length} .sql files in sorted order`);
130131
}
131132

133+
for (const warning of dumpCompatibilityWarnings(source)) {
134+
console.warn(`\nWARNING: ${warning}\n`);
135+
}
136+
132137
const result = await importDumpRows(source, { granularity, naming, withData });
133138

134139
let packages: { name: string; requires: string[]; rows: typeof result.rows }[];

pgpm/export/__tests__/dump-source.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as fs from 'fs';
22
import * as os from 'os';
33
import * as path from 'path';
44

5-
import { copyBlockToInsert, copyTargetOf, loadDumpSource, preprocessDumpText } from '../src/dump-source';
5+
import { copyBlockToInsert, copyTargetOf, dumpCompatibilityWarnings, loadDumpSource, preprocessDumpText } from '../src/dump-source';
66

77
describe('preprocessDumpText', () => {
88
it('strips psql backslash meta-commands and reports them', () => {
@@ -125,3 +125,34 @@ describe('copyTargetOf / copyBlockToInsert', () => {
125125
expect(copyBlockToInsert({ statement: 'COPY app.t (id) FROM stdin;', dataLines: [] })).toBe('');
126126
});
127127
});
128+
129+
describe('dumpCompatibilityWarnings', () => {
130+
it('warns loudly about COPY data blocks and recommends re-dump flags', () => {
131+
const warnings = dumpCompatibilityWarnings({
132+
copyBlocks: [{ statement: 'COPY app.t (id) FROM stdin;', dataLines: ['1'] }],
133+
metaCommands: []
134+
});
135+
expect(warnings).toHaveLength(1);
136+
expect(warnings[0]).toContain('NOT SQL');
137+
expect(warnings[0]).toContain('pg_dump --schema-only');
138+
expect(warnings[0]).toContain('pg_dump --inserts');
139+
});
140+
141+
it('warns about non-preamble psql meta-commands', () => {
142+
const warnings = dumpCompatibilityWarnings({
143+
copyBlocks: [],
144+
metaCommands: ['\\i other.sql']
145+
});
146+
expect(warnings).toHaveLength(1);
147+
expect(warnings[0]).toContain('\\i');
148+
});
149+
150+
it('is silent for a clean schema-only dump with only expected preamble', () => {
151+
expect(
152+
dumpCompatibilityWarnings({
153+
copyBlocks: [],
154+
metaCommands: ['\\restrict abc', '\\unrestrict abc', '\\connect db']
155+
})
156+
).toEqual([]);
157+
});
158+
});

pgpm/export/src/dump-source.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,39 @@ export const loadDumpSource = (inputPath: string): DumpSource => {
133133
return { files, ...preprocessDumpText(text) };
134134
};
135135

136+
/** psql meta-commands pg_dump always emits as preamble — expected noise. */
137+
const EXPECTED_META = /^\\(restrict|unrestrict|connect)\b/;
138+
139+
/**
140+
* Loud compatibility warnings for dump constructs we handle only best-effort
141+
* because they are not SQL and cannot go through the real parser: COPY stdin
142+
* data blocks and non-preamble psql meta-commands. The reliable path is to
143+
* hand `pgpm import` pure parseable SQL.
144+
*/
145+
export const dumpCompatibilityWarnings = (
146+
source: Pick<DumpSource, 'copyBlocks' | 'metaCommands'>
147+
): string[] => {
148+
const warnings: string[] = [];
149+
if (source.copyBlocks.length > 0) {
150+
warnings.push(
151+
`dump contains ${source.copyBlocks.length} COPY ... FROM stdin data block(s). ` +
152+
`COPY data is NOT SQL and is handled by a best-effort text scanner — data could be missed or truncated. ` +
153+
`For a guaranteed import, re-dump with:\n` +
154+
` pg_dump --schema-only (schema only; recommended)\n` +
155+
` pg_dump --inserts (data as parseable INSERT statements, for --with-data)`
156+
);
157+
}
158+
const unexpected = source.metaCommands.filter(cmd => !EXPECTED_META.test(cmd));
159+
if (unexpected.length > 0) {
160+
warnings.push(
161+
`dump contains ${unexpected.length} psql meta-command(s) beyond the standard pg_dump preamble ` +
162+
`(e.g. ${unexpected[0].split(/\s/)[0]}). These are client-side psql commands, not SQL — they are stripped ` +
163+
`and will NOT be part of the module. Feed pgpm import plain SQL (e.g. pg_dump output), not psql scripts.`
164+
);
165+
}
166+
return warnings;
167+
};
168+
136169
const COPY_TARGET = /^COPY\s+(.+?)(?:\s*\(([^)]*)\))?\s+FROM\s+stdin\s*;\s*$/i;
137170

138171
/** The parsed target of a COPY statement. */

pgpm/export/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export type { CatalogQueryable, CatalogSnapshot } from './catalog-check';
22
export { diffCatalogSnapshots, snapshotCatalog } from './catalog-check';
33
export type { CopyBlock, CopyTarget, DumpSource } from './dump-source';
4-
export { copyBlockToInsert, copyTargetOf, loadDumpSource, preprocessDumpText } from './dump-source';
4+
export { copyBlockToInsert, copyTargetOf, dumpCompatibilityWarnings, loadDumpSource, preprocessDumpText } from './dump-source';
55
export * from './export-data';
66
export * from './export-graphql';
77
export * from './export-graphql-meta';

0 commit comments

Comments
 (0)