Skip to content

fix: emit partition attach/detach when bounds or attachment change in plan - #660

Open
shivampwm2020 wants to merge 1 commit into
pgplex:mainfrom
shivampwm2020:fix/639-partition-bound-diff
Open

shivampwm2020 wants to merge 1 commit into
pgplex:mainfrom
shivampwm2020:fix/639-partition-bound-diff

Conversation

@shivampwm2020

Copy link
Copy Markdown

Problem

pgschema plan reports "No changes detected" when a partition's bound changes or when a partition is detached, even though the desired schema differs from the live one. The plan emits no SQL for the change.

Root cause

diffTables in internal/diff/table.go builds partitions correctly on the CREATE path (PARTITION OF parent <bound>), but the table-diff comparator never compares PartitionOf, PartitionOfSchema, or PartitionBound. With no other column/index/constraint differences, the hasChanges guard returns nil, so the change is dropped and the plan is empty.

Fix

Follow the existing table-level structural-change idiom already used for persistence (PersistenceChanged):

  • Add a PartitionOfChanged flag (plus OldPartitionOf/OldPartitionOfSchema to name the previous parent) to tableDiff.
  • Set it in diffTables when the parent, parent schema, or bound differs, and include it in the hasChanges guard so a non-nil diff is produced.
  • In generateAlterTableStatements, emit ALTER TABLE <old parent> DETACH PARTITION <child>; and, when the table is still a partition, ALTER TABLE <new parent> ATTACH PARTITION <child> <bound>;. Postgres cannot alter a bound in place, so detach + re-attach is the correct pair. A table that is no longer a partition is detached and left standalone.

Testing

Real commands run locally against the branch:

$ go build ./...
$ go test ./internal/diff/ -run 'TestPartitionBound|TestPartitionDetach' -v
=== RUN   TestPartitionBoundChangeReattaches
--- PASS: TestPartitionBoundChangeReattaches (0.00s)
=== RUN   TestPartitionDetachEmitsStatement
--- PASS: TestPartitionDetachEmitsStatement (0.00s)
=== RUN   TestPartitionBoundUnchangedNoPlan
--- PASS: TestPartitionBoundUnchangedNoPlan (0.00s)
PASS
ok      github.com/pgplex/pgschema/internal/diff        18.832s

$ go test ./internal/diff/ ./cmd/plan/
ok      github.com/pgplex/pgschema/internal/diff        30.870s
ok      github.com/pgplex/pgschema/cmd/plan     63.124s

Red-before / green-after: with the fix stashed (unmodified source, new test only), TestPartitionBoundChangeReattaches fails with changed partition bound produced an empty plan — the exact symptom reported in the issue.

Tests added in internal/diff/partition_bound_test.go (pure IR-level, no database required): a bound change re-attaches, a detach emits a statement, and an unchanged partition still plans nothing.

Not run: integration tests that need Postgres/embedded-postgres; the diff layer is exercised directly without a database.

Fixes #639

… plan

diffTables never compared PartitionOf/PartitionOfSchema/PartitionBound,
so a re-bounded or detached partition produced an empty plan. Mirror the
existing table-level structural-change idiom (PersistenceChanged): flag
the change, force a non-nil table diff, and emit DETACH + ATTACH
PARTITION so the desired bound is applied. A table that is no longer a
partition is detached and left standalone.

Fixes pgplex#639
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 2/5

[High impact] Changes how database schema migrations detect and emit partition changes.

Do not merge until partition changes are ordered safely around sibling bounds, parent drops, and column changes.

Findings

  1. P1 Sibling bounds overlap during apply ▶
  2. P1 Parent drops remove retained children ▶
  3. P1 Attach runs before columns match ▶

Summary

The PR detects changes to partition parents and bounds and emits detach and attach statements.

  • Partition changes now produce detach and re-attach SQL in migration plans.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Compare parent and bound] --> B[Record partition change]
  B --> C[Drop removed tables]
  C --> D[Create new tables]
  D --> E[Detach child]
  E --> F[Attach child]
  F --> G[Change child columns and constraints]
Loading

Reviews (1) · Last reviewed commit: "fix: emit partition attach/detach when b..." · Reviewed by Greptile

Comment thread internal/diff/table.go
Comment on lines +1293 to +1305
if td.Table.PartitionOf != "" && td.Table.PartitionBound != "" {
parentSchema := td.Table.PartitionOfSchema
if parentSchema == "" {
parentSchema = td.Table.Schema
}
parentName := ir.QualifyEntityNameWithQuotesMode(parentSchema, td.Table.PartitionOf, targetSchema, collector.qualifySchema)
collector.collect(&diffContext{
Type: DiffTypeTable,
Operation: DiffOperationAlter,
Path: fmt.Sprintf("%s.%s", td.Table.Schema, td.Table.Name),
Source: td.Table,
CanRunInTransaction: true,
}, fmt.Sprintf("ALTER TABLE %s ATTACH PARTITION %s %s;", parentName, tableName, td.Table.PartitionBound))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Sibling bounds overlap during apply

Re-attaching each child immediately after detaching it breaks changes that move a boundary between siblings. Changing a from [0,10) to [0,15) and b from [10,20) to [15,20) attaches a while b still occupies [10,20). PostgreSQL rejects the overlap, even with empty tables.

New partitions created in space freed by a changed partition also fail because creates run before these detaches. Emit all required detaches before creating or re-attaching partitions.

Knowledge Base Used: Migration dependency ordering

Comment thread internal/diff/table.go
Comment on lines +1276 to +1288
if td.OldPartitionOf != "" {
oldSchema := td.OldPartitionOfSchema
if oldSchema == "" {
oldSchema = td.Table.Schema
}
oldParent := ir.QualifyEntityNameWithQuotesMode(oldSchema, td.OldPartitionOf, targetSchema, collector.qualifySchema)
collector.collect(&diffContext{
Type: DiffTypeTable,
Operation: DiffOperationAlter,
Path: fmt.Sprintf("%s.%s", td.Table.Schema, td.Table.Name),
Source: td.Table,
CanRunInTransaction: true,
}, fmt.Sprintf("ALTER TABLE %s DETACH PARTITION %s;", oldParent, tableName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Parent drops remove retained children

The detach runs too late when the desired schema removes the old parent but keeps the child standalone or moves it to another parent. The drop phase first runs DROP TABLE ... CASCADE on the old parent, deleting its still-attached child and rows. This later detach then fails because the parent no longer exists.

A transaction rollback may restore the child, but the plan cannot perform the requested change. Detach retained children before dropping their old parents.

Knowledge Base Used:

Comment thread internal/diff/table.go
Path: fmt.Sprintf("%s.%s", td.Table.Schema, td.Table.Name),
Source: td.Table,
CanRunInTransaction: true,
}, fmt.Sprintf("ALTER TABLE %s ATTACH PARTITION %s %s;", parentName, tableName, td.Table.PartitionBound))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Attach runs before columns match

The new attach runs before the child's column changes. If an existing standalone table needs a column added or its type changed to match the parent, PostgreSQL rejects ATTACH PARTITION before the later statements can make it compatible. For example, an empty child containing only id cannot attach to a parent containing id and payload, even though this plan adds payload afterward.

Keep detach early, but defer attach until the child and parent have their desired columns and constraints.

Knowledge Base Used: Schema object diff generation

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plan ignores partition bounds and attachment: a re-bounded or detached partition gives No changes detected

1 participant