middleware-manager/database/transaction.go
hhftechnologies 9d0b772458
Some checks are pending
Build and Push Docker Image / build-and-push (push) Waiting to run
Tests / Run Tests (push) Waiting to run
Tests / Lint (push) Waiting to run
Tests / Build (push) Blocked by required conditions
Improve error handling, tests, and defaults
Add robust error checks and helpers, improve transaction safety, and refine default templates handling.

- Add api/handlers/test_helpers_test.go with mustUnmarshalResponse and mustScan helpers and update many handler tests to use them or to check json.Unmarshal errors to fail fast.
- Replace direct DB Scan calls in tests with mustScan or explicit error checks to surface query failures.
- Improve transaction rollback handling across codepaths (config handlers, database migrations, transaction utils, CertGenerator) to log rollback errors and avoid silent failures.
- Make WithTransaction/WithTimeoutTransaction/BatchTransaction formatting consistent and add panic-safe rollback logging.
- In main, log a warning instead of ignoring EnsureDefaultDataSources errors.
- Add/cleanup various helper blank assignments and small refactors in services (config_proxy, config_generator) and switch tests to shared response writers (writeJSONResponse/writeResponseBody) where applicable.
- Large cleanup and enhancements in config/defaults.go to preserve string formatting for YAML templates, ensure special fields are quoted/preserved, and tidy whitespace/formatting.

These changes improve test reliability, make DB/tx failures more visible, and ensure default template YAML preserves important string values.
2026-04-15 20:00:48 +05:30

85 lines
2.2 KiB
Go

package database
import (
"context"
"database/sql"
"fmt"
"log"
"time"
)
// TxFn represents a function that uses a transaction
type TxFn func(*sql.Tx) error
// WithTransaction wraps a function with a transaction
func (db *DB) WithTransaction(fn TxFn) error {
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if p := recover(); p != nil {
// Ensure rollback on panic
log.Printf("Recovered from panic in transaction: %v", p)
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Printf("Warning: Rollback failed after panic: %v", rollbackErr)
}
panic(p) // Re-throw panic after rollback
}
}()
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
log.Printf("Warning: Rollback failed: %v (original error: %v)", rbErr, err)
return fmt.Errorf("rollback failed: %v (original error: %w)", rbErr, err)
}
log.Printf("Transaction rolled back due to error: %v", err)
return err
}
if err := tx.Commit(); err != nil {
log.Printf("Error committing transaction: %v", err)
return fmt.Errorf("commit failed: %w", err)
}
return nil
}
// WithTimeoutTransaction wraps a function with a transaction that has a timeout
func (db *DB) WithTimeoutTransaction(ctx context.Context, timeout time.Duration, fn TxFn) error {
// Create a context with timeout
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Create a done channel to signal completion
done := make(chan error, 1)
// Run the transaction in a goroutine
go func() {
done <- db.WithTransaction(fn)
}()
// Wait for either context timeout or transaction completion
select {
case <-ctx.Done():
// Context timed out
return fmt.Errorf("transaction timed out after %v: %w", timeout, ctx.Err())
case err := <-done:
// Transaction completed
return err
}
}
// BatchTransaction executes multiple operations in a single transaction
// All operations must succeed or the transaction is rolled back
func (db *DB) BatchTransaction(operations []TxFn) error {
return db.WithTransaction(func(tx *sql.Tx) error {
for i, op := range operations {
if err := op(tx); err != nil {
return fmt.Errorf("operation %d failed: %w", i, err)
}
}
return nil
})
}