Repository navigation
fix(analyser): precise spans and coverage for the rules ported from Squawk - #905
Merged
Merged
Conversation
…like Postgres FigureColname takes the name of a scalar subquery's column, the last field name of an indirection, and the name of a CASE's ELSE result if it is a good name. The port named the first two `?column?` or after the indirection's argument, and every CASE `case`, so banDuplicateColumnAssignments reported valid views as duplicates and unknownColumn rejected existing view columns. Also run banDuplicateColumnAssignments over the Postgres regression suite and fix the SQLSTATE of duplicate column lists in its docs.
…quawk banDuplicateColumnAssignments now points at the duplicate, also checks ALTER TABLE ... ADD COLUMN, CREATE TYPE ... AS and COPY column lists, and a `*` in a view only hides the names it may expand to. requireTableSchema points at the table, names it in the note, skips temporary tables created earlier in the file, and covers foreign tables, RENAME, SET SCHEMA, SELECT INTO, CREATE INDEX and referenced tables. banDropConstraint and banDropDefault report each command with its name. RENAME ATTRIBUTE moves from renamingObject to renamingColumn, and requireEnumValueOrdering only runs on migrations.
psteinroe
added a commit
that referenced
this pull request
Oct 26, 2026
…ction and banDuplicateColumnAssignments (#909) Stacked on #905, and merges #897 (both change `identifierTooLong`), so until #897 lands the diff here includes it; the change of this PR is the last commit. Three of the rules ported from Squawk in #885 get fixes, using the fix API from #859. Squawk has fixes for these; `requireEnumValueOrdering`'s Squawk fix inserts a placeholder that doesn't run, so it isn't ported. **identifierTooLong: safe** The fix spells out the name Postgres already uses: the identifier truncated to 63 bytes at a character boundary, the way Postgres truncates it. Since #897 reports each name once per statement, the fix rewrites every identifier of the statement that stands for the name: ```sql select aaaa…(64).id from aaaa…(64) where AAAA…(64).id = 1; -- fix: select aaaa…(63).id from aaaa…(63) where AAAA…(63).id = 1; ``` Unquoted identifiers keep their case as written, quoted ones are re-quoted from the truncated name (so a `""` cut in half can't break the quoting). `U&"..."` identifiers are left alone, because rewriting them would leave a `UESCAPE` clause behind. The report proposed making it unsafe for quoted names. I made it safe for all: the statement does exactly the same afterwards, quoted or not, which is what `FixKind::Safe` means. Other statements that use the long name get their own diagnostic and fix, and leaving them as they are is still equivalent. The fix doesn't pick a good short name, that's still up to the user (the note says so). **banUncommittedTransaction: unsafe** Adds `commit;` on a new line after the last statement of the file, or of the migration section the `BEGIN` is in: ```sql -- section: up transaction:false begin; create table users (id bigint); commit; -- added -- section: down transaction:false ``` Unsafe because it changes what the migration does (its changes are now applied), and whether `COMMIT` belongs at the end, or a `ROLLBACK` was meant, is a guess. To know where the file or section ends, `AnalysedFileContext` now knows where its statements start and where the last one ends, including statements that don't parse, via `end_offset()`, relative to the current statement like other fix edits. **banDuplicateColumnAssignments: unsafe** Keeps the last assignment of a SET clause (UPDATE, ON CONFLICT DO UPDATE, MERGE) and removes the earlier ones with their commas. Only if every assignment to the column is a plain `col = value`: no fix for INSERT column lists (the values would have to move too), `(a, b) = ...` assignments, or subscripts and fields. Unsafe because we can't tell which value was meant. **Tests** Every spec snapshot of the three rules now shows the fixed SQL, and the test harness checks it still parses. New specs: `banDuplicateColumnAssignments/fixes.sql` (comments between assignments, ON CONFLICT, and the cases without a fix), `banUncommittedTransaction/fix_in_section.sql`. **Risks** The fixes are plain token edits, independent of the fix infrastructure changes in flight. One interaction to watch: #906 checks each fix by applying it to the statement text and re-parsing. The COMMIT fix's edit lies past the end of the `BEGIN` statement (at the end of the file or section), so `RuleFix::apply` on the statement text would slice out of range. Whichever of the two lands second has to handle edits outside the statement, e.g. skip the re-parse check for them or apply the fix to the file text. **Follow-ups:** #906 keeps fixes that edit past the end of their statement without re-parsing them, so the `commit;` fix works with it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #891.
Follow-ups to the rules ported from Squawk in #885: where the diagnostics point, and what they cover. Every new error case was checked against Postgres 15 in a rolled-back transaction.
banDuplicateColumnAssignments
Diagnostics pointed at the whole statement. They now point at the second occurrence: the column name in SET, INSERT and column definitions, and the whole select list entry (e.g.
coalesce(a, b) as x) in views,CREATE TABLE ASandSELECT INTO. View column lists and COPY column lists have no locations and still point at the statement.A
*anywhere in a view's select list turned the check off. It now only hides the names it may expand to:If the view's explicit column names reach into the
*, we still can't tell what the later entries are called, socreate view v (a, c, d) as select t.*, 2 as x, 3 as xstays silent (Postgres accepts it whenthas two columns).New statements, all rejected by Postgres with 42701:
alter table users add column email text, add column email text(also afterADD COLUMN IF NOT EXISTS email; a laterIF NOT EXISTSis skipped by Postgres and not reported),create type pair as (a int, a int),copy users (id, id), andcreate foreign table ... (id int, id int). ADD COLUMN and COPY have their own detail text.requireTableSchema (opt-in)
e.g. public.postsinstead ofpublic.my_table).DROP TABLE a, bpoints at each name.create temp table,create table pg_temp.x,select ... into temp x) are no longer reported on laterALTER/DROP/CREATE INDEX, until they are dropped. An unqualified name refers to them sincepg_tempcomes first in the search path. Squawk reports these too.CREATE FOREIGN TABLE,ALTER/DROP FOREIGN TABLE,ALTER TABLE ... RENAME(table, column, constraint),SET SCHEMA,SELECT INTO,CREATE INDEX ON, and inside those statementsREFERENCES,PARTITION OF,INHERITS,LIKE,ATTACH PARTITION. Queries (the body of a view,CREATE TABLE AS ... SELECT ... FROM x, DML) stay out of scope, otherwise the rule would fire on every query. This is wider than Squawk, which only checks CREATE/ALTER/DROP TABLE; the rule is opt-in, and a user who enables it wants every table name in DDL qualified.banDropConstraint, banDropDefault (opt-in)
One diagnostic per statement without a name became one per command, with the name, pointing at its
DROP CONSTRAINT/DROP DEFAULT:renamingObject (opt-in)
Most of its findings on the regression corpus are
ALTER INDEX ... RENAMEandRENAME CONSTRAINT, which Squawk'srenaming-objectreports as well, so they stay. We also report collations, text search configurations, FDWs and other objects Squawk doesn't. I kept those: clients do reference collations and text search configurations by name, and the rule is opt-in. The one change isALTER TYPE ... RENAME ATTRIBUTE, which moves torenamingColumnlike in Squawk (an attribute is a column of a composite type).requireEnumValueOrdering now has
applies_to: Migration, like the other DDL rules from #885.Tests
New spec files for the new duplicate cases (and the accepted ones next to them), the new requireTableSchema positions and temporary tables, multiple commands for banDropConstraint/banDropDefault, and RENAME ATTRIBUTE. The spec snapshots don't show spans, so there are unit tests per rule asserting the text each diagnostic points at, using a small test helper
spanned_textsinlib.rs.StatementTokensgets akeyword_pairshelper for theDROP CONSTRAINT/DROP DEFAULTspans.Not covered
Duplicates that need the catalog (
ADD COLUMNof an existing column,LIKE tplus a column oft) and view column lists / COPY column lists still point at the statement.