feat: add generate_schema_docs tool for database documentation - #278
feat: add generate_schema_docs tool for database documentation#278hasithasandunlakshan wants to merge 2 commits into
Conversation
…on generation in markdown and structured formats
|
Nice tool, been trying it out. Ran it against a few schema shapes and hit something on the foreign key output that seemed worth flagging. For a composite (multi column) foreign key, the FK list looks like it's giving the cartesian product of the source and target columns instead of the actual column pairing. Quick repro with one composite FK, (a, b) references parent (a, b): create table public.parent (a int not null, b int not null, primary key (a, b));
create table public.child (
a int not null, b int not null,
constraint child_parent_fk foreign key (a, b) references public.parent (a, b)
);generate_schema_docs comes back with 4 edges for child_parent_fk: Only a->a and b->b are real. The a->b and b->a rows are relationships that aren't actually in the schema. Since the tool's meant for AI reasoning and security auditing, having it report FKs that don't exist felt worth raising. Tracked it down to the FK subquery in pg-meta/tables.sql — the source and target columns are joined independently: ... on sa.attrelid = c.conrelid and sa.attnum = any (c.conkey)
... on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey)any(conkey) against any(confkey) cross-joins every source column with every target column, so an N-column FK gives N² rows. Pairing needs to be positional (conkey[i] with confkey[i]) — unnesting the two arrays together with ordinality keeps them aligned: join lateral unnest(c.conkey, c.confkey) with ordinality as cols(conkey, confkey, ord) on true
join pg_attribute sa on sa.attrelid = c.conrelid and sa.attnum = cols.conkey
join pg_attribute ta on ta.attrelid = c.confrelid and ta.attnum = cols.confkeySame query also backs list_tables (verbose), so this likely affects that path too — this PR just made it visible. Here's a failing test if it helps, drops in next to the existing schema docs test: test('composite foreign key is not a cartesian product', async () => {
// ...same setup, plus the two tables above...
const child = result.data.tables.find((t) => t.full_name === 'public.child');
const pairs = child.foreign_key_constraints.map((f) => `${f.source}=>${f.target}`);
expect(pairs).not.toContain('public.child.a=>public.parent.b');
expect(pairs).not.toContain('public.child.b=>public.parent.a');
expect(child.foreign_key_constraints).toHaveLength(2);
});Also noticed the FK list is the only collection that isn't sorted (tables, policies, triggers, functions all are), so FK order might shift between runs. |
#317) ### What kind of change does this PR introduce? Bug fix: data-integrity issue in `list_tables` (verbose) foreign-key output. ### What is the current behavior? For any composite (multi-column) foreign key, `list_tables` (verbose) returns the **cartesian product** of the source and target columns. An N-column FK produces N² `foreign_key_constraints` entries instead of N : reporting column pairings that do not exist in the schema. Repro: ```sql create table public.parent (a int not null, b int not null, primary key (a, b)); create table public.child ( a int not null, b int not null, constraint child_parent_fk foreign key (a, b) references public.parent (a, b) ); ``` `list_tables` with `verbose: true` returns 4 constraints for `child_parent_fk`: ``` public.child.a => public.parent.a public.child.a => public.parent.b <- does not exist public.child.b => public.parent.a <- does not exist public.child.b => public.parent.b ``` Only `a=>a` and `b=>b` are real. Because this tool exists so an AI agent can reason about database structure, the fabricated relationships are silently trusted - an agent could infer key relationships that don't exist. Root cause is in `pg-meta/tables.sql`. The FK subquery joins source and target columns independently: ```sql ... on sa.attrelid = c.conrelid and sa.attnum = any (c.conkey) ... on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey) ``` `any(conkey)` against `any(confkey)` cross-joins every source column with every target column, instead of pairing them positionally. ### What is the new behavior? Each foreign key is now returned as **one constraint object** with the grouped shape discussed in the review below: ```json { "name": "child_parent_fk", "source_table": "public.child", "source_columns": ["a", "b"], "target_table": "public.parent", "target_columns": ["a", "b"] } ``` - Columns are walked in lockstep with `unnest(conkey, confkey) with ordinality`, so wrong pairings are impossible by construction, and both arrays aggregate ordered by constraint ordinality - `source_columns[i]` pairs with `target_columns[i]`, in constraint-definition order (not attnum, not alphabetical). - `source_table` is kept in the output because `relationships` includes constraints where the listed table is the referenced target. - Single-column FKs are emitted as one-element arrays for a uniform shape. - Adds a regression test using deliberately non-alphabetical column order (`foreign key (b, a) references parent (y, x)`) so any future regression to attnum or name ordering fails immediately. ### Additional context The same `pg-meta` query backs the schema-docs work in #278, so this fix helps that path as well. --------- Co-authored-by: Ankita <anp8729@gmail.com>
What kind of change does this PR introduce?
Feature
What is the current behavior?
Currently, to understand the full structure of a database (tables, columns, RLS policies, triggers, and functions), an AI agent or developer must call multiple tools (e.g.,
list_tableswithverbose: true,execute_sql, etc.) or perform multiple manual queries. There is no single, consolidated way to get a documentation-ready overview of the database schema.Relevant Issue: #277
What is the new behavior?
This PR introduces a new tool,
generate_schema_docs, which provides a comprehensive, documentation-ready summary of the database schema in a single call.Key Features:
markdown(optimized for humans/AI context windows),json(optimized for programmatic use), orboth.databasefeature group and wired into the MCP server runtime.Additional context
server.test.tscovering various output formats and edge cases. All 172 unit tests are passing.