Repository navigation
capabilities: mark rows migrate refuses as refused - #188
Conversation
The matrix published attach/detach partition and drop/reindex as migrate-supported, but migrate refuses all four. A guard test now runs every migrate-supported row through migrate.Run.
Keep drop-index as refused by migrate and fold in main's replica-identity refusals in its notes and in limitations; regenerate the capabilities page from the merged matrix.
Regenerate the capabilities page from the merged matrix, which now also carries main's add-column rewrite rows.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Review 1/2 (adversarial correctness) of I read the whole diff (YAML, generated Markdown, Verdict: the four flips are correct. Under default options
BlockingNone. Non-blocking1. Nothing guards the reverse direction, and two rows already published a reason the engine does not emit. The new test proves
Test that passes for the four rows this PR flips, fails on the two drifted rows at 8d52f94, and kills the wrong-token mutant// migrateRefusals maps rows published as `migrate: refused` to statements the
// migrate front door must refuse with the row's refusal_reason.
var migrateRefusals = map[string]migrateClaim{
"attach-partition": {
setup: `CREATE TABLE %[1]s.events (id bigint NOT NULL, PRIMARY KEY (id)) PARTITION BY RANGE (id);
CREATE TABLE %[1]s.events_low (id bigint NOT NULL, PRIMARY KEY (id))`,
statements: []string{`ALTER TABLE %[1]s.events ATTACH PARTITION %[1]s.events_low FOR VALUES FROM (0) TO (1000)`},
},
"detach-partition-concurrently": {
setup: `CREATE TABLE %[1]s.events (id bigint NOT NULL, PRIMARY KEY (id)) PARTITION BY RANGE (id);
CREATE TABLE %[1]s.events_low PARTITION OF %[1]s.events FOR VALUES FROM (0) TO (1000)`,
statements: []string{
`ALTER TABLE %[1]s.events DETACH PARTITION %[1]s.events_low`,
`ALTER TABLE %[1]s.events DETACH PARTITION %[1]s.events_low CONCURRENTLY`,
},
},
"drop-index": {
setup: `CREATE TABLE %[1]s.accounts (id bigint PRIMARY KEY, email text NOT NULL);
CREATE INDEX accounts_email_idx ON %[1]s.accounts (email)`,
statements: []string{
`DROP INDEX %[1]s.accounts_email_idx`,
`DROP INDEX CONCURRENTLY %[1]s.accounts_email_idx`,
},
},
"reindex": {
setup: `CREATE TABLE %[1]s.accounts (id bigint PRIMARY KEY, email text NOT NULL);
CREATE INDEX accounts_email_idx ON %[1]s.accounts (email)`,
statements: []string{
`REINDEX INDEX %[1]s.accounts_email_idx`,
`REINDEX INDEX CONCURRENTLY %[1]s.accounts_email_idx`,
},
},
"enum-value-rename-removal": {
setup: `CREATE TYPE %[1]s.mood AS ENUM ('happy', 'sad')`,
statements: []string{`ALTER TYPE %[1]s.mood RENAME VALUE 'sad' TO 'blue'`},
},
"add-column-with-inline-unique-primary-key-references-check": {
setup: `CREATE TABLE %[1]s.accounts (id bigint PRIMARY KEY)`,
statements: []string{`ALTER TABLE %[1]s.accounts ADD COLUMN code int UNIQUE`},
},
}
// A row published as `migrate: refused` must be refused by migrate.Run with
// exactly the published refusal_reason.
func TestMigrateRefusedRowsRefuse(t *testing.T) {
rows, err := capabilities.Rows()
require.NoError(t, err)
byID := map[string]capabilities.Row{}
for _, row := range rows {
byID[row.ID] = row
}
url := testutil.StartPostgres(t)
pool, err := dbconn.NewPool(t.Context(), dbconn.Config{URL: url})
require.NoError(t, err)
defer pool.Close()
for id, claim := range migrateRefusals {
t.Run(id, func(t *testing.T) {
row, ok := byID[id]
require.True(t, ok, "row %s is not in the registry", id)
require.Equal(t, capabilities.DoorRefused, row.FrontDoors.Migrate)
schema := testutil.NewSchema(t, pool)
_, err := pool.Exec(t.Context(), fmt.Sprintf(claim.setup, schema))
require.NoError(t, err)
for _, sql := range claim.statements {
sql = fmt.Sprintf(sql, schema)
st, err := statement.ParseOne(sql)
require.NoError(t, err)
v, err := migrate.Run(t.Context(), pool, st, migrate.DefaultOptions())
require.NoError(t, err, sql)
assert.Equal(t, verdict.OutcomeRefused, v.Outcome, sql)
assert.Equal(t, row.RefusalReason, v.Reason, "%s: the matrix publishes a different reason than migrate emits", sql)
}
})
}
}(Lives in package
2. Test that fails on 8d52f94 until the four entries move// A representative statement is only evidence for a row the matrix publishes
// as supported.
func TestMigrateClaimsBelongToSupportedRows(t *testing.T) {
rows, err := capabilities.Rows()
require.NoError(t, err)
for id := range migrateClaims {
i := slices.IndexFunc(rows, func(r capabilities.Row) bool { return r.ID == id })
require.GreaterOrEqual(t, i, 0, "row %s is not in the registry", id)
assert.Equal(t, capabilities.DoorSupported, rows[i].FrontDoors.Migrate, "migrateClaims has statements for row %s, which is not published as migrate: supported", id)
}
}
3. Probe output on 8d52f94 (PostgreSQL 16, `Options.Force = "SCHEMA.TABLE"`)4.
Suggested fix: either make the dry-run apply sequence admission and the diff hint name the Verified
Mutations ran in a throwaway worktree; nothing was pushed. This review was generated by Claude Code (claude-sonnet-5-5). |
|
🤖 Review 2/2 (OSS adoption and integration ease, for This is the second lens: what an adopter or an importer (an orchestrator reading BlockingNone. Non-blocking1. The ❌ mark now has two meanings, and one row spans two refusal classes.
Suggested fix: pick one reading and make it uniform. Either extend the ❌ definition in the three hand-written places and the contract to "no online mechanism pg-sprite will drive: PostgreSQL has none, or the only online form is a direct statement the operator runs", and drop "yet" from the detail; or split each row into a plain and a concurrent row so each carries one class. The first is a docs-and-string change. 2. " 3. Say what changed for existing consumers. Four existing ids changed value in a published, versioned JSON surface: Verified
This review was generated by Claude Code (claude-sonnet-5-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Stamping with comments: 0 blocking, 7 non-blocking (see the two review comments above).
This stamp was left by Claude Code (claude-sonnet-5-5).
Test that every migrate: refused row refuses with its published reason, fix the two drifted tokens, and drop stale supported claims. Document --force, dry-run and diff caveats for attach/detach/drop-index, and define the ❌ mark uniformly.
|
🤖 Review response — created by Kiran's code review agent (Amp, Claude Opus 4.6) — pull/188, follow-up commit Verdict: all 7 non-blocking findings addressed in a follow-up commit that also merges
Source: block/pg-sprite#188, review comments 6229736338 and 6229736931 and review 5556242609 at head |
…blocking-cascade * origin/main: capabilities: mark rows migrate refuses as refused (#188) fix(migrate): report a desired-state run executed only when a fresh plan is empty (#217) feat(migrate): refresh column statistics after a native type change (#198) planner: carry every attribute of a primary key or unique constraint into its online sequence (#197) # Conflicts: # docs/capabilities.md # pkg/capabilities/capabilities.yaml
The capability matrix no longer says
migrateruns four operations it always refuses, and a test now keeps everymigrate: supportedrow honest.Why
capabilities --jsonis what an orchestrator reads to decide what to submit.ATTACH PARTITION,DETACH PARTITION [CONCURRENTLY],DROP INDEX, andREINDEXwere published as T1 withmigrate: supported, butmigraterefuses each of them with exit 2, so a consumer trusting the matrix submits statements that can never run.What
attach-partitionanddetach-partition-concurrentlybecome T2 (planned),migrate: refusedwith the reasonmigrateactually emits (not-native-safe-rewrite-required,unsupported-statement).drop-indexandreindexbecome T3,migrate: refusedwithindex-statement; the notes keep the--accept-blockingexit-3 path.diffisnot_applicablefor attach, detach, and reindex, which a desired schema cannot express;drop-indexkeepsdiff: supported.docs/capabilities.mdregenerated;docs/limitations.mdand the README synced.TestMigrateSupportedRowsExecuteruns a representative statement for everymigrate: supportedrow throughmigrate.Runagainst Postgres and fails if any is refused; a new supported row with no statement mapped fails even underSKIP_INTEGRATION.Before / after
Only the published matrix changes;
migratebehaves exactly as before. The example is the attach row, which an orchestrator would most likely act on.The same holds for
DETACH PARTITION,DROP INDEX, andREINDEX.Release note: capability matrix corrections (
pg-sprite capabilities --json)Four existing rows published
migrate: supportedfor statementsmigraterefuses by default. They now match the shipped behaviour:attach-partition: T1 → T2 (🟡),migrate: refused(not-native-safe-rewrite-required),diff: supported→not_applicable.detach-partition-concurrently: T1 → T2 (🟡),migrate: refused(unsupported-statement),diff: supported→not_applicable.drop-index: T1 → T3 (❌),migrate: refused(index-statement);diffstayssupported.reindex: T1 → T3 (❌),migrate: refused(index-statement),diff: supported→not_applicable.Two rows now publish the
refusal_reasonthatmigrateactually emits:add-column-with-inline-unique-primary-key-references-checkisnot-native-safe-rewrite-required(wasunsupported-statement), andenum-value-rename-removalisunsupported-statement(wasbackend-unavailable).This corrects wrong data; the JSON schema is unchanged. A consumer that selects rows by
tier == "t1",front_doors.migrate == "supported",front_doors.diff == "supported", or a specificrefusal_reasongets a different set of rows after upgrading. The ❌ mark now reads "no online mechanism pg-sprite will drive": either PostgreSQL has none, or the only online form is a direct statement the operator runs (DROP INDEX CONCURRENTLY,REINDEX ... CONCURRENTLY).diffisnot_applicablefor attach and detach because a desired file describes state, not a transition, sodiffnever plans anATTACHorDETACH. A desiredCREATE TABLE ... PARTITION OFis refused under thecreate-table-partition-ofrow (diff: refused).