Repository navigation
Conversation
The live-type parse dropped text after the modifiers and the target parse dropped array bounds, schema qualifier, and COLLATE, so a table rewrite such as timestamptz(3) to timestamp(3) read as a free catalog relabel.
Resolve against #196, which reads the type shapes this branch refused: an array is now an array of its element (a relabel only when the element is unchanged), a schema-qualified target is NewTypeSchema, and format_type text after the modifiers is part of the name ("timestamp(3) with time zone" is timestamptz). Take main's parsing and fields, and keep the one shape #196 does not read: a COLLATE clause, now NewTypeCollated, which targetTypeShape leaves without a shape so it never takes the relabel route. NewTypeOpaque and typeBeyondNameAndMods go; the test cases #196 already covers (arrays, a user-schema target) are dropped, and the timestamptz / timetz, trailing-text, COLLATE, pg_catalog-spelling, and dry-run cases stay.
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 of 02f3096: 0 blocking, 4 non-blocking. Adversarial correctness pass over the full diff at The change itself is correct. A target with a Worth knowing before merging: on current Non-blocking1. The live column's own collation is not read, so a collated column retyped without Test that fails on 02f3096 and passes with the fix// A column with an explicit collation that is retyped without a COLLATE
// clause takes the type's default collation, so every index on it is rebuilt
// under ACCESS EXCLUSIVE and its sort order changes. That is not a relabel.
func TestMigrateDryRunRoutesCollatedColumnRetypeAsRewrite(t *testing.T) {
url := testutil.StartPostgres(t)
pool, err := dbconn.NewPool(t.Context(), dbconn.Config{URL: url})
require.NoError(t, err)
defer pool.Close()
schema := testutil.NewSchema(t, pool)
_, err = pool.Exec(t.Context(), fmt.Sprintf(`CREATE TABLE %s.t (id int PRIMARY KEY, c varchar(50) COLLATE "C")`, schema))
require.NoError(t, err)
_, err = pool.Exec(t.Context(), fmt.Sprintf(`CREATE INDEX t_c ON %s.t (c)`, schema))
require.NoError(t, err)
report := dryRunPlan(t, url, fmt.Sprintf("ALTER TABLE %s.t ALTER COLUMN c TYPE varchar(100)", schema))
require.Len(t, report.Statements, 1)
assert.Equal(t, planner.RouteCopyAndSwap, report.Statements[0].Route)
}
2. Test that fails on 02f3096 and passes with the fixfunc TestDescribeKeepsCollation(t *testing.T) {
plain, err := statement.ParseOps(`ALTER TABLE t ALTER COLUMN c TYPE text`)
require.NoError(t, err)
collated, err := statement.ParseOps(`ALTER TABLE t ALTER COLUMN c TYPE text COLLATE "C"`)
require.NoError(t, err)
assert.NotEqual(t, plain[0].Describe(), collated[0].Describe())
}
3. The PR description does not describe the diff. It names 4. Smaller items.
Verified
This review was generated by Claude Code (claude-sonnet-5-5). |
|
🤖 Review 2/2 of 02f3096: OSS adoption and integration lenses, 0 additional blocking, 3 non-blocking. The two lenses applied to the diff at OSS adoption: would an operator who is new to pg-sprite understand what happened?1. A collation-only change is reported as a table rewrite. After this PR, 2. The classification doc is silent on the new rule. Integration ease: schemabot and other importers3. The exported surface grows by one field, and the PR text describes a different one. Two things that are good for integrators and worth keeping: the new gate lives in the planner's single 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, 4 non-blocking (see the review comments above).
This stamp was left by Claude Code (claude-sonnet-5-5).
An
ALTER COLUMN ... TYPEwhose live or target type the planner cannot read in full now takes the rewrite route, instead of being classified as a binary-coercible catalog relabel.Why
The relabel check compares a parse of the live type's
format_typerendering with a parse of the statement's target type, and both parses lose information. The live side ignores everything after the closing parenthesis, sotimestamp(3) with time zonereads astimestamp(3), andcharacter varying(50)[]asvarchar(50). The target side keeps only the last name part and the modifiers, so it drops[], a schema qualifier, andCOLLATE. A change PostgreSQL carries out as a table rewrite underACCESS EXCLUSIVE, or one it cannot cast at all, could therefore be routed native as a brief relabel.What
parseTypeTextreturns the empty shape, which is never coercible, for an array rendering, text after the modifiers, or modifiers it cannot parse.statement.OpgainsNewTypeOpaqueforALTER COLUMN TYPE. It is set when the target has array bounds, a schema qualifier other thanpg_catalog, or aCOLLATEclause, and the planner routes such a change to copy-and-swap withtype-rewrite.varchar,varchartotext, wideningnumericprecision, and theirpg_catalog.and SQL-standard spellings. One case now takes the rewrite route where it was a native no-op: retyping atimestamp(3)column totimestamp(3), whose live rendering also carries trailing text (without time zone).TestClassifyTypeChangeRewritesWhatTheModelCannotSeehas eight named planner cases, all native onmain.TestClassifyTypeChangeAcceptsPgCatalogQualifiedTargetguards the qualified spellings.TestParseOpsShapesrows cover the target shapes.TestMigrateDryRunRoutesTimestamptzToTimestampAsRewriteruns a dry run against a real server, so the live type is the server's own rendering.Before / after
The example is a
timestamptz(3)column retyped totimestamp(3), which PostgreSQL does by rewriting the table. The statement and the live rendering are the same on both sides. What changes is that the planner stops reading the rendering only up to the parenthesis.The other planner cases that change the same way:
timetz(3)totime(3), a live array retyped to a scalar, a scalar retyped to an array, an array retyped to an array, a target in a user schema (myschema.text), and a target withCOLLATE "C".