PROTOTYPE (do not merge): text-first update-script checker, for comparison with #90 - #92
Conversation
INCOMPLETE PROTOTYPE opened for comparison against Postgres-Extensions#90, not a merge candidate. Nothing is wired into the Makefile or CI. Instead of parsing install scripts into semantic object keys, split both sides into top-level statements, normalize (strip comments, collapse whitespace), multiset-diff them, and require every added statement to appear as a substring of the normalized update script. Update scripts are overwhelmingly copy-paste from the install script, so most of the work the object model does is not needed to catch the omission. Implements one gap for real -- ALTER DEFAULT PRIVILEGES is not retroactive, so an ADP newly added in NEW leaves every same-category object that already existed in OLD needing an explicit GRANT. That flags the five enum types this tree never granted USAGE on. Enum values, new columns, changed view column lists and removals are stubbed and documented as such, and the header lists the false-positive sources. The escape hatch is a `-- update-lint: ok /REGEX/ reason` comment in the update script; unused waivers are reported so they cannot rot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @jnasbyupgrade's task in 3m 40s —— View job Review: PR #92 — text-first update-script checker (prototype)
Note: I was unable to execute This is explicitly a non-merge comparison prototype, so I focused on things the PR description/header doesn't already disclose, rather than re-listing the stubs and false positives it's upfront about. Findings1. Waiver regex parsing breaks on a literal next unless $line =~ m{--\s*update-lint:\s*ok\s+/(.+?)/\s*(\S.*?)\s*\z};The non-greedy 2. next unless $st->{norm} =~ /\A
ALTER \s+ DEFAULT \s+ PRIVILEGES \s+
IN \s+ SCHEMA \s+ (\S+) \s+
GRANT \s+ (.+?) \s+ ON \s+ TYPES \s+ TO \s+ (\S+)
/xi;This matches the exact form used today (confirmed against 3. $text =~ s/^(.*)-- SED: PRIOR TO ([^!\n]*)!/-- Not used prior to $2: $1/gm;
Smaller observations
Given the PR's own framing (prototype for comparison, not a merge candidate), none of the above blocks anything — they're notes for if/when this direction gets carried forward rather than #90's. |
This is a deliberately incomplete prototype, opened only for comparison against #90. It is not a merge candidate and can be closed without merging. Nothing here is wired into the
Makefileor CI; several real cases are stubbed, and it has known false positives.The principle
Never build a model of what an object is. Compare statement text.
;inside any of those is not a boundary).DOblock, aformat()or anIFstill matches.This works because update scripts are overwhelmingly copy-paste from the install script. The interesting content of the tool is therefore not the matcher — it is the list of cases where a copy is impossible, plus the escape hatch for everything else.
Numbers, on every real pair in the tree
0.2.0→0.2.10.2.0→0.2.2__cat_tools.omit_column,_cat_tools.columnrebuild,cat_tools.trigger__parse0.2.1→0.2.20.2.2→0.2.3relation__kind/relation__relkindhelper calls the author deliberately reformatted into hand-writtenCREATE OR REPLACE FUNCTION0.2.3→0.3.0create_function,routine__parse_arg_types_text), 3CREATE TYPE ... AS ENUMthat gained labels0.3.0→ current)__cat_tools.create_functionscaffolding copy, bound to an older helper nameRemoved statements are counted and listed but not failed on — a
DROPnever appears in an install script, so a removal can never be matched by copy.Every unmatched item above falls into a category the header names: scaffolding bound to old names, enum-value additions, or deliberate hand-reformatting. None of them is a missed update; all six pairs are real, and the false-positive rate is 14/96 added statements.
Size
bin/update_lint_textfirstbin/test/textfirst.tbin/update_lintfrom #90, for reference)What it implements for real
ALTER DEFAULT PRIVILEGES. ADP is not retroactive, so a copy of the ADP statement in the update script is not enough. When NEW's install adds an ADP that OLD's did not have, every object of that category that already existed in OLD needs an explicitGRANTin the update script.--versions 0.2.1 0.2.2flags exactly the five enum types (constraint_type,procedure_type,relation_type,relation_relkind,object_type) created in 0.2.0/0.2.1 that never gotGRANT USAGE— the real historical bug. Scoped to ADP statements that are newly added, which is why0.2.2→0.2.3raises nothing. Only the TYPES category is written.The escape hatch, which is central to the design and not an afterthought:
-- update-lint: ok /REGEX/ reasonin the update script. Any finding whose text matches is suppressed and the reason is printed instead; the reason is mandatory; waivers that match nothing are reported so they cannot rot. They live in the update script because that is the file being reviewed and the file the exception is a property of. Intent: a handful per release on genuine exceptions, never one per statement.
What it stubs
Each is documented in the header and is a false positive today, waivable by hand:
CREATE TYPE ... AS ENUM ('a','b','c'), update must sayALTER TYPE ... ADD VALUE 'c'. Needs a label-set diff.CREATE TABLElist, update needsALTER TABLE ... ADD COLUMN.CREATE OR REPLACE VIEW, update mustDROP VIEW+CREATE VIEW.Known false-positive sources
__cat_tools.create_function()calls a differently-named helper than the install's, so the text legitimately differs. This fires on the dev pair today.0.2.2→0.2.3reports 2 of 2 added unmatched even though the update is correct. Text-first cannot distinguish that from a real omission; the escape hatch is the only answer.Honest comparison with #90
Where this shape is better:
Where it is worse, and I think these matter:
/CREATE TYPE cat_tools.object_type/waives that whole statement forever, including a future label added and forgotten. Add bin/update_lint: static check that the update script covers the object diff #90's enum handling stays live.GRANTs, breaks a match that is semantically fine. That is a maintenance tax paid on every SQL edit, whereas Add bin/update_lint: static check that the update script covers the object diff #90 pays it only when a new DDL shape appears.format()template that is never executed, matches. So does a copy inside anIF falsebranch.My read: this is a good sketch of the floor — if #90 did not exist, 204 lines buying 85% coverage would be an easy yes. Against #90 it is a downgrade, because the gap between them is exactly the recurring cases (enum labels, ACLs on pre-existing objects, scaffolding), and the escape hatch converts those from "handled" into "annotated by hand every release". The two ideas are not exclusive, though: the ADP check here is text-first and cost ~30 lines, and the statement-text matcher would make #90's findings considerably easier to read.
Tests
prove bin/test/textfirst.t— 12 tests: the splitter's four;hiding places, substring match through aDOblock, an unmatched statement failing, a waiver suppressing and an unused waiver being reported, and the five-enum-type ADP result on the real0.2.1→0.2.2pair. Deliberately a handful, not a suite.