check / check (push) Waiting to run
WithReadTx ended the process when its rollback failed, including after Ctrl-C, when database/sql has already rolled the transaction back and Rollback returns sql.ErrTxDone. It now handles the rollback as WithTx does. The query functions ended the process when closing their rows failed; they now ignore the deferred close, because database/sql reports a close failure through rows.Err(), which each returns, once the rows are read to the end. The unused database.Fatalf is removed. cleanupBlobTempFile returns its close or remove failure and the upload step returns it, so such a failure now fails the backup. No test makes a rows close fail: SQLite fails it only with the error the last read already returned. Model: opus-5-5
61 lines
1.5 KiB
Go
61 lines
1.5 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// List returns every chunk in the index, ordered by chunk hash.
|
|
func (r *ChunkRepository) List(ctx context.Context) ([]*Chunk, error) {
|
|
return r.list(ctx, `
|
|
SELECT chunk_hash, size
|
|
FROM chunks
|
|
ORDER BY chunk_hash
|
|
`)
|
|
}
|
|
|
|
// ListInUploadedBlobs returns the chunks that are stored in a blob whose
|
|
// upload has completed (uploaded_ts set), ordered by chunk hash. These
|
|
// are the only chunks a backup may safely deduplicate against: a chunk
|
|
// recorded solely in a blob that was never uploaded refers to data that
|
|
// is not in remote storage, so trusting it would silently drop that data
|
|
// from later snapshots.
|
|
func (r *ChunkRepository) ListInUploadedBlobs(ctx context.Context) ([]*Chunk, error) {
|
|
return r.list(ctx, `
|
|
SELECT DISTINCT c.chunk_hash, c.size
|
|
FROM chunks c
|
|
JOIN blob_chunks bc ON c.chunk_hash = bc.chunk_hash
|
|
JOIN blobs b ON bc.blob_id = b.id
|
|
WHERE b.uploaded_ts IS NOT NULL
|
|
ORDER BY c.chunk_hash
|
|
`)
|
|
}
|
|
|
|
// list runs a chunk-selecting query and scans the (chunk_hash, size) rows.
|
|
func (r *ChunkRepository) list(ctx context.Context, query string) ([]*Chunk, error) {
|
|
rows, err := r.db.conn.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("querying chunks: %w", err)
|
|
}
|
|
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
var chunks []*Chunk
|
|
|
|
for rows.Next() {
|
|
var chunk Chunk
|
|
|
|
err := rows.Scan(
|
|
&chunk.ChunkHash,
|
|
&chunk.Size,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scanning chunk: %w", err)
|
|
}
|
|
|
|
chunks = append(chunks, &chunk)
|
|
}
|
|
|
|
return chunks, rows.Err()
|
|
}
|