sqldir

package
v1.0.21 Latest Latest
Warning

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

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

README

SQL Directory Reader

The SQL Directory Reader (sqldir) reads SQL scripts from a directory structure and populates the Scripts field of a Schema. It supports recursive directory scanning and extracts priority, sequence, and name information from filenames.

File Naming Convention

Scripts must follow this naming pattern (supports both underscores and hyphens as separators):

{priority}_{sequence}_{name}.{sql|pgsql}
{priority}-{sequence}-{name}.{sql|pgsql}
Components
  • priority: Integer (0-9999) - Defines execution order (lower executes first)
  • sequence: Integer (0-9999) - Defines order within the same priority level
  • separator: Underscore _ or hyphen - (can be mixed)
  • name: Descriptive name (alphanumeric, underscores, hyphens allowed)
  • extension: .sql or .pgsql
Examples
migrations/
├── 1_001_create_schema.sql          # Priority 1, Sequence 1 (underscore format)
├── 1-002-create-users-table.sql     # Priority 1, Sequence 2 (hyphen format)
├── 1_003_create_posts_table.pgsql   # Priority 1, Sequence 3 (underscore format)
├── 2-001-add-indexes.sql            # Priority 2, Sequence 1 (hyphen format)
├── 2_002_add_constraints.sql        # Priority 2, Sequence 2 (underscore format)
├── 10-10-create-newid.pgsql         # Priority 10, Sequence 10 (hyphen format)
└── subdirectory/
    └── 3_001_seed_data.sql          # Priority 3, Sequence 1 (subdirs supported)

Execution Order: 1→2→3→4→5→6→7 (sorted by Priority ascending, then Sequence ascending)

Both formats can be mixed in the same directory - the reader handles both seamlessly.

Invalid Filenames (Ignored)
  • migration.sql - Missing priority/sequence
  • 1_create_users.sql - Missing sequence
  • create_users.sql - Missing priority/sequence
  • 1_001_test.txt - Wrong extension
  • readme.md - Not a SQL file

Usage

Basic Usage
import (
    "git.warky.dev/wdevs/relspecgo/pkg/readers"
    "git.warky.dev/wdevs/relspecgo/pkg/readers/sqldir"
)

reader := sqldir.NewReader(&readers.ReaderOptions{
    FilePath: "/path/to/migrations",
    Metadata: map[string]any{
        "schema_name":   "public",      // Optional, defaults to "public"
        "database_name": "myapp",       // Optional, defaults to "database"
    },
})

// Read all scripts
database, err := reader.ReadDatabase()
if err != nil {
    log.Fatal(err)
}

// Access scripts
for _, schema := range database.Schemas {
    for _, script := range schema.Scripts {
        fmt.Printf("Script: %s (P:%d S:%d)\n",
            script.Name, script.Priority, script.Sequence)
        fmt.Printf("SQL: %s\n", script.SQL)
    }
}
Read Schema Only
schema, err := reader.ReadSchema()
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Found %d scripts\n", len(schema.Scripts))

Features

  • Recursive Directory Scanning: Automatically scans all subdirectories
  • Symlink Skipping: Symbolic links are automatically skipped (prevents loops and duplicates)
  • Multiple Extensions: Supports both .sql and .pgsql files
  • Flexible Naming: Extract metadata from filename patterns
  • Error Handling: Validates directory existence and file accessibility
  • Schema Integration: Scripts are added to the standard RelSpec Schema model

Script Model

Each script is stored as a models.Script:

type Script struct {
    Name        string   // Extracted from filename (e.g., "create_users")
    Description string   // Auto-generated description with file path
    SQL         string   // Complete SQL content from file
    Priority    int      // Execution priority from filename
    Sequence    uint     // Execution sequence from filename
    // ... other fields available but not populated by this reader
}

Integration with SQL Executor

The SQL Directory Reader is designed to work seamlessly with the SQL Executor Writer:

// Read scripts
reader := sqldir.NewReader(&readers.ReaderOptions{
    FilePath: "./migrations",
})
db, _ := reader.ReadDatabase()

// Execute scripts
writer := sqlexec.NewWriter(&writers.WriterOptions{
    Metadata: map[string]any{
        "connection_string": "postgres://localhost/mydb",
    },
})
writer.WriteDatabase(db)  // Executes in Priority→Sequence order

See pkg/writers/sqlexec/README.md for more details on script execution.

Error Handling

The reader will return errors for:

  • Non-existent directory paths
  • Inaccessible directories or files
  • Invalid file permissions
  • File read failures

Files that don't match the naming pattern are silently ignored (not treated as errors).

Testing

Run tests:

go test ./pkg/readers/sqldir/

Tests include:

  • Valid file parsing (underscore and hyphen formats)
  • Recursive directory scanning
  • Symlink skipping
  • Invalid filename handling
  • Empty directory handling
  • Error conditions

Documentation

Overview

Example

Example demonstrates how to read SQL scripts from a directory and execute them

package main

import (
	"fmt"
	"log"

	"git.warky.dev/wdevs/relspecgo/pkg/readers"
	"git.warky.dev/wdevs/relspecgo/pkg/readers/sqldir"
	"git.warky.dev/wdevs/relspecgo/pkg/writers"
	"git.warky.dev/wdevs/relspecgo/pkg/writers/sqlexec"
)

func main() {
	// Step 1: Read SQL scripts from a directory
	// Directory structure example:
	//   migrations/
	//     1_001_create_schema.sql
	//     1_002_create_users_table.sql
	//     1_003_create_posts_table.pgsql
	//     2_001_add_indexes.sql
	//     2_002_seed_data.sql

	reader := sqldir.NewReader(&readers.ReaderOptions{
		FilePath: "/path/to/migrations",
		Metadata: map[string]any{
			"schema_name":   "public",
			"database_name": "myapp",
		},
	})

	// Read the database schema with scripts
	database, err := reader.ReadDatabase()
	if err != nil {
		log.Fatalf("Failed to read scripts: %v", err)
	}

	fmt.Printf("Read %d schemas\n", len(database.Schemas))
	fmt.Printf("Found %d scripts in schema '%s'\n",
		len(database.Schemas[0].Scripts),
		database.Schemas[0].Name)

	// Step 2: Execute the scripts against a PostgreSQL database
	writer := sqlexec.NewWriter(&writers.WriterOptions{
		Metadata: map[string]any{
			"connection_string": "postgres://user:password@localhost:5432/myapp?sslmode=disable",
		},
	})

	// Execute all scripts in Priority then Sequence order
	if err := writer.WriteDatabase(database); err != nil {
		log.Fatalf("Failed to execute scripts: %v", err)
	}

	fmt.Println("All scripts executed successfully!")
}
Example (FileNamingConvention)

Example_fileNamingConvention shows the expected file naming pattern

package main

import (
	"fmt"
)

func main() {
	// File naming pattern: {priority}_{sequence}_{name}.sql or .pgsql
	//                  OR: {priority}-{sequence}-{name}.sql or .pgsql
	//
	// Both underscore (_) and hyphen (-) separators are supported and can be mixed.
	//
	// Components:
	//   - priority: Integer (0-9999) - Scripts with lower priority execute first
	//   - sequence: Integer (0-9999) - Within same priority, lower sequence executes first
	//   - separator: Underscore (_) or hyphen (-)
	//   - name: Descriptive name (alphanumeric, underscores, hyphens)
	//   - extension: .sql or .pgsql
	//
	// Examples (underscore format):
	//   ✓ 1_001_create_users.sql        (Priority=1, Sequence=1)
	//   ✓ 1_002_create_posts.sql        (Priority=1, Sequence=2)
	//   ✓ 2_001_add_indexes.pgsql       (Priority=2, Sequence=1)
	//   ✓ 10_100_migration.sql          (Priority=10, Sequence=100)
	//
	// Examples (hyphen format):
	//   ✓ 1-001-create-users.sql        (Priority=1, Sequence=1)
	//   ✓ 1-002-create-posts.sql        (Priority=1, Sequence=2)
	//   ✓ 2-001-add-indexes.pgsql       (Priority=2, Sequence=1)
	//   ✓ 10-10-create-newid.pgsql      (Priority=10, Sequence=10)
	//
	// Mixed format (both in same directory):
	//   ✓ 1_001_create_users.sql        (underscore format)
	//   ✓ 1-002-create-posts.sql        (hyphen format)
	//   ✓ 2_001_add_indexes.sql         (underscore format)
	//
	// Execution order for mixed examples:
	//   1. 1_001_create_users.sql       (Priority 1, Sequence 1)
	//   2. 1-002-create-posts.sql       (Priority 1, Sequence 2)
	//   3. 2_001_add_indexes.sql        (Priority 2, Sequence 1)
	//
	// Invalid filenames (will be ignored):
	//   ✗ migration.sql                 (missing priority/sequence)
	//   ✗ 1_create_users.sql           (missing sequence)
	//   ✗ create_users.sql             (missing priority/sequence)
	//   ✗ 1_001_create_users.txt       (wrong extension)

	fmt.Println("See comments for file naming conventions")
}
Example (WithSingleSchema)

Example_withSingleSchema shows how to read and execute scripts for a single schema

package main

import (
	"fmt"
	"log"

	"git.warky.dev/wdevs/relspecgo/pkg/readers"
	"git.warky.dev/wdevs/relspecgo/pkg/readers/sqldir"
	"git.warky.dev/wdevs/relspecgo/pkg/writers"
	"git.warky.dev/wdevs/relspecgo/pkg/writers/sqlexec"
)

func main() {
	// Read scripts
	reader := sqldir.NewReader(&readers.ReaderOptions{
		FilePath: "/path/to/migrations",
	})

	schema, err := reader.ReadSchema()
	if err != nil {
		log.Fatalf("Failed to read schema: %v", err)
	}

	// Execute scripts
	writer := sqlexec.NewWriter(&writers.WriterOptions{
		Metadata: map[string]any{
			"connection_string": "postgres://localhost/testdb",
		},
	})

	if err := writer.WriteSchema(schema); err != nil {
		log.Fatalf("Failed to execute scripts: %v", err)
	}

	fmt.Println("Schema scripts executed successfully!")
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Reader

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

Reader implements the readers.Reader interface for SQL script directories

func NewReader

func NewReader(options *readers.ReaderOptions) *Reader

NewReader creates a new SQL directory reader

func (*Reader) ReadDatabase

func (r *Reader) ReadDatabase() (*models.Database, error)

ReadDatabase reads all SQL scripts from a directory into a Database

func (*Reader) ReadSchema

func (r *Reader) ReadSchema() (*models.Schema, error)

ReadSchema reads all SQL scripts from a directory into a Schema

func (*Reader) ReadTable

func (r *Reader) ReadTable() (*models.Table, error)

ReadTable is not applicable for SQL script directories

Jump to

Keyboard shortcuts

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