Schema Introspection
Two commands cover the bulk of “what’s in this database?” workflows:
ferrule tables lists tables, and ferrule describe shows column
shapes for one of them. Both work the same way against every backend.
When to use them
- Exploring an unfamiliar database. Drop in, list tables, describe the ones that look interesting. Same flow on Postgres, MySQL, MSSQL, SQLite, or Oracle.
- Scripted schema checks. CI step that asserts a table has the columns it should, or a pre-deploy guard that fails if a migration is missing.
- ORM / codegen scaffolding. Generate model definitions from live introspection rather than hand-rolled SQL.
- Onboarding. A new engineer can
tables+describetheir way around without learning each backend’s metadata catalog by heart.
For the deepest backend-specific introspection (Postgres \d+ with
indexes, constraints, and triggers; MySQL SHOW CREATE TABLE; MSSQL
sys catalogs; etc.) reach for the native shell. Ferrule’s surface is
the cross-backend subset that’s worth unifying.
List tables
ferrule tables demo
JSON output is the default:
[
{"table_name": "customers"},
{"table_name": "events"},
{"table_name": "schema_migrations"}
]
For a terminal-friendly view, add --format table:
ferrule tables demo --format table
The list comes from the backend’s metadata catalog: pg_catalog
(plus information_schema) on Postgres, information_schema on
MySQL, sys.tables on MSSQL, sqlite_schema (formerly
sqlite_master) on SQLite, ALL_TABLES on Oracle. Views and
materialized views are excluded — only base tables.
Describe a table
ferrule describe demo customers
[
{
"column_name": "id",
"data_type": "integer",
"is_nullable": "NO",
"column_default": "nextval('customers_id_seq'::regclass)"
},
{
"column_name": "name",
"data_type": "text",
"is_nullable": "YES"
},
{
"column_name": "signed_up",
"data_type": "timestamp with time zone",
"is_nullable": "YES",
"column_default": "now()"
}
]
Column-level fields are taken from the backend’s catalog. Coverage
varies by backend — column_default is reliably populated on
Postgres / MySQL / MSSQL, less so on SQLite (older versions don’t
expose it via pragma_table_info).
Scripted use with jq
Because the default format is JSON, downstream shell scripts compose naturally:
# All NOT NULL columns in `customers`
ferrule describe demo customers \
| jq '.[] | select(.is_nullable == "NO") | .column_name'
# Tables in the `audit` schema (Postgres-style)
ferrule tables demo \
| jq -r '.[].table_name | select(startswith("audit_"))'
# Quick "does this table have an index_id column?" check
ferrule describe demo events | jq -e '.[] | select(.column_name == "index_id")' \
> /dev/null && echo "yes" || echo "no"
If you need Postgres-style schema-qualified names, use the SQL form
directly — the tables command lists the current schema’s tables,
not all schemas.
What you won’t get
By design, tables and describe expose only a backend-agnostic
subset. The following do not unify well across backends, so ferrule
doesn’t try:
- Indexes, constraints, foreign keys. Use the native shell or a direct SQL query.
- Enum variants (Postgres
CREATE TYPE … AS ENUM, MySQL inlineENUM). - Composite types and ranges (Postgres-specific).
- Generated / computed column expressions beyond the bare
column_defaultfield. - Triggers, stored procedures, sequences. Query the relevant
catalog (
pg_catalog.pg_proc,INFORMATION_SCHEMA.ROUTINES, etc.) withferrule queryfor now.
If a future workflow needs structured access to one of these, file
an issue — the Value enum has the headroom (e.g., Json for
constraint detail), the question is which subset stays cross-
backend.
REPL equivalents
Inside ferrule repl, the meta-commands \dt and \d <table> do
the same thing as the CLI variants:
> \dt
> \d customers
See Interactive REPL.
Reference
Both commands accept the standard output flags (--format,
--output, --limit, --offset, --timing, --verbose) plus
the connection flags (--insecure, --daemon). See
CLI Reference for the full list.