# RelSpec Inspector > Database Schema Validation and Linting Tool The RelSpec Inspector validates database schemas against configurable rules, helping you maintain consistency, enforce naming conventions, and catch common schema design issues across your database models. ## Overview The Inspector reads database schemas from any supported RelSpec format and validates them against a set of configurable rules. It generates detailed reports highlighting violations, warnings, and passed checks. ## Features - **Flexible Rule Configuration**: YAML-based rules with three severity levels (enforce, warn, off) - **Generic Validators**: Reusable regex-based validators for custom naming conventions - **Multiple Input Formats**: Works with all RelSpec readers (PostgreSQL, DBML, JSON, GORM, Bun, etc.) - **Multiple Output Formats**: Markdown with ANSI colors for terminals, JSON for tooling integration - **Smart Defaults**: Works out-of-the-box with sensible default rules - **Terminal-Aware**: Automatic color support detection for improved readability - **Exit Codes**: Proper exit codes for CI/CD integration [Todo List of Features](./TODO.md) ## Quick Start ### Basic Usage ```bash # Inspect a PostgreSQL database with default rules relspec inspect --from pgsql --from-conn "postgres://user:pass@localhost/mydb" # Inspect a DBML file relspec inspect --from dbml --from-path schema.dbml # Inspect with custom rules relspec inspect --from json --from-path db.json --rules my-rules.yaml # Output JSON report to file relspec inspect --from pgsql --from-conn "..." \ --output-format json --output report.json # Inspect specific schema only relspec inspect --from pgsql --from-conn "..." --schema public ``` ### Configuration Create a `.relspec-rules.yaml` file to customize validation rules. If the file doesn't exist, the inspector uses sensible defaults. ```yaml version: "1.0" rules: # Primary key columns must start with "id_" primary_key_naming: enabled: enforce # enforce|warn|off function: primary_key_naming pattern: "^id_" message: "Primary key columns must start with 'id_'" # Foreign key columns must start with "rid_" foreign_key_column_naming: enabled: warn function: foreign_key_column_naming pattern: "^rid_" message: "Foreign key columns should start with 'rid_'" # Table names must be lowercase snake_case table_naming_case: enabled: warn function: table_regexpr # Generic regex validator pattern: "^[a-z][a-z0-9_]*$" message: "Table names should be lowercase with underscores" # Ensure all tables have primary keys missing_primary_key: enabled: warn function: have_primary_key message: "Table is missing a primary key" ``` ## Built-in Validation Rules ### Primary Key Rules | Rule | Function | Description | |------|----------|-------------| | `primary_key_naming` | `primary_key_naming` | Validate PK column names against regex pattern | | `primary_key_datatype` | `primary_key_datatype` | Enforce approved PK data types (bigint, serial, etc.) | | `primary_key_auto_increment` | `primary_key_auto_increment` | Check if PKs have auto-increment enabled | ### Foreign Key Rules | Rule | Function | Description | |------|----------|-------------| | `foreign_key_column_naming` | `foreign_key_column_naming` | Validate FK column names against regex pattern | | `foreign_key_constraint_naming` | `foreign_key_constraint_naming` | Validate FK constraint names against regex pattern | | `foreign_key_index` | `foreign_key_index` | Ensure FK columns have indexes for performance | ### Naming Convention Rules | Rule | Function | Description | |------|----------|-------------| | `table_naming_case` | `table_regexpr` | Generic regex validator for table names | | `column_naming_case` | `column_regexpr` | Generic regex validator for column names | ### Length Rules | Rule | Function | Description | |------|----------|-------------| | `table_name_length` | `table_name_length` | Limit table name length (default: 64 chars) | | `column_name_length` | `column_name_length` | Limit column name length (default: 64 chars) | ### Reserved Keywords | Rule | Function | Description | |------|----------|-------------| | `reserved_keywords` | `reserved_words` | Detect use of SQL reserved keywords as identifiers | ### Schema Integrity Rules | Rule | Function | Description | |------|----------|-------------| | `missing_primary_key` | `have_primary_key` | Ensure tables have primary keys | | `orphaned_foreign_key` | `orphaned_foreign_key` | Detect FKs referencing non-existent tables | | `circular_dependency` | `circular_dependency` | Detect circular FK dependencies | ## Rule Configuration ### Severity Levels Rules support three severity levels: - **`enforce`**: Violations are errors (exit code 1) - **`warn`**: Violations are warnings (exit code 0) - **`off`**: Rule is disabled ### Rule Structure ```yaml rule_name: enabled: enforce|warn|off function: validator_function_name message: "Custom message shown on violation" # Rule-specific parameters pattern: "^regex_pattern$" # For pattern-based validators allowed_types: [type1, type2] # For type validators max_length: 64 # For length validators check_tables: true # For keyword validator check_columns: true # For keyword validator require_index: true # For FK index validator ``` ## Generic Validators The inspector provides generic validator functions that can be reused for custom rules: ### `table_regexpr` Generic regex validator for table names. Create custom table naming rules: ```yaml # Example: Ensure table names don't contain numbers table_no_numbers: enabled: warn function: table_regexpr pattern: "^[a-z_]+$" message: "Table names should not contain numbers" # Example: Tables must start with "tbl_" table_prefix: enabled: enforce function: table_regexpr pattern: "^tbl_[a-z][a-z0-9_]*$" message: "Table names must start with 'tbl_'" ``` ### `column_regexpr` Generic regex validator for column names. Create custom column naming rules: ```yaml # Example: Audit columns must end with "_audit" audit_column_suffix: enabled: enforce function: column_regexpr pattern: ".*_audit$" message: "Audit columns must end with '_audit'" # Example: Timestamp columns must end with "_at" timestamp_suffix: enabled: warn function: column_regexpr pattern: ".*(created|updated|deleted)_at$" message: "Timestamp columns should end with '_at'" ``` ## Output Formats ### Markdown (Default) Human-readable markdown report with ANSI colors when outputting to a terminal: ``` # RelSpec Inspector Report **Database:** my_database **Source Format:** pgsql **Generated:** 2025-12-31T10:30:45Z ## Summary - Rules Checked: 13 - Errors: 2 - Warnings: 5 - Passed: 120 ## Violations ### Errors (2) #### primary_key_naming **Location:** public.users.user_id **Message:** Primary key columns must start with 'id_' **Details:** expected_pattern=^id_ ### Warnings (5) #### foreign_key_index **Location:** public.orders.customer_id **Message:** Foreign key columns should have indexes **Details:** has_index=false ``` ### JSON Structured JSON output for tooling integration: ```json { "summary": { "total_rules": 13, "rules_checked": 13, "error_count": 2, "warning_count": 5, "passed_count": 120 }, "violations": [ { "rule_name": "primary_key_naming", "level": "error", "message": "Primary key columns must start with 'id_'", "location": "public.users.user_id", "context": { "schema": "public", "table": "users", "column": "user_id", "expected_pattern": "^id_" }, "passed": false } ], "generated_at": "2025-12-31T10:30:45Z", "database": "my_database", "source_format": "pgsql" } ``` ## CLI Reference ### Flags | Flag | Type | Description | |------|------|-------------| | `--from` | string | **Required**. Source format (dbml, pgsql, json, yaml, gorm, etc.) | | `--from-path` | string | Source file path (for file-based formats) | | `--from-conn` | string | Connection string (for database formats) | | `--rules` | string | Path to rules YAML file (default: `.relspec-rules.yaml`) | | `--output-format` | string | Output format: `markdown` or `json` (default: `markdown`) | | `--output` | string | Output file path (default: stdout) | | `--schema` | string | Filter to specific schema by name | ### Exit Codes | Code | Meaning | |------|---------| | 0 | Success (no errors, only warnings or all passed) | | 1 | Validation errors found (rules with `enabled: enforce` failed) | | 2 | Runtime error (invalid config, reader error, etc.) | ## CI/CD Integration ### GitHub Actions Example ```yaml name: Schema Validation on: [pull_request] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install RelSpec run: go install git.warky.dev/wdevs/relspecgo/cmd/relspec@latest - name: Validate Schema run: | relspec inspect \ --from dbml \ --from-path schema.dbml \ --rules .relspec-rules.yaml \ --output-format json \ --output validation-report.json - name: Upload Report if: always() uses: actions/upload-artifact@v3 with: name: validation-report path: validation-report.json ``` ### Pre-commit Hook Example ```bash #!/bin/bash # .git/hooks/pre-commit echo "Running schema validation..." relspec inspect \ --from dbml \ --from-path schema.dbml \ --rules .relspec-rules.yaml exit $? ``` ## Example Configuration File See [`.relspec-rules.yaml.example`](../../.relspec-rules.yaml.example) for a fully documented example configuration with all available rules and customization options. ## Common Use Cases ### Enforce Naming Standards ```yaml # Ensure consistent naming across your schema table_naming_case: enabled: enforce function: table_regexpr pattern: "^[a-z][a-z0-9_]*$" message: "Tables must use snake_case" column_naming_case: enabled: enforce function: column_regexpr pattern: "^[a-z][a-z0-9_]*$" message: "Columns must use snake_case" primary_key_naming: enabled: enforce function: primary_key_naming pattern: "^id$" message: "Primary key must be named 'id'" foreign_key_column_naming: enabled: enforce function: foreign_key_column_naming pattern: "^[a-z]+_id$" message: "Foreign keys must end with '_id'" ``` ### Performance Best Practices ```yaml # Ensure optimal database performance foreign_key_index: enabled: enforce function: foreign_key_index require_index: true message: "Foreign keys must have indexes" primary_key_datatype: enabled: enforce function: primary_key_datatype allowed_types: [bigserial, bigint] message: "Use bigserial or bigint for primary keys" ``` ### Schema Integrity ```yaml # Prevent common schema issues missing_primary_key: enabled: enforce function: have_primary_key message: "All tables must have a primary key" orphaned_foreign_key: enabled: enforce function: orphaned_foreign_key message: "Foreign keys must reference existing tables" circular_dependency: enabled: warn function: circular_dependency message: "Circular dependencies detected" ``` ### Avoid Reserved Keywords ```yaml reserved_keywords: enabled: warn function: reserved_words check_tables: true check_columns: true message: "Avoid using SQL reserved keywords" ``` ## Programmatic Usage You can use the inspector programmatically in your Go code: ```go package main import ( "fmt" "git.warky.dev/wdevs/relspecgo/pkg/inspector" "git.warky.dev/wdevs/relspecgo/pkg/models" ) func main() { // Load your database model db := &models.Database{ Name: "my_database", Schemas: []*models.Schema{ // ... your schema }, } // Load rules configuration config, err := inspector.LoadConfig(".relspec-rules.yaml") if err != nil { panic(err) } // Create and run inspector insp := inspector.NewInspector(db, config) report, err := insp.Inspect() if err != nil { panic(err) } // Generate report formatter := inspector.NewMarkdownFormatter(os.Stdout) output, err := formatter.Format(report) if err != nil { panic(err) } fmt.Println(output) // Check for errors if report.HasErrors() { os.Exit(1) } } ``` ## Contributing Contributions are welcome! To add a new validator: 1. Add the validator function to `validators.go` 2. Register it in `inspector.go` `getValidator()` function 3. Add default configuration to `rules.go` `GetDefaultConfig()` 4. Update this README with the new rule documentation ## License Apache License 2.0 - See [LICENSE](../../LICENSE) for details.