Skip to content

Latest commit

 

History

History
354 lines (265 loc) · 25.5 KB

File metadata and controls

354 lines (265 loc) · 25.5 KB

CLI reference

Global options (before the subcommand):

Option Description
-c, --config <FILE> Optional TOML config; merges with flags (see Configuration)
--color auto|always|never Terminal styling
-v, --verbose More log output (repeatable: -v info, -vv debug, -vvv trace with span events)
-q, --quiet Suppresses non-error diagnostics (warning/info/hint) and success notices; errors are still reported

Logging is filtered through RUST_LOG. When RUST_LOG is set to a non-empty value it takes precedence and fully controls per-target filtering (standard tracing EnvFilter syntax), so you can trace a single module, e.g. RUST_LOG=relune_introspect=debug relune doc (with DATABASE_URL set). When RUST_LOG is unset or empty, -v/-q select the level.

Every command requires at least one input. Typical inputs:

Input Flag Notes
SQL file --sql <FILE> DDL
SQL string --sql-text '<DDL>' Not available on lint
Normalized schema JSON --schema-json <FILE> From a previous export
Live DB --db-url <URL> Read-only introspection; PostgreSQL/MySQL/MariaDB use a 30s statement deadline, and remote TCP connections default to verifying TLS (PostgreSQL verify-full, MySQL/MariaDB verify-identity) — set sslmode=require / ssl-mode=required in the URL to keep encryption without certificate verification
SQL dialect --dialect auto|postgres|mysql|sqlite Drives both SQL parsing and relune review rule evaluation. postgres / mysql activate the lock-risk caution rules; sqlite skips them. auto (default) is promoted to the parser-resolved dialect when both inputs agree (so SQL inputs that both look like Postgres run lock-risk under Postgres); when the two sides disagree, auto stays inactive and a REVIEW002 warning is emitted.

Output path: -o / --out writes a file. render and diff still print to stdout when piped, but for interactive terminals they require --stdout before emitting raw SVG/HTML directly. If the --out parent directory does not exist, Relune reports it as a usage error (exit 2) and names the missing directory.

For SQL files and schema JSON files, Relune currently rejects inputs larger than 8 MiB. The limit applies to the bytes actually read, and input paths must be regular files (FIFOs, device files, and directories are rejected).

Warning

Avoid passing a live-DB DSN on the command line. A --db-url value is visible to other users via ps//proc and is recorded in shell history. For render, inspect, doc, export, and lint, when no input flag is given Relune falls back to the DATABASE_URL environment variable, so prefer export DATABASE_URL=... (or a .env loaded by your shell) over --db-url. An explicit --sql/--schema-json/--db-url flag always takes precedence over DATABASE_URL.

--db-url introspection runs catalog queries through a small connection pool. Set RELUNE_DB_POOL_MAX_CONNECTIONS to a positive integer to override the default cap (PostgreSQL/MySQL fan out to 7 connections, SQLite uses 1). Non-positive or non-numeric values are ignored and the default applies.

Beyond the 30s per-statement deadline, the whole catalog fetch is bounded by an overall deadline (default 600s) so backends without an enforceable statement timeout (SQLite, or servers that cannot set a session timeout) cannot run unbounded. Set RELUNE_DB_INTROSPECTION_TIMEOUT_SECS to a positive integer to raise or lower it; invalid values are ignored.

The DSN is fully trusted — Relune connects to exactly the host it names with no destination allow/deny-listing — so do not pass an untrusted URL. Introspection is read-only and needs catalog read access: PostgreSQL reads pg_catalog / information_schema; MySQL/MariaDB needs SELECT on information_schema plus SHOW VIEW to read view definitions (missing it yields empty definitions with a logged warning); SQLite reads the database file.

MySQL/MariaDB URLs must name a database (mysql://user@host:3306/dbname); introspection reads only that database, never other databases the same user can see. A URL without a database path is rejected.


Table patterns

render --include/--exclude, lint --except-table, and review --except-table (plus the matching include/exclude/except_tables config keys) accept glob patterns. A pattern matches when it covers either the schema-qualified name (public.users) or the bare table name (users).

Syntax Matches
* Any run of characters, including none (audit_*, *_log, audit_*_log)
? Exactly one character (log_202?)
[abc], [a-z] One character from the set or range
[!abc], [^abc] One character not in the set
\ Escapes the next character (a\*b matches the literal a*b)

Matching is case-sensitive, and * also spans the . schema separator. Quote patterns in the shell so it does not expand them against local files.


Exit codes

Code Meaning
0 Success. For diff --exit-code / review --exit-code, no changes/findings.
1 General/internal failure (I/O, rasterization, unexpected errors).
2 Usage error: bad flags, no input selected, unknown rule id, a missing --out directory, etc.
3 A diagnostic reached the warning threshold under --fail-on-warning (or --deny warning/--deny info on a parser diagnostic).
10 A configured threshold was reached: diff --exit-code/review --exit-code detected changes, review --deny / lint --deny found a finding/issue at or above the threshold.

lint --deny, review --deny, and diff/review --exit-code all use exit code 10 for "a threshold was reached", so CI can distinguish a policy gate from a general failure (1) or a usage mistake (2).


render

Generate SVG, HTML, or JSON representations of the ERD. SVG/HTML outputs include tables, views, and PostgreSQL enum types. For SQL-defined views, Relune preserves the full view definition and extracts columns from either an explicit CREATE VIEW ... (cols...) list or simple top-level SELECT items; more complex queries may render the view without inferred columns.

Formats (-f / --format): svg (default), html, graph-json, schema-json.

When rendering svg or html without -o, interactive terminals require --stdout; otherwise Relune asks you to choose a file output path or explicitly opt in to raw stdout.

View options:

Option Description
--viewpoint <NAME> Apply a named viewpoint from the active config
--focus <TABLE> Center on a table
--depth <N> Neighbor depth for focus (default 1)
--group-by none|schema|prefix Group tables
--include <PATTERN> Repeatable allowlist (see Table patterns)
--exclude <PATTERN> Repeatable denylist (see Table patterns)
--theme light|dark Visual theme
--layout hierarchical|force-directed Layout algorithm
--direction top-to-bottom|left-to-right|right-to-left|bottom-to-top Primary flow direction
--edge-style straight|orthogonal|curved Edge rendering style

Other: --stats (stderr statistics), --fail-on-warning (non-zero on warnings).

render validates focus/filter combinations before running:

  • --depth requires --focus
  • the same pattern cannot appear in both --include and --exclude

If the include/exclude filters remove the focused table, render fails with focus target table not found after applying include/exclude filters.

relune render --sql schema.sql -o erd.svg
relune render --sql schema.sql --format html -o erd.html
relune render --sql schema.sql --format html --stdout > erd.html
relune render --sql schema.sql --focus orders --depth 2 -o orders.svg
relune render --config relune.toml --sql schema.sql --viewpoint billing -o billing.svg
relune render --sql schema.sql --group-by schema -o grouped.svg
relune render --sql schema.sql --layout force-directed --edge-style orthogonal -o force.svg
relune render --sql schema.sql --include users --include orders -o subset.svg
relune render --schema-json schema.json -o from-json.svg

inspect

Show a schema summary or details for one table.

Option Description
--table <NAME> Table to inspect; omit for summary
--summary Force summary mode
--format text|json Output encoding
-o, --out <FILE> Optional file (else stdout)
--fail-on-warning Return exit code 3 when diagnostics include warnings
relune inspect --sql schema.sql
relune inspect --sql schema.sql --table orders
relune inspect --sql schema.sql --table orders --format json
relune inspect --sql schema.sql --table orders --format json -o inspect.json

doc

Generate Markdown documentation for a schema. Outputs a single Markdown file covering tables, columns, types, constraints, foreign keys, indexes, views, and enums.

Option Description
-o, --out <FILE> Optional file (else stdout)
--fail-on-warning Return exit code 3 when diagnostics include warnings
relune doc --sql schema.sql -o schema.md
relune doc --sql schema.sql
relune doc --db-url 'postgres://user:pass@localhost:5432/mydb' -o schema.md

export

Emit normalized JSON or diagram text. --format is required.

Formats:

Format Description
schema-json Normalized schema as JSON
graph-json Graph representation (nodes/edges) as JSON
layout-json Positioned graph with coordinates plus routing_debug metadata
mermaid Mermaid erDiagram — renders in GitHub/GitLab Markdown
d2 D2 diagram source
dot Graphviz DOT source

Supports --viewpoint, --focus, --depth, --group-by, --include, --exclude, --layout, --direction, and --edge-style like render for graph-backed exports. export applies the same focus/filter validation rules as render, so --depth requires --focus, the same table cannot appear in both --include and --exclude, and the focused table must stay inside the effective include set. --fail-on-warning is also available for CI flows that treat parser diagnostics as failures.

layout-json includes graph-level routing_debug.non_self_loop_detour_activations and per-edge routing_debug fields for source/target side policy, slot indices, slot counts, row offsets, and selected channel coordinates.

relune export --sql schema.sql --format schema-json -o schema.json
relune export --sql schema.sql --format graph-json -o graph.json
relune export --sql schema.sql --format layout-json -o layout.json
relune export --sql schema.sql --format layout-json --layout force-directed --edge-style orthogonal -o layout-force.json
relune export --config relune.toml --sql schema.sql --format schema-json --viewpoint billing -o billing.json
relune export --sql schema.sql --format mermaid -o erd.mmd
relune export --sql schema.sql --format d2 -o erd.d2
relune export --sql schema.sql --format dot -o erd.dot

Routing debug comparison workflow:

relune export --sql schema.sql --format layout-json > layout.json
relune render --sql schema.sql --format svg -o erd.svg
relune render --sql schema.sql --format html -o erd.html

lint

Run built-in rules on the schema. Inputs: --sql, --schema-json, or --db-url (no --sql-text on this command).

Option Description
--format text|json Report format
-o, --out <FILE> Optional file (else stdout)
--profile default|strict Seed rule set for schema review
--rules <RULE> Repeatable; run only these rules
--exclude-rules <RULE> Repeatable; remove rules from the active set
--rule-category <CATEGORY> Repeatable; keep only structure, relationships, naming, documentation
--except-table <PATTERN> Repeatable; suppress issues for matching tables (see Table patterns)
--deny error|warning|info|hint Minimum severity for non-zero exit
--fail-on-warning Shortcut for treating warning diagnostics as failures
relune lint --sql schema.sql
relune lint --sql schema.sql --format json
relune lint --sql schema.sql --format json -o lint.json
relune lint --sql schema.sql --profile strict --rule-category documentation
relune lint --sql schema.sql --deny warning
relune lint --sql schema.sql --rules no-primary-key --rules missing-foreign-key-index
relune lint --sql schema.sql --exclude-rules missing-table-comment --except-table 'audit_*'

Rule IDs are kebab-case (for example missing-foreign-key-index, missing-table-comment, circular-foreign-key). default is the everyday schema review profile; strict additionally enforces column comment coverage. Categories are structure, relationships, naming, and documentation.


diff

Compare two schemas across tables, views, and enum types. Provide before and after inputs independently (each side uses one of the following).

Before: --before <FILE>, --before-sql-text '<DDL>', or --before-schema-json <FILE>.

After: --after <FILE>, --after-sql-text '<DDL>', or --after-schema-json <FILE>.

When --before <FILE> or --after <FILE> is used, Relune inspects the file contents and treats schema JSON as schema JSON even if the extension is not .json.

When rendering the diff as svg or html without -o, interactive terminals require --stdout; text and JSON remain safe on stdout by default.

Option Description
-f, --format text|json|markdown|svg|html Output format
-o, --out <FILE> Optional file (else stdout)
--stdout Explicitly allow raw SVG/HTML on interactive stdout
--dialect For SQL parsing on both sides
--fail-on-warning Return exit code 3 when diagnostics include warnings
--exit-code Return exit code 10 when schema changes are detected (like git diff --exit-code)
--allow-invalid-schema Compare inputs that fail identity validation instead of exiting 1 (see below)

diff and review match objects between the two schemas by name, so they refuse an input with empty or duplicate table, column, view, or enum names (or empty / duplicate table stable_ids) and exit 1 before comparing anything. --allow-invalid-schema downgrades those errors to SCHEMA004 warnings; duplicate objects may then be merged in the output. Two cases are handled earlier and are not affected by the flag: the SQL parser keeps the first of several CREATE TABLE, CREATE VIEW, or CREATE TYPE ... AS ENUM statements for the same object (a later CREATE OR REPLACE VIEW replaces the view instead), skips statements that declare an empty object name, and reports both as parse warnings; and schema JSON import always rejects empty or duplicate ids. Other validation problems, such as foreign keys or indexes that reference missing tables or columns, are always reported as SCHEMA004 warnings, because review rules such as risk/drop-table-referenced analyze exactly those states. render, inspect, export, doc, and lint always report validation errors as warnings.

Before comparing, diff and review treat objects in the default schema as unqualified when either input contains unqualified objects, so hand-written DDL (CREATE TABLE users) matches a schema exported from the database (public.users) instead of reporting every table as removed and re-added. The default schema is public for PostgreSQL, main for SQLite, and the connected database for MySQL (the schema shared by every object). Tables in other schemas, such as auth.users, stay qualified. When both inputs are fully qualified, names are reported as they are.

Within a table, an unnamed foreign key, index, or CHECK written in DDL matches the same constraint under the name the database generated for it (such as orders_user_id_fkey or users_email_key), so it is not reported as dropped and re-added; two differently named constraints stay a rename. Index predicates, expression keys, and CHECK expressions are compared with keyword case ignored, while text inside quoted literals and identifiers must match ('Active' → 'active' is a change). Tables are paired by their stable_id (the id in schema JSON), so renaming a table there while keeping its id is reported as a modification of the same table.

relune diff --before old_schema.sql --after new_schema.sql
relune diff --before old.sql --after new.sql --format json -o diff.json
relune diff --before old.sql --after new.sql --format markdown
relune diff --before old.sql --after new.sql --format html -o diff.html
relune diff --before old.sql --after new.sql --format html --stdout > diff.html
relune diff --before old.sql --after new.sql --exit-code  # exits 10 if changes found
relune --config relune.toml diff --before old.sql --after new.sql

review

Compare a before schema with an after schema and emit migration risk findings: dropped tables, columns, and enum values, dropped references, narrowing type changes, NOT NULL on existing data, missing FK indexes, etc. Findings are grouped into four severity buckets — info < warning < caution < breaking — so --deny can gate CI on the level of risk you are willing to ship.

Before: --before <FILE>, --before-sql-text '<DDL>', or --before-schema-json <FILE>.

After: --after <FILE>, --after-sql-text '<DDL>', or --after-schema-json <FILE>.

Option Description
-f, --format text|markdown|json Output format (default text)
-o, --out <FILE> Optional file (else stdout)
--dialect Drives both SQL parsing and review rule evaluation. postgres / mysql activate the lock-risk caution rules; sqlite skips them. auto (default) is promoted to the parser-resolved dialect when both inputs agree, and stays inactive (with a REVIEW002 warning) when they disagree.
--rules <RULE> Repeatable; run only these rules (accepts risk/<id> or bare <id>)
--except-rule <RULE> Repeatable; remove rules from the active set
--except-table <PATTERN> Repeatable; suppress findings for matching tables (see Table patterns)
--deny info|warning|caution|breaking Exit non-zero when findings reach this severity
--exit-code Exit 10 when any findings are emitted (regardless of severity)
--fail-on-warning Exit 3 when diagnostics include warnings or input coverage is incomplete, such as SQL the parser skipped as unsupported or an input (SQL or schema JSON) with no schema objects; the report and --emit-summary file are still written
--list-rules List every review rule (with default severity and description) and exit; honors --format text|json only
--emit-summary <PATH> Always write the full review JSON (same shape as --format json) to PATH, even when --deny or --fail-on-warning short-circuits (rc=10 / rc=3)
--allow-invalid-schema Review inputs that fail identity validation instead of exiting 1 (same rules as diff)

Rule IDs are kebab-case under the risk/ namespace. The catalog covers sixteen rules:

Rule ID Default severity Dialect
risk/drop-column breaking any
risk/drop-table breaking any
risk/drop-enum-value breaking any
risk/drop-column-referenced breaking any
risk/drop-table-referenced breaking any
risk/add-not-null-on-existing warning any
risk/type-narrow breaking any
risk/drop-pk-or-unique warning (breaking with referencing FK) any
risk/add-unique-on-existing warning any
risk/add-check-on-existing warning any
risk/add-cascade-delete warning any
risk/fk-without-index info any
risk/add-index-on-large-table caution postgres / mysql
risk/add-fk-on-existing caution postgres / mysql
risk/alter-column-type caution postgres / mysql
risk/rewrite-table caution mysql

risk/drop-table and risk/drop-column flag every dropped table and every dropped stored column (generated columns are skipped) because their data is permanently lost, whether or not anything references them. When a foreign key still references the dropped object, risk/drop-table-referenced / risk/drop-column-referenced additionally report that the migration itself will fail. risk/drop-enum-value flags values removed from a named enum type (once per pre-existing column that still uses it) or from an inline ENUM(...) / SET(...) column, because rows holding the removed value fail the migration or lose it. Review compares schema states, so a rename of a table, column, or enum value shows up as a drop plus an add and is reported by these rules too; exclude them with --except-rule / --except-table for intentional renames and cleanups.

risk/add-not-null-on-existing skips a new NOT NULL column that the database fills for existing rows: one with a non-NULL DEFAULT, an identity or generated definition, or an auto-increment / SERIAL type. Changing an existing nullable column to NOT NULL is always reported, because existing NULLs fail regardless of the default.

risk/add-unique-on-existing covers a newly added UNIQUE index, a modified index whose new uniqueness the old definition does not guarantee (it gains UNIQUE, its key is narrowed, or its partial predicate is dropped or changed), and a primary key moved onto existing columns (ADD PRIMARY KEY (email), or a PK rotated from id to email). It stays silent when the table already guaranteed uniqueness over those columns, such as a widened UNIQUE key or a UNIQUE covering the primary key, and when a column added in the same migration is part of the new primary key and numbered by the database (identity, auto-increment, SERIAL). Other added columns give every existing row the same value, so only the pre-existing key columns are judged.

risk/add-check-on-existing reports a table-level CHECK that is added or whose expression changes, and a column-level CHECK added to an existing column, because existing rows may violate it. CHECKs on columns added in the same migration are skipped.

risk/drop-pk-or-unique is breaking when a surviving foreign key, including a self-referencing one such as parent_id -> id, still references the dropped primary key or UNIQUE column set. Neither PostgreSQL nor MySQL changes an FK or index definition in place, so a changed FK or index counts as a new one: risk/add-index-on-large-table and risk/add-fk-on-existing report it on an existing table (including an FK whose only change is its ON DELETE / ON UPDATE action), and risk/fk-without-index checks an FK moved onto other columns for a supporting index.

--rules and --except-rule accept either the fully-qualified form (risk/fk-without-index) or the short form (fk-without-index).

--list-rules is the single source of truth for the rule catalog (CI / docs automation can pipe --format json into jq to enumerate all sixteen rules). --emit-summary is intended for CI pipelines that need to read the structured report even when the user-visible run exits with rc=10 or rc=3 (e.g. PR comment generation in a single pass); reusing the same path as --out is rejected as a usage error.

Review only sees what the parser modeled. When an input produced no schema objects (PARSE004), or the parser skipped constructs it does not support (PARSE002, e.g. an unsupported ALTER TABLE form), the text and markdown reports print a "Review coverage is incomplete" warning above the findings, and the JSON report records it under inputs.before / inputs.after (empty, unsupported_constructs). A run with no findings is then not evidence that the migration is safe. --deny only looks at findings, so add --fail-on-warning (or [review] fail_on_warning = true) to make CI fail on these warnings too. When both apply, --deny takes precedence (exit 10).

Note

Lock-risk caution rules read schema state, not migration SQL. Lock-risk caution rules are based on schema state-change diff, not on the migration SQL itself. They flag a state change that — if executed naively — would acquire a problematic lock; they do not read your migration script and cannot detect that you wrote CREATE INDEX CONCURRENTLY or ALGORITHM=INPLACE. Treat the caution as a "make sure you used the safe variant" reminder.

relune review --before old.sql --after new.sql
relune review --before old.sql --after new.sql --format markdown -o review.md
relune review --before old.sql --after new.sql --format json -o review.json
relune review --before old.sql --after new.sql --deny breaking
relune review --before old.sql --after new.sql --deny breaking --fail-on-warning  # also fail on skipped SQL
relune review --before old.sql --after new.sql --except-rule fk-without-index
relune review --before old.sql --after new.sql --except-table 'audit_*'
relune review --before old.sql --after new.sql --exit-code  # exits 10 if findings exist
relune review --before old.sql --after new.sql --deny breaking --emit-summary review.json
relune review --before old.sql --after new.sql --dialect postgres --deny caution
relune review --list-rules                       # text listing of all 16 rules
relune review --list-rules --format json | jq '.[0]'
relune --config relune.toml review --before old.sql --after new.sql