package database import ( "context" "database/sql" "fmt" ) // Repositories provides access to all database repositories. // It serves as a centralized access point for all database operations // and manages transaction coordination across repositories. type Repositories struct { db *DB Files *FileRepository Chunks *ChunkRepository Blobs *BlobRepository FileChunks *FileChunkRepository BlobChunks *BlobChunkRepository ChunkFiles *ChunkFileRepository Snapshots *SnapshotRepository Uploads *UploadRepository } // NewRepositories creates a new Repositories instance with all repository types. // Each repository shares the same database connection for coordinated transactions. func NewRepositories(db *DB) *Repositories { return &Repositories{ db: db, Files: NewFileRepository(db), Chunks: NewChunkRepository(db), Blobs: NewBlobRepository(db), FileChunks: NewFileChunkRepository(db), BlobChunks: NewBlobChunkRepository(db), ChunkFiles: NewChunkFileRepository(db), Snapshots: NewSnapshotRepository(db), Uploads: NewUploadRepository(db.conn), } } // TxFunc is a function that executes within a database transaction. // The transaction is automatically committed if the function returns nil, // or rolled back if it returns an error. type TxFunc func(ctx context.Context, tx *sql.Tx) error // WithTx executes a function within a write transaction. // SQLite handles its own locking internally, so no explicit locking is needed. // The transaction is automatically committed on success or rolled back on error. // This method should be used for all write operations to ensure atomicity. func (r *Repositories) WithTx(ctx context.Context, fn TxFunc) error { LogSQL("WithTx", "Beginning transaction", "") tx, err := r.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("beginning transaction: %w", err) } LogSQL("WithTx", "Transaction started", "") defer func() { if p := recover(); p != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { Fatal("failed to rollback transaction: %v", rollbackErr) } panic(p) } else if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { Fatal("failed to rollback transaction: %v", rollbackErr) } } }() err = fn(ctx, tx) if err != nil { return err } return tx.Commit() } // WithReadTx executes a function within a read-only transaction. // Read transactions can run concurrently with other read transactions // but will be blocked by write transactions. The transaction is // automatically committed on success or rolled back on error. func (r *Repositories) WithReadTx(ctx context.Context, fn TxFunc) error { opts := &sql.TxOptions{ ReadOnly: true, } tx, err := r.db.BeginTx(ctx, opts) if err != nil { return fmt.Errorf("beginning read transaction: %w", err) } defer func() { if p := recover(); p != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { Fatal("failed to rollback transaction: %v", rollbackErr) } panic(p) } else if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { Fatal("failed to rollback transaction: %v", rollbackErr) } } }() err = fn(ctx, tx) if err != nil { return err } return tx.Commit() }