transaction

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2026 License: MIT Imports: 5 Imported by: 0

README

Transaction Manager

This package provides transaction management for UniRTM's database operations, ensuring atomicity and consistency across multiple repository operations.

Overview

The transaction manager implements the transaction pattern for SQLite database operations, providing:

  • Atomic Operations: All operations within a transaction either succeed completely or fail completely
  • Transaction-Scoped Repositories: Each transaction provides its own repository instances that operate within the transaction scope
  • Explicit Commit/Rollback: Transactions must be explicitly committed or rolled back
  • Automatic Rollback on Error: Failed operations can trigger rollback to maintain consistency

Architecture

Interfaces
TransactionManager
type TransactionManager interface {
    Begin(ctx context.Context) (Transaction, error)
}

The TransactionManager is responsible for creating new transactions.

Transaction
type Transaction interface {
    Commit() error
    Rollback() error
    InstallationRepo() repository.InstallationRepository
    CacheRepo() repository.CacheRepository
    AuditRepo() repository.AuditRepository
    IndexRepo() repository.IndexRepository
}

The Transaction interface provides:

  • Transaction control methods (Commit, Rollback)
  • Access to transaction-scoped repositories
Implementation

The package provides a SQLite-specific implementation:

  • sqliteTransactionManager: Implements TransactionManager using *sql.DB
  • sqliteTransaction: Implements Transaction using *sql.Tx

Usage

Basic Transaction
// Create transaction manager
tm := transaction.NewSQLiteTransactionManager(db)

// Begin transaction
tx, err := tm.Begin(ctx)
if err != nil {
    return fmt.Errorf("begin transaction: %w", err)
}

// Always ensure rollback on error
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

// Perform operations
installation := &repository.Installation{
    Tool:        "node",
    Version:     "20.0.0",
    Backend:     "github",
    Provider:    "node",
    InstallPath: "/opt/unirtm/node/20.0.0",
    Checksum:    "abc123",
    Metadata:    "{}",
}

err = tx.InstallationRepo().Create(ctx, installation)
if err != nil {
    return fmt.Errorf("create installation: %w", err)
}

// Commit transaction
err = tx.Commit()
if err != nil {
    return fmt.Errorf("commit transaction: %w", err)
}
Multi-Repository Transaction
tx, err := tm.Begin(ctx)
if err != nil {
    return err
}
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

// Create installation
installation := &repository.Installation{...}
err = tx.InstallationRepo().Create(ctx, installation)
if err != nil {
    return err
}

// Log audit entry
auditEntry := &repository.AuditEntry{
    Operation: "install",
    Tool:      installation.Tool,
    Version:   installation.Version,
    Status:    "success",
}
err = tx.AuditRepo().Log(ctx, auditEntry)
if err != nil {
    return err
}

// Update index
indexEntry := &repository.IndexEntry{
    Tool:        installation.Tool,
    Description: "Tool description",
    Backend:     installation.Backend,
}
err = tx.IndexRepo().Upsert(ctx, indexEntry)
if err != nil {
    return err
}

// Commit all operations atomically
return tx.Commit()
Error Handling with Rollback
tx, err := tm.Begin(ctx)
if err != nil {
    return err
}

// Automatic rollback on any error
defer func() {
    if err != nil {
        if rbErr := tx.Rollback(); rbErr != nil {
            log.Printf("rollback failed: %v", rbErr)
        }
    }
}()

// Perform operations...
err = performOperations(tx)
if err != nil {
    return err // Deferred rollback will execute
}

// Explicit commit
return tx.Commit()

Requirements Validation

This implementation validates the following requirements:

  • Requirement 2.8: Use transactions for all write operations to ensure atomicity
  • Requirement 3.3: Support explicit commit operations for multi-step workflows

Design Decisions

Repository Interface Abstraction

The repositories use a DBExecutor interface that is implemented by both *sql.DB and *sql.Tx. This allows:

  • Repositories to work with both regular connections and transactions
  • Transaction-scoped repository instances without code duplication
  • Type-safe transaction boundaries
Explicit Transaction Control

Transactions require explicit Commit() or Rollback() calls:

  • Pros: Clear transaction boundaries, explicit error handling
  • Cons: Requires careful cleanup (use defer for rollback)

This design follows Go best practices and makes transaction boundaries explicit in the code.

Transaction-Scoped Repositories

Each transaction creates its own repository instances:

  • Ensures all operations use the same transaction
  • Prevents accidental mixing of transactional and non-transactional operations
  • Clear ownership of transaction scope

Testing

The package includes comprehensive unit tests covering:

  • Transaction creation and basic operations
  • Commit and rollback behavior
  • Multi-repository atomic operations
  • Error handling and automatic rollback
  • Transaction isolation
  • Context cancellation
  • Edge cases (double commit, double rollback)

Run tests:

go test ./internal/transaction/...

Performance Considerations

  • Prepared Statements: Repositories use prepared statements for performance
  • Connection Pooling: SQLite connection pooling is managed by the database layer
  • WAL Mode: Write-Ahead Logging mode is recommended for better concurrent read performance
  • Transaction Scope: Keep transactions short to minimize lock contention

Future Enhancements

Potential improvements for future iterations:

  1. Savepoints: Support for nested transactions using SQLite savepoints
  2. Read-Only Transactions: Optimize read-only transaction paths
  3. Transaction Retry: Automatic retry logic for transient failures
  4. Transaction Metrics: Instrumentation for transaction duration and success rates
  5. Distributed Transactions: Support for coordinating transactions across multiple databases (if needed)

Documentation

Overview

Example (BasicTransaction)

Example_basicTransaction demonstrates basic transaction usage

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository"
	"github.com/snowdreamtech/unirtm/internal/transaction"
)

func main() {
	// Open database
	db, err := database.Open(context.Background(), database.Config{
		Path:    "/tmp/unirtm.db",
		WALMode: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Create transaction manager
	tm := transaction.NewSQLiteTransactionManager(db.Conn())

	// Begin transaction
	ctx := context.Background()
	tx, err := tm.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Ensure rollback on error
	defer func() {
		if err != nil {
			tx.Rollback()
		}
	}()

	// Create installation
	installation := &repository.Installation{
		Tool:        "node",
		Version:     "20.0.0",
		Backend:     "github",
		Provider:    "node",
		InstallPath: "/opt/unirtm/node/20.0.0",
		Checksum:    "abc123",
		Metadata:    "{}",
	}

	err = tx.InstallationRepo().Create(ctx, installation)
	if err != nil {
		log.Fatal(err)
	}

	// Commit transaction
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Installation created successfully")
}
Example (ContextCancellation)

Example_contextCancellation demonstrates handling context cancellation

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository"
	"github.com/snowdreamtech/unirtm/internal/transaction"
)

func main() {
	db, err := database.Open(context.Background(), database.Config{
		Path:    "/tmp/unirtm.db",
		WALMode: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	tm := transaction.NewSQLiteTransactionManager(db.Conn())

	// Create a context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	tx, err := tm.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}

	defer func() {
		if err != nil {
			tx.Rollback()
		}
	}()

	// Perform operations with the context
	installation := &repository.Installation{
		Tool:        "rust",
		Version:     "1.70.0",
		Backend:     "github",
		Provider:    "rust",
		InstallPath: "/opt/unirtm/rust/1.70.0",
		Checksum:    "jkl012",
		Metadata:    "{}",
	}

	err = tx.InstallationRepo().Create(ctx, installation)
	if err != nil {
		log.Printf("Operation failed: %v", err)
		return
	}

	// Commit before context timeout
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Transaction completed before timeout")
}
Example (ErrorHandlingWithRollback)

Example_errorHandlingWithRollback demonstrates automatic rollback on error

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository"
	"github.com/snowdreamtech/unirtm/internal/transaction"
)

func main() {
	db, err := database.Open(context.Background(), database.Config{
		Path:    "/tmp/unirtm.db",
		WALMode: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	tm := transaction.NewSQLiteTransactionManager(db.Conn())
	ctx := context.Background()

	// Function that performs operations in a transaction
	performInstallation := func() error {
		tx, err := tm.Begin(ctx)
		if err != nil {
			return fmt.Errorf("begin transaction: %w", err)
		}

		// Automatic rollback on any error
		defer func() {
			if err != nil {
				if rbErr := tx.Rollback(); rbErr != nil {
					log.Printf("rollback failed: %v", rbErr)
				}
			}
		}()

		// Create installation
		installation := &repository.Installation{
			Tool:        "go",
			Version:     "1.21.0",
			Backend:     "github",
			Provider:    "go",
			InstallPath: "/opt/unirtm/go/1.21.0",
			Checksum:    "ghi789",
			Metadata:    "{}",
		}

		err = tx.InstallationRepo().Create(ctx, installation)
		if err != nil {
			return fmt.Errorf("create installation: %w", err)
		}

		// Simulate an error condition
		if installation.Version == "1.21.0" {
			return fmt.Errorf("simulated error: version validation failed")
		}

		// This commit will never be reached due to the error above
		return tx.Commit()
	}

	// Call the function
	err = performInstallation()
	if err != nil {
		fmt.Printf("Installation failed (transaction rolled back): %v\n", err)
	}
}
Example (MultiRepositoryTransaction)

Example_multiRepositoryTransaction demonstrates atomic operations across multiple repositories

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/snowdreamtech/unirtm/internal/database"
	"github.com/snowdreamtech/unirtm/internal/repository"
	"github.com/snowdreamtech/unirtm/internal/transaction"
)

func main() {
	db, err := database.Open(context.Background(), database.Config{
		Path:    "/tmp/unirtm.db",
		WALMode: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	tm := transaction.NewSQLiteTransactionManager(db.Conn())
	ctx := context.Background()

	// Begin transaction
	tx, err := tm.Begin(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Automatic rollback on error
	defer func() {
		if err != nil {
			if rbErr := tx.Rollback(); rbErr != nil {
				log.Printf("rollback failed: %v", rbErr)
			}
		}
	}()

	// 1. Create installation
	installation := &repository.Installation{
		Tool:        "python",
		Version:     "3.11.0",
		Backend:     "github",
		Provider:    "python",
		InstallPath: "/opt/unirtm/python/3.11.0",
		Checksum:    "def456",
		Metadata:    "{}",
	}
	err = tx.InstallationRepo().Create(ctx, installation)
	if err != nil {
		log.Fatal(err)
	}

	// 2. Log audit entry
	auditEntry := &repository.AuditEntry{
		Operation: "install",
		Tool:      installation.Tool,
		Version:   installation.Version,
		Status:    "success",
		Duration:  5000,
		Metadata:  "{}",
	}
	err = tx.AuditRepo().Log(ctx, auditEntry)
	if err != nil {
		log.Fatal(err)
	}

	// 3. Update tool index
	indexEntry := &repository.IndexEntry{
		Tool:        installation.Tool,
		Description: "Python programming language",
		Homepage:    "https://python.org",
		License:     "PSF",
		Backend:     installation.Backend,
		Metadata:    "{}",
	}
	err = tx.IndexRepo().Upsert(ctx, indexEntry)
	if err != nil {
		log.Fatal(err)
	}

	// 4. Cache installation metadata
	err = tx.CacheRepo().Set(ctx, "python:3.11.0:metadata", []byte("cached metadata"), 24*time.Hour)
	if err != nil {
		log.Fatal(err)
	}

	// Commit all operations atomically
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Multi-repository transaction completed successfully")
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Transaction

type Transaction interface {
	// Commit commits the transaction
	Commit() error

	// Rollback rolls back the transaction
	Rollback() error

	// InstallationRepo returns the installation repository for this transaction
	InstallationRepo() repository.InstallationRepository

	// CacheRepo returns the cache repository for this transaction
	CacheRepo() repository.CacheRepository

	// AuditRepo returns the audit repository for this transaction
	AuditRepo() repository.AuditRepository

	// IndexRepo returns the index repository for this transaction
	IndexRepo() repository.IndexRepository
}

Transaction represents an active database transaction with repository access Validates Requirements: 2.8 (Use transactions for all write operations), 3.3 (Support explicit commit operations)

type TransactionManager

type TransactionManager interface {
	// Begin starts a new transaction
	Begin(ctx context.Context) (Transaction, error)
}

TransactionManager manages database transactions Validates Requirements: 2.8 (Use transactions for all write operations)

func NewSQLiteTransactionManager

func NewSQLiteTransactionManager(db *sql.DB) TransactionManager

NewSQLiteTransactionManager creates a new SQLite transaction manager

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL