inspector

package
v1.0.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 10, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Version string          `yaml:"version"`
	Rules   map[string]Rule `yaml:"rules"`
}

Config represents the inspector rules configuration

func GetDefaultConfig

func GetDefaultConfig() *Config

GetDefaultConfig returns the default inspector configuration All rules are enabled at "warn" level by default

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig loads configuration from a YAML file If the file doesn't exist, returns default configuration If the file exists but is invalid, returns an error

type Inspector

type Inspector struct {
	// contains filtered or unexported fields
}

Inspector performs validation on database models

func NewInspector

func NewInspector(db *models.Database, config *Config) *Inspector

NewInspector creates a new inspector with the given database and configuration

func (*Inspector) Inspect

func (i *Inspector) Inspect() (*InspectorReport, error)

Inspect runs all enabled validation rules and returns a report

type InspectorReport

type InspectorReport struct {
	Summary      ReportSummary      `json:"summary"`
	Violations   []ValidationResult `json:"violations"`
	GeneratedAt  time.Time          `json:"generated_at"`
	Database     string             `json:"database"`
	SourceFormat string             `json:"source_format"`
}

InspectorReport contains the complete validation report

func (*InspectorReport) HasErrors

func (r *InspectorReport) HasErrors() bool

HasErrors returns true if the report contains any errors

type JSONFormatter

type JSONFormatter struct{}

JSONFormatter formats reports as JSON

func NewJSONFormatter

func NewJSONFormatter() *JSONFormatter

NewJSONFormatter creates a JSON formatter

func (*JSONFormatter) Format

func (f *JSONFormatter) Format(report *InspectorReport) (string, error)

Format generates a JSON report

type MarkdownFormatter

type MarkdownFormatter struct {
	UseColors bool
}

MarkdownFormatter formats reports as markdown

func NewMarkdownFormatter

func NewMarkdownFormatter(writer io.Writer) *MarkdownFormatter

NewMarkdownFormatter creates a markdown formatter with color support detection

func (*MarkdownFormatter) Format

func (f *MarkdownFormatter) Format(report *InspectorReport) (string, error)

Format generates a markdown report

type ReportFormatter

type ReportFormatter interface {
	Format(report *InspectorReport) (string, error)
}

ReportFormatter defines the interface for report formatters

type ReportSummary

type ReportSummary struct {
	TotalRules   int `json:"total_rules"`
	RulesChecked int `json:"rules_checked"`
	RulesSkipped int `json:"rules_skipped"`
	ErrorCount   int `json:"error_count"`
	WarningCount int `json:"warning_count"`
	PassedCount  int `json:"passed_count"`
}

ReportSummary contains aggregate statistics

type Rule

type Rule struct {
	Enabled              string   `yaml:"enabled"`  // "enforce", "warn", "off"
	Function             string   `yaml:"function"` // validator function name
	Message              string   `yaml:"message"`
	Pattern              string   `yaml:"pattern,omitempty"`
	AllowedTypes         []string `yaml:"allowed_types,omitempty"`
	MaxLength            int      `yaml:"max_length,omitempty"`
	Case                 string   `yaml:"case,omitempty"`
	RequireIndex         bool     `yaml:"require_index,omitempty"`
	CheckTables          bool     `yaml:"check_tables,omitempty"`
	CheckColumns         bool     `yaml:"check_columns,omitempty"`
	RequireAutoIncrement bool     `yaml:"require_auto_increment,omitempty"`
}

Rule represents a single validation rule

func (*Rule) IsEnabled

func (r *Rule) IsEnabled() bool

IsEnabled returns true if the rule is enabled (either "enforce" or "warn")

func (*Rule) IsEnforced

func (r *Rule) IsEnforced() bool

IsEnforced returns true if the rule is set to "enforce" level

type ValidationResult

type ValidationResult struct {
	RuleName string                 `json:"rule_name"`
	Level    string                 `json:"level"` // "error" or "warning"
	Message  string                 `json:"message"`
	Location string                 `json:"location"` // e.g., "schema.table.column"
	Context  map[string]interface{} `json:"context"`
	Passed   bool                   `json:"passed"`
}

ValidationResult represents the result of a single validation check

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL