From a076caa3cb9dd3d081abc8c789490644275e283a Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Wed, 28 Jan 2026 07:13:25 -0800 Subject: [PATCH 1/2] feat: add --no-comments flag to dump command (#262) Add CLI option to suppress object comment headers (-- Name: ...; Type: ...) in dump output while retaining the dump header. This enables users to get pure DDL output without post-processing. Co-Authored-By: Claude Opus 4.5 --- cmd/dump/dump.go | 54 +++++++++++--------- cmd/dump/dump_test.go | 102 +++++++++++++++++++++++++++++++++++++ cmd/dump/multifile_test.go | 4 +- internal/dump/formatter.go | 18 +++++-- 4 files changed, 146 insertions(+), 32 deletions(-) diff --git a/cmd/dump/dump.go b/cmd/dump/dump.go index dbce9052..e43e323e 100644 --- a/cmd/dump/dump.go +++ b/cmd/dump/dump.go @@ -12,26 +12,28 @@ import ( ) var ( - host string - port int - db string - user string - password string - schema string - multiFile bool - file string + host string + port int + db string + user string + password string + schema string + multiFile bool + file string + noComments bool ) // DumpConfig holds configuration for dump execution type DumpConfig struct { - Host string - Port int - DB string - User string - Password string - Schema string - MultiFile bool - File string + Host string + Port int + DB string + User string + Password string + Schema string + MultiFile bool + File string + NoComments bool } var DumpCmd = &cobra.Command{ @@ -52,6 +54,7 @@ func init() { DumpCmd.Flags().StringVar(&schema, "schema", "public", "Schema name to dump (default: public)") DumpCmd.Flags().BoolVar(&multiFile, "multi-file", false, "Output schema to multiple files organized by object type") DumpCmd.Flags().StringVar(&file, "file", "", "Output file path (required when --multi-file is used)") + DumpCmd.Flags().BoolVar(&noComments, "no-comments", false, "Do not output object comment headers") } // ExecuteDump executes the dump operation with the given configuration @@ -82,7 +85,7 @@ func ExecuteDump(config *DumpConfig) (string, error) { diffs := diff.GenerateMigration(emptyIR, schemaIR, config.Schema) // Create dump formatter - formatter := dump.NewDumpFormatter(schemaIR.Metadata.DatabaseVersion, config.Schema) + formatter := dump.NewDumpFormatter(schemaIR.Metadata.DatabaseVersion, config.Schema, config.NoComments) if config.MultiFile { // Multi-file mode - output to files @@ -109,14 +112,15 @@ func runDump(cmd *cobra.Command, args []string) error { // Create config from command-line flags config := &DumpConfig{ - Host: host, - Port: port, - DB: db, - User: user, - Password: finalPassword, - Schema: schema, - MultiFile: multiFile, - File: file, + Host: host, + Port: port, + DB: db, + User: user, + Password: finalPassword, + Schema: schema, + MultiFile: multiFile, + File: file, + NoComments: noComments, } // Execute dump diff --git a/cmd/dump/dump_test.go b/cmd/dump/dump_test.go index 28b79da6..3d9a63a0 100644 --- a/cmd/dump/dump_test.go +++ b/cmd/dump/dump_test.go @@ -2,9 +2,13 @@ package dump import ( "os" + "strings" "testing" "github.com/pgschema/pgschema/cmd/util" + "github.com/pgschema/pgschema/internal/diff" + "github.com/pgschema/pgschema/internal/dump" + "github.com/pgschema/pgschema/ir" "github.com/spf13/cobra" ) @@ -288,3 +292,101 @@ func TestDumpCommand_PgpassFile(t *testing.T) { t.Error("Expected error with unreachable database, but got nil") } } + +func TestDumpCommand_NoCommentsFlag(t *testing.T) { + // Test that the --no-comments flag is defined + flags := DumpCmd.Flags() + noCommentsFlag := flags.Lookup("no-comments") + if noCommentsFlag == nil { + t.Error("Expected --no-comments flag to be defined") + } + + // Verify default value is false + if noCommentsFlag.DefValue != "false" { + t.Errorf("Expected --no-comments default to be 'false', got '%s'", noCommentsFlag.DefValue) + } +} + +func TestNoComments_SingleFile(t *testing.T) { + // Create test diffs + diffs := []diff.Diff{ + { + Statements: []diff.SQLStatement{ + { + SQL: "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL);", + CanRunInTransaction: true, + }, + }, + Type: diff.DiffTypeTable, + Operation: diff.DiffOperationCreate, + Path: "public.users", + Source: &ir.Table{ + Name: "users", + }, + }, + { + Statements: []diff.SQLStatement{ + { + SQL: "COMMENT ON TABLE users IS 'User accounts';", + CanRunInTransaction: true, + }, + }, + Type: diff.DiffTypeComment, + Operation: diff.DiffOperationCreate, + Path: "public.users", + Source: &ir.Table{ + Name: "users", + }, + }, + } + + t.Run("WithComments", func(t *testing.T) { + formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public", false) + output := formatter.FormatSingleFile(diffs) + + // Should contain dump header + if !strings.Contains(output, "-- pgschema database dump") { + t.Error("Expected output to contain dump header") + } + + // Should contain object comment header + if !strings.Contains(output, "-- Name: users; Type: TABLE") { + t.Error("Expected output to contain object comment header") + } + + // Should contain DDL + if !strings.Contains(output, "CREATE TABLE users") { + t.Error("Expected output to contain DDL") + } + + // Should contain COMMENT ON statement (this is schema content, not commentary) + if !strings.Contains(output, "COMMENT ON TABLE users") { + t.Error("Expected output to contain COMMENT ON statement") + } + }) + + t.Run("NoComments", func(t *testing.T) { + formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public", true) + output := formatter.FormatSingleFile(diffs) + + // Should still contain dump header (retained per design) + if !strings.Contains(output, "-- pgschema database dump") { + t.Error("Expected output to contain dump header even with --no-comments") + } + + // Should NOT contain object comment header + if strings.Contains(output, "-- Name: users; Type: TABLE") { + t.Error("Expected output to NOT contain object comment header with --no-comments") + } + + // Should still contain DDL + if !strings.Contains(output, "CREATE TABLE users") { + t.Error("Expected output to contain DDL") + } + + // Should still contain COMMENT ON statement (this is schema content) + if !strings.Contains(output, "COMMENT ON TABLE users") { + t.Error("Expected output to contain COMMENT ON statement") + } + }) +} diff --git a/cmd/dump/multifile_test.go b/cmd/dump/multifile_test.go index 347b8baa..fb9cacd4 100644 --- a/cmd/dump/multifile_test.go +++ b/cmd/dump/multifile_test.go @@ -77,7 +77,7 @@ func TestCreateMultiFileOutput(t *testing.T) { } // Test the FormatMultiFile function - formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public") + formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public", false) err := formatter.FormatMultiFile(diffs, outputPath) if err != nil { t.Fatalf("FormatMultiFile failed: %v", err) @@ -154,7 +154,7 @@ func TestCreateMultiFileOutput(t *testing.T) { func TestDumpFormatterHelpers(t *testing.T) { // Create a formatter instance for testing helper methods - formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public") + formatter := dump.NewDumpFormatter("PostgreSQL 17.0", "public", false) // Test getObjectDirectory through the formatter testObjectDirectories := []struct { diff --git a/internal/dump/formatter.go b/internal/dump/formatter.go index 00aa8b24..68f3cd6c 100644 --- a/internal/dump/formatter.go +++ b/internal/dump/formatter.go @@ -16,13 +16,15 @@ import ( type DumpFormatter struct { dbVersion string targetSchema string + noComments bool } // NewDumpFormatter creates a new DumpFormatter -func NewDumpFormatter(dbVersion string, targetSchema string) *DumpFormatter { +func NewDumpFormatter(dbVersion string, targetSchema string, noComments bool) *DumpFormatter { return &DumpFormatter{ dbVersion: dbVersion, targetSchema: targetSchema, + noComments: noComments, } } @@ -46,8 +48,12 @@ func (f *DumpFormatter) FormatSingleFile(diffs []diff.Diff) string { output.WriteString("\n") } } else { - // Add object comment header - output.WriteString(f.formatObjectCommentHeader(step)) + // Add object comment header (unless --no-comments is set) + if !f.noComments { + output.WriteString(f.formatObjectCommentHeader(step)) + } else if i > 0 { + output.WriteString("\n") // Add separator between statements + } // Add the SQL statements for _, stmt := range step.Statements { @@ -195,8 +201,10 @@ func (f *DumpFormatter) writeObjectFile(filePath string, diffs []diff.Diff) erro } } - // Add object comment header - file.WriteString(f.formatObjectCommentHeader(step)) + // Add object comment header (unless --no-comments is set) + if !f.noComments { + file.WriteString(f.formatObjectCommentHeader(step)) + } // Print the SQL statements for _, stmt := range step.Statements { From 8e785b49cf77fda7e4c879d19eeeb720bb6130b3 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Wed, 28 Jan 2026 07:17:44 -0800 Subject: [PATCH 2/2] docs: add --no-comments flag to dump command documentation Co-Authored-By: Claude Opus 4.5 --- docs/cli/dump.mdx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/cli/dump.mdx b/docs/cli/dump.mdx index b610c3ea..ca3c68f4 100644 --- a/docs/cli/dump.mdx +++ b/docs/cli/dump.mdx @@ -137,11 +137,17 @@ pgschema apply --host staging-host --db myapp --user postgres --file current.sql Output file path (required when --multi-file is used) - + For single-file mode, this is optional (defaults to stdout). For multi-file mode, this specifies the main file path. + + Do not output object comment headers (e.g., `-- Name: users; Type: TABLE; Schema: -; Owner: -`). + + The dump header with pgschema version information is retained. This option is useful when you need pure DDL output without per-object commentary. + + ## Ignoring Objects You can exclude specific database objects from dumps using a `.pgschemaignore` file. See [Ignore (.pgschemaignore)](/cli/ignore) for complete documentation.