Add sql-lint: style linter for PostgreSQL SQL files - #16
Conversation
Port cat_tools PR #49: vendor the shared Postgres-Extensions style linter as a git submodule (.vendor/linter, pinned to b8632c2, same commit used by cat_tools#49), add a `make lint` target via a thin lint.mk hand-off, and gate it into CI as a new `lint` job. LINT_TARGETS is scoped to sql/extension_drop.sql and test/, excluding the frozen sql/extension_drop--1.0.0.sql release snapshot (RELEASE.md: once a version is released its sql/<ext>--<version>.sql is never hand-edited again, so linting it would produce permanent, unfixable findings) -- though the linter's own generated-file skip (first line matching both "GENERATED" and "DO NOT EDIT") already excludes it independently. The lint job runs once, ungated (no PG version dependency), and the existing 12-leg PostgreSQL test matrix now gates behind it (needs: [lint]) per the cost-gating pattern of running cheap checks before expensive ones. Its own checkout deliberately omits submodules: true, matching cat_tools's choice, so `make lint` (lint.mk's self-init via `git submodule update --init`) is exercised the same way a developer would run it locally. No pre-existing SQL style violations were found in this repo's actual source (sql/extension_drop.sql, test/*) -- make lint already passes clean. This branch is independent, based on upstream/master. Other in-flight testing-infrastructure branches in this repo also touch test/sql/*.sql and .github/workflows/ci.yml, so a rebase will likely be needed once those land. Co-Authored-By: Claude Sonnet 5 <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: Pro Plus 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 |
… be reverted)" This reverts commit 8198f76.
Verification: confirmed the lint gate can actually fail (not just pass)Per the lesson in a related incident (linter added to another repo with
Leaving the temp-violation + revert commits in the branch history rather than squashing, since they're the actual evidence this verification happened on real CI, not just locally. |
lint.mk unconditionally included the .vendor/linter submodule's lint.mk, with a remake rule that runs `git submodule update --init` if the file is missing. GNU Make tries to satisfy every `include` before running any requested target, so a tree with no .git at all (exactly what git archive produces -- PGXN dist packages, make dist, never include submodule content and strip .git entirely) hit the remake rule on every single `make` invocation, not just `make lint`. `git submodule update` fails immediately outside a git repo, aborting the whole build. CI never caught this because CI always runs from a real checkout with submodules present. Guard the include on a real .git being present (directory for a normal clone, or a gitdir: file for a worktree/submodule checkout -- both match $(wildcard .git)). Lint is simply unavailable in a tarball build, which is correct -- there's no reason to lint a frozen source tarball.
Summary
Ports cat_tools PR #49 to this repo (
extension_drop)..vendor/linter, pinned tob8632c2a3d93de664a45f4622235871e8f19cf78— the same commit cat_tools#49 pinned.lint.mk(the entire local footprint per the linter's own README) that self-initializes the submodule viagit submodule update --initon first use, then hands off to.vendor/linter/lint.mk. This meansmake lintworks right after a plaingit clone, no--recurse-submodulesneeded.MakefilesetsLINT_TARGETS = sql/extension_drop.sql test/, scopingmake lintto the actively-maintained source.sql/extension_drop--1.0.0.sqlis excluded: it's a frozen, already-released version snapshot (perRELEASE.md's "Ongoing development" section, a releasedsql/<ext>--<version>.sqlis never hand-edited again), so linting it would produce permanent, unfixable findings and makemake lintunusable as a CI gate. In practice the linter's own generated-file skip (first line matching both "GENERATED" and "DO NOT EDIT", whichsql/extension_drop--1.0.0.sql's/* DO NOT EDIT - AUTO-GENERATED FILE */header satisfies) already excludes it independently — the explicitLINT_TARGETSscoping just documents the intent, mirroring cat_tools's own Makefile comment.lintjob to.github/workflows/ci.yml: cheap, PG-version-independent, so it runs once (not matrixed). Its checkout deliberately omitssubmodules: true— same choice cat_tools made — somake lint(the same command a developer runs locally) is what actually proveslint.mk's self-init works, rather than papering over it with a pre-populated checkout.testmatrix now gates behindlint(needs: [lint],if: success()): per~/test-fixes.mditem 3 (gate expensive CI matrices behind cheap ones), there's no point running a dozen expensive legs against a baseline that's already broken by a lint violation. Per item 6, the lint job itself does not get crossed into the PG matrix — it's a single job, notmatrix.pg-scoped.lintis added toall-checks-passed'sneeds:list.What was fixed
Nothing (originally) —
make lintalready passes clean against this repo's real SQL (sql/extension_drop.sql,test/deps.sql,test/helpers/*.sql,test/sql/*.sql,test/extension/*.sql). No pre-existing style violations were found, unlike cat_tools (which needed several inline-comment and borrowed-view fixes). Verified locally: with the submodule de-initialized (simulating a fresh CI checkout without submodules),make lintself-initializes to the pinned commit and exits 0.Update: fixed a real bug — unconditional
includebreaks every build from a source tarballMakefiledoesinclude lint.mkunconditionally, andlint.mkin turn didinclude .vendor/linter/lint.mkunconditionally, alongside its self-init rule:.vendor/linter/lint.mk: git submodule update --init -- .vendor/linterGNU Make tries to satisfy every
included file before running any requested target, for any target — not justlint. That's fine in a normal git checkout: if.vendor/linter/lint.mkis missing, Make runs the remake rule, the submodule gets initialized, and everything proceeds. Butgit archive— which is exactly how PGXN distribution tarballs, and this repo's ownmake dist(pgxntool/base.mk'sdist-onlytarget), are built — never includes submodule content and strips.gitentirely. In a tree built that way,.vendor/linter/lint.mkdoesn't exist and never can (there's no.gitforgit submodule updateto work with), so the remake rule fails immediately withfatal: not a git repository, and that failure aborted everymakeinvocation — plainmake,make install, everything — not justmake lint. CI never caught this because CI always runs from a real git checkout with submodules present, never from an actual archived tarball a real downstream consumer would build from.Fix: guard the
include .vendor/linter/lint.mk(and its remake rule) behindifneq ($(wildcard .git),), which matches both a real.gitdirectory and agitdir:file (worktree/submodule checkouts). No.gitat all → the include and remake rule are skipped entirely, andmake/make install/etc. proceed normally without ever touching.vendor/linter.make lintitself becomes unavailable in that case (fails with a plain "No rule to make target 'lint'" if explicitly invoked) — correct, since a frozen source tarball has no reason to lint. The top-levelMakefile's owninclude lint.mkneeded no change:lint.mkis a regular committed file (not vendored), so it's always present and includes cleanly regardless of.git.Verification performed (tarball build, not just
make lintfrom a normal checkout — that environment can never hit this bug):git archive --prefix=.../ -o ... HEADfrom this branch, extracted into a clean scratch directory with no.gitanywhere (confirmed viafind—.vendor/linterpresent in the archive but completely empty, submodule content stripped as expected).make(default target) → succeeds (Nothing to be done for 'all').make install DESTDIR=<scratch>→ succeeds, installs cleanly into the scratchDESTDIR, no reference to.vendor/linter, no submodule error.make lintin that same tree → fails gracefully withmake: *** No rule to make target 'lint'. Stop.(exit 2) rather than the submodule crash — confirms lint is simply unavailable there, not silently broken.e96e675, this PR's tip before the fix) the same way and ran plainmakein a clean.git-less tree — reproduced the exact failure:fatal: not a git repository (or any of the parent directories): .git/make: *** [lint.mk:9: .vendor/linter/lint.mk] Error 128..gitpresent):make lintstill runs exactly as before, self-initializing the submodule and exiting 0.Rebase note
This branch is independent, based on
upstream/master. Other in-flight testing-infrastructure branches in this repo (fix-cat-tools-install→test-install-foundation→pg-upgrade-ci/pg-tle-ci) also touchtest/sql/*.sqland.github/workflows/ci.yml. A rebase against those will likely be needed once they land — not addressed here.Test plan
make lintruns clean locally againstsql/extension_drop.sqlandtest/make lintself-inits to the pinned commit and still exits 0.github/workflows/ci.ymlvalidated as parseable YAMLgit archivetarball, extracted to a clean directory with no.git, and confirmed plainmakeandmake install DESTDIR=...succeed without touching.vendor/linter🤖 Generated with Claude Code