Files
relspecgo/pkg/readers/sqldir/reader.go
Hein b4ff4334cc
Some checks failed
CI / Lint (push) Successful in -27m53s
CI / Test (1.24) (push) Successful in -27m31s
CI / Build (push) Successful in -28m13s
CI / Test (1.25) (push) Failing after 1m11s
Integration Tests / Integration Tests (push) Failing after -28m15s
feat(models): 🎉 Add GUID field to various models
* Introduced GUID field to Database, Domain, DomainTable, Schema, Table, View, Sequence, Column, Index, Relationship, Constraint, Enum, and Script models.
* Updated initialization functions to assign new GUIDs using uuid package.
* Enhanced DCTX reader and writer to utilize GUIDs from models where available.
2026-01-04 19:53:17 +02:00

170 lines
4.2 KiB
Go

package sqldir
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/readers"
)
// Reader implements the readers.Reader interface for SQL script directories
type Reader struct {
options *readers.ReaderOptions
}
// NewReader creates a new SQL directory reader
func NewReader(options *readers.ReaderOptions) *Reader {
return &Reader{
options: options,
}
}
// ReadDatabase reads all SQL scripts from a directory into a Database
func (r *Reader) ReadDatabase() (*models.Database, error) {
if r.options.FilePath == "" {
return nil, fmt.Errorf("directory path is required")
}
// Check if directory exists
info, err := os.Stat(r.options.FilePath)
if err != nil {
return nil, fmt.Errorf("failed to access directory: %w", err)
}
if !info.IsDir() {
return nil, fmt.Errorf("path is not a directory: %s", r.options.FilePath)
}
// Read scripts from directory
scripts, err := r.readScripts()
if err != nil {
return nil, fmt.Errorf("failed to read scripts: %w", err)
}
// Get schema name from metadata or use default
schemaName := "public"
if name, ok := r.options.Metadata["schema_name"].(string); ok && name != "" {
schemaName = name
}
// Create schema with scripts
schema := &models.Schema{
Name: schemaName,
Scripts: scripts,
}
// Get database name from metadata or use default
dbName := "database"
if name, ok := r.options.Metadata["database_name"].(string); ok && name != "" {
dbName = name
}
// Create database with schema
database := &models.Database{
Name: dbName,
Schemas: []*models.Schema{schema},
}
// Set back-reference
schema.RefDatabase = database
return database, nil
}
// ReadSchema reads all SQL scripts from a directory into a Schema
func (r *Reader) ReadSchema() (*models.Schema, error) {
db, err := r.ReadDatabase()
if err != nil {
return nil, err
}
if len(db.Schemas) == 0 {
return nil, fmt.Errorf("no schema found")
}
return db.Schemas[0], nil
}
// ReadTable is not applicable for SQL script directories
func (r *Reader) ReadTable() (*models.Table, error) {
return nil, fmt.Errorf("ReadTable is not supported for SQL script directories")
}
// readScripts recursively scans the directory for SQL files and parses them into Script models
func (r *Reader) readScripts() ([]*models.Script, error) {
var scripts []*models.Script
// Regular expression to parse filename: {priority}{sep}{sequence}{sep}{name}.sql or .pgsql
// Separator can be underscore (_) or hyphen (-)
// Example: 1_001_create_users.sql -> priority=1, sequence=001, name=create_users
// Example: 2_005_add_indexes.pgsql -> priority=2, sequence=005, name=add_indexes
// Example: 10-10-create-newid.pgsql -> priority=10, sequence=10, name=create-newid
pattern := regexp.MustCompile(`^(\d+)[_-](\d+)[_-](.+)\.(sql|pgsql)$`)
err := filepath.WalkDir(r.options.FilePath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
// Skip directories
if d.IsDir() {
return nil
}
// Get filename
filename := d.Name()
// Match against pattern
matches := pattern.FindStringSubmatch(filename)
if matches == nil {
// Skip files that don't match the pattern
return nil
}
// Parse priority
priority, err := strconv.Atoi(matches[1])
if err != nil {
return fmt.Errorf("invalid priority in filename %s: %w", filename, err)
}
// Parse sequence
sequence, err := strconv.ParseUint(matches[2], 10, 64)
if err != nil {
return fmt.Errorf("invalid sequence in filename %s: %w", filename, err)
}
// Extract name
name := matches[3]
// Read SQL content
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err)
}
// Get relative path from base directory
relPath, err := filepath.Rel(r.options.FilePath, path)
if err != nil {
relPath = path
}
// Create Script model
script := models.InitScript(name)
script.Description = fmt.Sprintf("SQL script from %s", relPath)
script.SQL = string(content)
script.Priority = priority
script.Sequence = uint(sequence)
scripts = append(scripts, script)
return nil
})
if err != nil {
return nil, err
}
return scripts, nil
}