package db import ( "database/sql" "errors" "fmt" "git.wntrmute.dev/mc/mcr/internal/gc" ) // FindAndDeleteUnreferencedBlobs finds all blob rows with no manifest_blobs // entries, deletes them in a single transaction, and returns the digests // and sizes of the deleted blobs. func (d *DB) FindAndDeleteUnreferencedBlobs() ([]gc.UnreferencedBlob, error) { tx, err := d.Begin() if err != nil { return nil, fmt.Errorf("db: begin gc transaction: %w", err) } // Find unreferenced blobs. rows, err := tx.Query( `SELECT b.id, b.digest, b.size FROM blobs b LEFT JOIN manifest_blobs mb ON mb.blob_id = b.id WHERE mb.manifest_id IS NULL`, ) if err != nil { _ = tx.Rollback() return nil, fmt.Errorf("db: find unreferenced blobs: %w", err) } var unreferenced []gc.UnreferencedBlob var ids []int64 for rows.Next() { var id int64 var blob gc.UnreferencedBlob if err := rows.Scan(&id, &blob.Digest, &blob.Size); err != nil { _ = rows.Close() _ = tx.Rollback() return nil, fmt.Errorf("db: scan unreferenced blob: %w", err) } unreferenced = append(unreferenced, blob) ids = append(ids, id) } if err := rows.Err(); err != nil { _ = rows.Close() _ = tx.Rollback() return nil, fmt.Errorf("db: iterate unreferenced blobs: %w", err) } _ = rows.Close() // Delete the unreferenced blob rows. for _, id := range ids { if _, err := tx.Exec(`DELETE FROM blobs WHERE id = ?`, id); err != nil { _ = tx.Rollback() return nil, fmt.Errorf("db: delete blob %d: %w", id, err) } } if err := tx.Commit(); err != nil { return nil, fmt.Errorf("db: commit gc transaction: %w", err) } return unreferenced, nil } // BlobExistsByDigest checks whether a blob row exists for the given digest. func (d *DB) BlobExistsByDigest(digest string) (bool, error) { var count int err := d.QueryRow(`SELECT COUNT(*) FROM blobs WHERE digest = ?`, digest).Scan(&count) if err != nil { if errors.Is(err, sql.ErrNoRows) { return false, nil } return false, fmt.Errorf("db: blob exists by digest: %w", err) } return count > 0, nil }