feat(scripts): 🎉 Add --ignore-errors flag for script execution
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
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.
This commit is contained in:
@@ -14,10 +14,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
scriptsDir string
|
scriptsDir string
|
||||||
scriptsConn string
|
scriptsConn string
|
||||||
scriptsSchemaName string
|
scriptsSchemaName string
|
||||||
scriptsDBName string
|
scriptsDBName string
|
||||||
|
scriptsIgnoreErrors bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var scriptsCmd = &cobra.Command{
|
var scriptsCmd = &cobra.Command{
|
||||||
@@ -62,7 +63,7 @@ var scriptsExecuteCmd = &cobra.Command{
|
|||||||
Long: `Execute SQL scripts from a directory against a PostgreSQL database.
|
Long: `Execute SQL scripts from a directory against a PostgreSQL database.
|
||||||
|
|
||||||
Scripts are executed in order: Priority (ascending), Sequence (ascending), Name (alphabetical).
|
Scripts are executed in order: Priority (ascending), Sequence (ascending), Name (alphabetical).
|
||||||
Execution stops immediately on the first error.
|
By default, execution stops immediately on the first error. Use --ignore-errors to continue execution.
|
||||||
|
|
||||||
The directory is scanned recursively for all subdirectories and files matching the patterns:
|
The directory is scanned recursively for all subdirectories and files matching the patterns:
|
||||||
{priority}_{sequence}_{name}.sql or .pgsql (underscore format)
|
{priority}_{sequence}_{name}.sql or .pgsql (underscore format)
|
||||||
@@ -86,7 +87,12 @@ Examples:
|
|||||||
|
|
||||||
# Execute with SSL disabled
|
# Execute with SSL disabled
|
||||||
relspec scripts execute --dir ./sql \
|
relspec scripts execute --dir ./sql \
|
||||||
--conn "postgres://user:pass@localhost/db?sslmode=disable"`,
|
--conn "postgres://user:pass@localhost/db?sslmode=disable"
|
||||||
|
|
||||||
|
# Continue executing even if errors occur
|
||||||
|
relspec scripts execute --dir ./migrations \
|
||||||
|
--conn "postgres://localhost/mydb" \
|
||||||
|
--ignore-errors`,
|
||||||
RunE: runScriptsExecute,
|
RunE: runScriptsExecute,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +111,7 @@ func init() {
|
|||||||
scriptsExecuteCmd.Flags().StringVar(&scriptsConn, "conn", "", "PostgreSQL connection string (required)")
|
scriptsExecuteCmd.Flags().StringVar(&scriptsConn, "conn", "", "PostgreSQL connection string (required)")
|
||||||
scriptsExecuteCmd.Flags().StringVar(&scriptsSchemaName, "schema", "public", "Schema name (optional, default: public)")
|
scriptsExecuteCmd.Flags().StringVar(&scriptsSchemaName, "schema", "public", "Schema name (optional, default: public)")
|
||||||
scriptsExecuteCmd.Flags().StringVar(&scriptsDBName, "database", "database", "Database name (optional, default: database)")
|
scriptsExecuteCmd.Flags().StringVar(&scriptsDBName, "database", "database", "Database name (optional, default: database)")
|
||||||
|
scriptsExecuteCmd.Flags().BoolVar(&scriptsIgnoreErrors, "ignore-errors", false, "Continue executing scripts even if errors occur")
|
||||||
|
|
||||||
err = scriptsExecuteCmd.MarkFlagRequired("dir")
|
err = scriptsExecuteCmd.MarkFlagRequired("dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,17 +257,39 @@ func runScriptsExecute(cmd *cobra.Command, args []string) error {
|
|||||||
writer := sqlexec.NewWriter(&writers.WriterOptions{
|
writer := sqlexec.NewWriter(&writers.WriterOptions{
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
"connection_string": scriptsConn,
|
"connection_string": scriptsConn,
|
||||||
|
"ignore_errors": scriptsIgnoreErrors,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := writer.WriteSchema(schema); err != nil {
|
if err := writer.WriteSchema(schema); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
return fmt.Errorf("execution failed: %w", err)
|
return fmt.Errorf("script execution failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get execution results from writer metadata
|
||||||
|
totalCount := len(schema.Scripts)
|
||||||
|
successCount := totalCount
|
||||||
|
failedCount := 0
|
||||||
|
|
||||||
|
opts := writer.Options()
|
||||||
|
if total, exists := opts.Metadata["execution_total"].(int); exists {
|
||||||
|
totalCount = total
|
||||||
|
}
|
||||||
|
if success, exists := opts.Metadata["execution_success"].(int); exists {
|
||||||
|
successCount = success
|
||||||
|
}
|
||||||
|
if failed, exists := opts.Metadata["execution_failed"].(int); exists {
|
||||||
|
failedCount = failed
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "\n=== Execution Complete ===\n")
|
fmt.Fprintf(os.Stderr, "\n=== Execution Complete ===\n")
|
||||||
fmt.Fprintf(os.Stderr, "Completed at: %s\n", getCurrentTimestamp())
|
fmt.Fprintf(os.Stderr, "Completed at: %s\n", getCurrentTimestamp())
|
||||||
fmt.Fprintf(os.Stderr, "Successfully executed %d script(s)\n\n", len(schema.Scripts))
|
fmt.Fprintf(os.Stderr, "Total scripts: %d\n", totalCount)
|
||||||
|
fmt.Fprintf(os.Stderr, "Successful: %d\n", successCount)
|
||||||
|
if failedCount > 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed: %d\n", failedCount)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ func NewWriter(options *writers.WriterOptions) *Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// WriteDatabase executes all scripts from all schemas in the database
|
||||||
func (w *Writer) WriteDatabase(db *models.Database) error {
|
func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
@@ -92,6 +97,22 @@ func (w *Writer) executeScripts(ctx context.Context, conn *pgx.Conn, scripts []*
|
|||||||
return nil
|
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)
|
// Sort scripts by Priority (ascending), Sequence (ascending), then Name (ascending)
|
||||||
sortedScripts := make([]*models.Script, len(scripts))
|
sortedScripts := make([]*models.Script, len(scripts))
|
||||||
copy(sortedScripts, scripts)
|
copy(sortedScripts, scripts)
|
||||||
@@ -111,18 +132,49 @@ func (w *Writer) executeScripts(ctx context.Context, conn *pgx.Conn, scripts []*
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalCount++
|
||||||
fmt.Printf("Executing script: %s (Priority=%d, Sequence=%d)\n",
|
fmt.Printf("Executing script: %s (Priority=%d, Sequence=%d)\n",
|
||||||
script.Name, script.Priority, script.Sequence)
|
script.Name, script.Priority, script.Sequence)
|
||||||
|
|
||||||
// Execute the SQL script
|
// Execute the SQL script
|
||||||
_, err := conn.Exec(ctx, script.SQL)
|
_, err := conn.Exec(ctx, script.SQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute script %s (Priority=%d, Sequence=%d): %w",
|
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)
|
script.Name, script.Priority, script.Sequence, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
successCount++
|
||||||
fmt.Printf("✓ Successfully executed: %s\n", script.Name)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user