Files
relspecgo/pkg/inspector
Hein 64aeac972a
Some checks failed
CI / Test (1.24) (push) Successful in -25m43s
CI / Test (1.25) (push) Successful in -25m39s
CI / Build (push) Successful in -25m54s
CI / Lint (push) Successful in -25m43s
Integration Tests / Integration Tests (push) Failing after -26m1s
Release / Build and Release (push) Successful in -24m59s
feature: I had an idea
2025-12-31 01:51:37 +02:00
..
2025-12-31 01:40:08 +02:00
2025-12-31 01:40:08 +02:00
2025-12-31 01:40:08 +02:00
2025-12-31 01:40:08 +02:00
2025-12-31 01:40:08 +02:00
2025-12-31 01:51:37 +02:00
2025-12-31 01:40:08 +02:00

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

Quick Start

Basic Usage

# 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.

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

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:

# 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:

# 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:

{
  "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

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

#!/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 for a fully documented example configuration with all available rules and customization options.

Common Use Cases

Enforce Naming Standards

# 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

# 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

# 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

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:

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 for details.