LLM-ready summary of your database
db-snooper turns a database into compact Markdown for SQL agents. A useful profile is not a dump: it keeps the facts needed to understand tables and joins, makes them cheap to retrieve, and omits the rest.
I built db-snooper1 because a text-to-SQL agent kept relearning the same tables, joins, data types, and filter values. A plain schema dump was insufficient, because often column name and type are not enough: status text says much less than active=8,412, cancelled=327.
1. What is in the output.
The database schema summary created by db-snooper is a plain text file with full profile, plus a table of contents. Each table block combines structure with data shape:
# "orders" (rows=128420)
columns:
"id" bigint PK: unique identifier, 1..128420
"status" text: paid=91204, pending=21831, cancelled=15385
"customer_id" bigint FK: 18210 distinct, nulls=41
indexes: ("customer_id", "status")
Blocks can include types, constraints, distributions, indexes, relationships, and samples. Low-cardinality columns get histograms; small tables show every row. Empty and technical tables are skipped. Passwords, hashes, secrets, and tokens are redacted.
This makes the file useful for SQL generation, database exploration, migration review, and debugging without live production access. Because it contains real data, it must be protected like a database export.
2. How it works
The pipeline has three steps:
- Inspect tables, views, columns, keys, and indexes.
- Gather stats for each column according to table size and data shape.
- Render contiguous table blocks and record their line ranges in a table of contents.
For a large database, an agent can read the table of contents (TOC) and load only the relevant blocks. One line per column and a TOC avoid repeating the same facts across DDL, statistics, and samples.
This matters because more context does not guarantee better SQL. In my first benchmark,2 putting the full profile in every prompt used about six times more input tokens without improving accuracy. A later retrieval experiment3 reduced that overhead by loading selected blocks.
3. Handling real-world databases
Profiling adapts to the data instead of applying one statistic everywhere:
- Identifiers get ranges rather than averages; enums get frequencies; high-cardinality text stays in samples.
- SQLite values are checked against declared types, so
numeric→textand strings such as00123survive correctly. - Table size and indexes determine whether counts, medians, distributions, content-shape checks, and JSON inspection run.
- Very large tables use PostgreSQL
pg_stats, MySQLCOLUMN_STATISTICS, or MariaDBmysql.column_statsinstead of full scans. Estimates are marked with≈and(from db stats). - Queries have timeouts. BigQuery queries are dry-run against a byte budget. A failed metric does not abort the profile.
- If reflection fails on views, partial indexes, restricted accounts, or dialect plugins, db-snooper falls back to
pg_dump --schema-onlyormysqldump --no-dataand skips data profiling rather than losing the table.
Delimited identifiers preserve spaces, reserved words, and case. One latest row plus two random rows gives samples some variety without turning the profile into a copy of the database.