Repository navigation
fix: emit partition attach/detach when bounds or attachment change in plan - #660
shivampwm2020 wants to merge 1 commit into
Conversation
… 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
[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
SummaryThe PR detects changes to partition parents and bounds and emits detach and attach statements.
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]
Reviews (1) · Last reviewed commit: "fix: emit partition attach/detach when b..." · Reviewed by Greptile |
| 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)) |
There was a problem hiding this comment.
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
| 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)) |
There was a problem hiding this comment.
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:
| 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)) |
There was a problem hiding this comment.
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
Problem
pgschema planreports "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
diffTablesininternal/diff/table.gobuilds partitions correctly on the CREATE path (PARTITION OF parent <bound>), but the table-diff comparator never comparesPartitionOf,PartitionOfSchema, orPartitionBound. With no other column/index/constraint differences, thehasChangesguard returnsnil, so the change is dropped and the plan is empty.Fix
Follow the existing table-level structural-change idiom already used for persistence (
PersistenceChanged):PartitionOfChangedflag (plusOldPartitionOf/OldPartitionOfSchemato name the previous parent) totableDiff.diffTableswhen the parent, parent schema, or bound differs, and include it in thehasChangesguard so a non-nil diff is produced.generateAlterTableStatements, emitALTER 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:
Red-before / green-after: with the fix stashed (unmodified source, new test only),
TestPartitionBoundChangeReattachesfails withchanged 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