sqlb-survey: the whole-database adoption report (#112) - #113
Merged
Conversation
An adoption discovers what the DSL cannot express one table at a time, because the only way to ask is to write a throwaway program (#112). This is that program, kept. Three phases, because the flat skip list is not the question an adopter has. Phase A introspects the whole schema, which is what a drift gate would see. Phase B introspects every table ALONE, which is what makes the result triageable: introspect reports per construct, but the gate is per registry, so one unmodelable table takes its whole module out (#109). Per-table isolation names the blocked tables instead of leaving them mixed into a list of skips. Phase C renders the modelled registry into a scratch database and re-introspects, so a construct that survives import but not the round trip is separated from one that never imported. Run against a 68-table production schema: 57 tables clean, 11 partial, 0 refused, 15 skipped constructs over 6 reasons, and 8 residual changes after the round trip -- 4 CHECK constraints dropped and re-added, all of them on varchar columns, none on the text columns beside them. One finding is the tool's own setup and belongs in the doc comment rather than in a report: Diff renders no CREATE EXTENSION, so bootstrapping into a bare database fails once per table with a missing uuid_generate_v4 rather than once with the missing extension named. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Scg4WyZ3hV6ZqLjQPL2ckK
This was referenced Aug 2, 2026
jryannel
added a commit
that referenced
this pull request
Aug 2, 2026
…t a schema (#117) A flat verdict list under-reports a modular monolith. The gate is per registry and a modular monolith has one registry per module (ADR-0015), so a table that cannot be modelled does not take out "the schema" -- it takes out its module. Whether eleven blocked tables mean one app or six is the number that decides how much of a port is blocked, and it is not visible in a list sorted by table name. sqlb-survey -modules billing,catalog src dst regroups the per-table verdict the run already computes, and marks a module blocked when any of its tables is partial or refused. Tables matching no prefix are counted together and listed, since that set is either the shared core or a prefix missing from the flag, and the reader has to be able to tell which. Prefixes are supplied rather than inferred. Guessing them from table names splits hotel_rooms from hotels, and a wrong split reads as a real result. Longest prefix wins so a module named user does not claim the tables of user_billing. On the 68-table schema from #113 the eleven partial tables fall across five of seven module groups, which is the shape the flat list hid: the blockers are diffuse, so no single module unblocks by fixing one thing. Claude-Session: https://claude.ai/code/session_01Scg4WyZ3hV6ZqLjQPL2ckK Co-authored-by: Claude <noreply@anthropic.com>
jryannel
added a commit
that referenced
this pull request
Aug 2, 2026
… needs no patch (#118) The exclusion list was the literal string goose_db_version. Pointing the survey at a project on golang-migrate or atlas meant editing the source, and forgetting to meant its bookkeeping table arrived as an unmodelable table -- a blocker that is not one. -exclude replaces the list. The default now names five runners so the common cases need no flag at all. Naming five runners does not mean excluding five tables, and getting that wrong cost a wrong number here first: introspect reports a name in Exclude it cannot find, deliberately, because a typo would otherwise silently shrink what a gate checks. Right for a gate, wrong for a default list of which four entries are absent from any given project -- they arrived as four extra skipped constructs, and the run said 19 where the schema has 15. So the list is narrowed to what the database actually holds before it reaches introspect, and the header prints the narrowed list rather than the wish. Narrowing needs the unfiltered table list, which is where the second bug was: listTables took the skip list as a parameter for the first time, nil binds as SQL NULL, and `tablename <> ALL(NULL)` is NULL rather than true, so the discovery call matched no rows and the survey reported zero tables. Output on the schema from #113 is unchanged: 68 tables, 15 skipped constructs, 57 clean / 11 partial / 0 refused, fixpoint residual 8. Claude-Session: https://claude.ai/code/session_01Scg4WyZ3hV6ZqLjQPL2ckK Co-authored-by: Claude <noreply@anthropic.com>
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.
An adoption currently discovers what the DSL cannot express one table at a time, because the only way to ask is to write a throwaway program (#112). This is that program, kept.
Why three phases
The flat skip list is not the question an adopter has.
introspectreports per construct, but the gate is per registry, so one unmodelable table takes its whole module out (A composite PRIMARY KEY has no declaration, so every natural-key table pays for a surrogate it does not need #109). Per-table isolation names the blocked tables instead of leaving them mixed into a list of skips.Result on a 68-table production schema
The 8 residuals are 4 CHECK constraints dropped and re-added. Every one is on a
varchar(n)column; the threetextcolumns carrying the same kind ofINlist round-trip clean. Postgres normalises the list two different ways depending on the column type:Semantically identical, textually different. Worth knowing whether that belongs here or in the adopter's schema — for the subject above it was cheaper to move the columns to
text, which ADR-0017 wanted anyway.One finding that is the tool's own setup
Diffrenders noCREATE EXTENSION, so bootstrapping into a bare database fails once per table withfunction uuid_generate_v4() does not existrather than once with the missing extension named. Withuuid-osspandvectorpre-created, the same 329 statements applied cleanly. That is recorded in the command's doc comment; whetherDiffshould emit extensions is a separate question and not decided here.Notes
pgxandsqlbonly — the dependency gate is unaffected.go buildandgo vetclean.cmd/sqlbas a subcommand. If No 'sqlb introspect' subcommand: the first step of every adoption is the only one with no shell, and diagnosing a refusal means writing a throwaway program #112 lands assqlb introspect, this is the behaviour to fold in; keeping it separate first means the report shape can change without touching the released CLI surface.🤖 Generated with Claude Code
https://claude.ai/code/session_01Scg4WyZ3hV6ZqLjQPL2ckK
Generated by Claude Code