All checks were successful
CI / Test (1.24) (push) Successful in -26m18s
CI / Test (1.25) (push) Successful in -26m14s
CI / Build (push) Successful in -26m38s
CI / Lint (push) Successful in -26m30s
Release / Build and Release (push) Successful in -26m27s
Integration Tests / Integration Tests (push) Successful in -26m10s
- Allow continued execution of scripts even if errors occur. - Update execution summary to include counts of successful and failed scripts. - Enhance error handling and reporting for better visibility.
181 lines
4.9 KiB
Go
181 lines
4.9 KiB
Go
package sqlexec
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
|
)
|
|
|
|
// Writer implements the writers.Writer interface for executing SQL scripts
|
|
type Writer struct {
|
|
options *writers.WriterOptions
|
|
}
|
|
|
|
// NewWriter creates a new SQL executor writer
|
|
func NewWriter(options *writers.WriterOptions) *Writer {
|
|
return &Writer{
|
|
options: options,
|
|
}
|
|
}
|
|
|
|
// Options returns the writer options (useful for reading execution results)
|
|
func (w *Writer) Options() *writers.WriterOptions {
|
|
return w.options
|
|
}
|
|
|
|
// WriteDatabase executes all scripts from all schemas in the database
|
|
func (w *Writer) WriteDatabase(db *models.Database) error {
|
|
if db == nil {
|
|
return fmt.Errorf("database is nil")
|
|
}
|
|
|
|
// Get connection string from metadata
|
|
connString, ok := w.options.Metadata["connection_string"].(string)
|
|
if !ok || connString == "" {
|
|
return fmt.Errorf("connection_string is required in writer metadata")
|
|
}
|
|
|
|
// Connect to database
|
|
ctx := context.Background()
|
|
conn, err := pgx.Connect(ctx, connString)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to database: %w", err)
|
|
}
|
|
defer conn.Close(ctx)
|
|
|
|
// Execute scripts from all schemas
|
|
for _, schema := range db.Schemas {
|
|
if err := w.executeScripts(ctx, conn, schema.Scripts); err != nil {
|
|
return fmt.Errorf("failed to execute scripts from schema %s: %w", schema.Name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteSchema executes all scripts from a single schema
|
|
func (w *Writer) WriteSchema(schema *models.Schema) error {
|
|
if schema == nil {
|
|
return fmt.Errorf("schema is nil")
|
|
}
|
|
|
|
// Get connection string from metadata
|
|
connString, ok := w.options.Metadata["connection_string"].(string)
|
|
if !ok || connString == "" {
|
|
return fmt.Errorf("connection_string is required in writer metadata")
|
|
}
|
|
|
|
// Connect to database
|
|
ctx := context.Background()
|
|
conn, err := pgx.Connect(ctx, connString)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to database: %w", err)
|
|
}
|
|
defer conn.Close(ctx)
|
|
|
|
// Execute scripts
|
|
if err := w.executeScripts(ctx, conn, schema.Scripts); err != nil {
|
|
return fmt.Errorf("failed to execute scripts: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteTable is not applicable for SQL script execution
|
|
func (w *Writer) WriteTable(table *models.Table) error {
|
|
return fmt.Errorf("WriteTable is not supported for SQL script execution")
|
|
}
|
|
|
|
// executeScripts executes scripts in Priority, Sequence, then Name order
|
|
func (w *Writer) executeScripts(ctx context.Context, conn *pgx.Conn, scripts []*models.Script) error {
|
|
if len(scripts) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Check if we should ignore errors
|
|
ignoreErrors := false
|
|
if val, ok := w.options.Metadata["ignore_errors"].(bool); ok {
|
|
ignoreErrors = val
|
|
}
|
|
|
|
// Track failed scripts and execution counts
|
|
var failedScripts []struct {
|
|
name string
|
|
priority int
|
|
sequence uint
|
|
err error
|
|
}
|
|
successCount := 0
|
|
totalCount := 0
|
|
|
|
// Sort scripts by Priority (ascending), Sequence (ascending), then Name (ascending)
|
|
sortedScripts := make([]*models.Script, len(scripts))
|
|
copy(sortedScripts, scripts)
|
|
sort.Slice(sortedScripts, func(i, j int) bool {
|
|
if sortedScripts[i].Priority != sortedScripts[j].Priority {
|
|
return sortedScripts[i].Priority < sortedScripts[j].Priority
|
|
}
|
|
if sortedScripts[i].Sequence != sortedScripts[j].Sequence {
|
|
return sortedScripts[i].Sequence < sortedScripts[j].Sequence
|
|
}
|
|
return sortedScripts[i].Name < sortedScripts[j].Name
|
|
})
|
|
|
|
// Execute each script in order
|
|
for _, script := range sortedScripts {
|
|
if script.SQL == "" {
|
|
continue
|
|
}
|
|
|
|
totalCount++
|
|
fmt.Printf("Executing script: %s (Priority=%d, Sequence=%d)\n",
|
|
script.Name, script.Priority, script.Sequence)
|
|
|
|
// Execute the SQL script
|
|
_, err := conn.Exec(ctx, script.SQL)
|
|
if err != nil {
|
|
if ignoreErrors {
|
|
fmt.Printf("⚠ Error executing %s: %v (continuing due to --ignore-errors)\n", script.Name, err)
|
|
failedScripts = append(failedScripts, struct {
|
|
name string
|
|
priority int
|
|
sequence uint
|
|
err error
|
|
}{
|
|
name: script.Name,
|
|
priority: script.Priority,
|
|
sequence: script.Sequence,
|
|
err: err,
|
|
})
|
|
continue
|
|
}
|
|
return fmt.Errorf("script %s (Priority=%d, Sequence=%d): %w",
|
|
script.Name, script.Priority, script.Sequence, err)
|
|
}
|
|
|
|
successCount++
|
|
fmt.Printf("✓ Successfully executed: %s\n", script.Name)
|
|
}
|
|
|
|
// Store execution results in metadata for caller
|
|
w.options.Metadata["execution_total"] = totalCount
|
|
w.options.Metadata["execution_success"] = successCount
|
|
w.options.Metadata["execution_failed"] = len(failedScripts)
|
|
|
|
// Print summary of failed scripts if any
|
|
if len(failedScripts) > 0 {
|
|
fmt.Printf("\n⚠ Failed Scripts Summary (%d failed):\n", len(failedScripts))
|
|
for i, failed := range failedScripts {
|
|
fmt.Printf(" %d. %s (Priority=%d, Sequence=%d)\n Error: %v\n",
|
|
i+1, failed.name, failed.priority, failed.sequence, failed.err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|