From 84571c2d0e827605b8801d47da2c9d7cbf113d2e Mon Sep 17 00:00:00 2001 From: Kareem Janoudi Date: Mon, 30 Mar 2026 13:38:48 -0500 Subject: [PATCH] Add --skip-privileges flag to skip table privilege diffing Add WithSkipTablePrivileges() plan option and --skip-privileges CLI flag that strips table privileges from both schemas before diffing. This allows plan generation to succeed when partitioned tables have privileges, which otherwise fails with "privileges on partitions: not implemented". Fixes #269 --- cmd/pg-schema-diff/plan_cmd.go | 6 ++++++ pkg/diff/plan_generator.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/cmd/pg-schema-diff/plan_cmd.go b/cmd/pg-schema-diff/plan_cmd.go index 6c65722..43d4bfe 100644 --- a/cmd/pg-schema-diff/plan_cmd.go +++ b/cmd/pg-schema-diff/plan_cmd.go @@ -117,6 +117,7 @@ type ( dataPackNewTables bool disablePlanValidation bool noConcurrentIndexOps bool + skipPrivileges bool statementTimeoutModifiers []string lockTimeoutModifiers []string @@ -227,6 +228,8 @@ func createPlanOptionsFlags(cmd *cobra.Command) *planOptionsFlags { "database with an identical schema to the original, asserting that the generated plan actually migrates the schema to the desired target.") cmd.Flags().BoolVar(&flags.noConcurrentIndexOps, "no-concurrent-index-ops", false, "If set, will disable the use of CONCURRENTLY in CREATE INDEX and DROP INDEX statements. "+ "This may result in longer lock times and potential downtime during migrations.") + cmd.Flags().BoolVar(&flags.skipPrivileges, "skip-privileges", false, "If set, will skip diffing of table privileges (GRANT/REVOKE). "+ + "This is useful when privileges on partitioned tables cause plan generation to fail.") timeoutModifierFlagVar(cmd, &flags.statementTimeoutModifiers, "statement", "t") timeoutModifierFlagVar(cmd, &flags.lockTimeoutModifiers, "lock", "l") @@ -329,6 +332,9 @@ func parsePlanOptions(p planOptionsFlags) (planOptions, error) { if p.noConcurrentIndexOps { opts = append(opts, diff.WithNoConcurrentIndexOps()) } + if p.skipPrivileges { + opts = append(opts, diff.WithSkipTablePrivileges()) + } var statementTimeoutModifiers []timeoutModifier for _, s := range p.statementTimeoutModifiers { diff --git a/pkg/diff/plan_generator.go b/pkg/diff/plan_generator.go index 2c2c92b..d0d2b24 100644 --- a/pkg/diff/plan_generator.go +++ b/pkg/diff/plan_generator.go @@ -36,6 +36,7 @@ type ( getSchemaOpts []schema.GetSchemaOpt randReader io.Reader noConcurrentIndexOps bool + skipTablePrivileges bool } PlanOpt func(opts *planOptions) @@ -113,6 +114,16 @@ func WithNoConcurrentIndexOps() PlanOpt { } } +// WithSkipTablePrivileges configures plan generation to ignore table privilege differences. +// This is useful when privileges on partitioned tables cause plan generation to fail with +// "privileges on partitions: not implemented", or when privilege changes should not be +// included in the migration plan. +func WithSkipTablePrivileges() PlanOpt { + return func(opts *planOptions) { + opts.skipTablePrivileges = true + } +} + // Generate generates a migration plan to migrate the database to the target schema // // Parameters: @@ -153,6 +164,11 @@ func Generate( return Plan{}, fmt.Errorf("getting new schema: %w", err) } + if planOptions.skipTablePrivileges { + currentSchema = clearTablePrivileges(currentSchema) + newSchema = clearTablePrivileges(newSchema) + } + statements, err := generateMigrationStatements(currentSchema, newSchema, planOptions) if err != nil { return Plan{}, fmt.Errorf("generating plan statements: %w", err)