172 lines
4.2 KiB
Go
172 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.Script{
|
|
Name: name,
|
|
Description: fmt.Sprintf("SQL script from %s", relPath),
|
|
SQL: string(content),
|
|
Priority: priority,
|
|
Sequence: uint(sequence),
|
|
}
|
|
|
|
scripts = append(scripts, script)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scripts, nil
|
|
}
|