package store import ( "database/sql" "errors" "fmt" "github.com/google/uuid" ) // CreateVolumeSnapshot inserts a snapshot metadata record. ID is generated // when empty; CreatedAt is stamped server-side. func (s *Store) CreateVolumeSnapshot(v VolumeSnapshot) (VolumeSnapshot, error) { if v.WorkloadID == "" || v.Filename == "" { return VolumeSnapshot{}, fmt.Errorf("volume_snapshot: workload_id and filename are required") } if v.ID == "" { v.ID = uuid.New().String() } if v.Manifest == "" { v.Manifest = "[]" } v.CreatedAt = Now() if _, err := s.db.Exec( `INSERT INTO volume_snapshots (id, workload_id, label, filename, size_bytes, manifest, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, v.ID, v.WorkloadID, v.Label, v.Filename, v.SizeBytes, v.Manifest, v.CreatedAt, ); err != nil { return VolumeSnapshot{}, fmt.Errorf("insert volume snapshot: %w", err) } return v, nil } // GetVolumeSnapshot returns one snapshot by ID. func (s *Store) GetVolumeSnapshot(id string) (VolumeSnapshot, error) { var v VolumeSnapshot err := s.db.QueryRow( `SELECT id, workload_id, label, filename, size_bytes, manifest, created_at FROM volume_snapshots WHERE id = ?`, id, ).Scan(&v.ID, &v.WorkloadID, &v.Label, &v.Filename, &v.SizeBytes, &v.Manifest, &v.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return VolumeSnapshot{}, fmt.Errorf("volume snapshot %s: %w", id, ErrNotFound) } if err != nil { return VolumeSnapshot{}, fmt.Errorf("query volume snapshot: %w", err) } return v, nil } // ListVolumeSnapshots returns a workload's snapshots, newest first. func (s *Store) ListVolumeSnapshots(workloadID string) ([]VolumeSnapshot, error) { rows, err := s.db.Query( `SELECT id, workload_id, label, filename, size_bytes, manifest, created_at FROM volume_snapshots WHERE workload_id = ? ORDER BY created_at DESC`, workloadID, ) if err != nil { return nil, fmt.Errorf("query volume snapshots: %w", err) } defer rows.Close() out := []VolumeSnapshot{} for rows.Next() { v, err := scanVolumeSnapshot(rows) if err != nil { return nil, err } out = append(out, v) } return out, rows.Err() } // DeleteVolumeSnapshot removes one snapshot row by ID. func (s *Store) DeleteVolumeSnapshot(id string) error { result, err := s.db.Exec(`DELETE FROM volume_snapshots WHERE id = ?`, id) if err != nil { return fmt.Errorf("delete volume snapshot: %w", err) } if n, _ := result.RowsAffected(); n == 0 { return fmt.Errorf("volume snapshot %s: %w", id, ErrNotFound) } return nil } // CountVolumeSnapshots returns how many snapshots a workload has. func (s *Store) CountVolumeSnapshots(workloadID string) (int, error) { var n int if err := s.db.QueryRow( `SELECT COUNT(*) FROM volume_snapshots WHERE workload_id = ?`, workloadID, ).Scan(&n); err != nil { return 0, fmt.Errorf("count volume snapshots: %w", err) } return n, nil } // GetOldestVolumeSnapshots returns the N oldest snapshots for a workload, for // retention pruning. func (s *Store) GetOldestVolumeSnapshots(workloadID string, limit int) ([]VolumeSnapshot, error) { rows, err := s.db.Query( `SELECT id, workload_id, label, filename, size_bytes, manifest, created_at FROM volume_snapshots WHERE workload_id = ? ORDER BY created_at ASC LIMIT ?`, workloadID, limit, ) if err != nil { return nil, fmt.Errorf("query oldest volume snapshots: %w", err) } defer rows.Close() out := []VolumeSnapshot{} for rows.Next() { v, err := scanVolumeSnapshot(rows) if err != nil { return nil, err } out = append(out, v) } return out, rows.Err() } // AllVolumeSnapshotFilenames returns every snapshot archive filename across all // workloads, for orphan-file reconciliation at startup. func (s *Store) AllVolumeSnapshotFilenames() ([]string, error) { rows, err := s.db.Query(`SELECT filename FROM volume_snapshots`) if err != nil { return nil, fmt.Errorf("query snapshot filenames: %w", err) } defer rows.Close() out := []string{} for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, fmt.Errorf("scan snapshot filename: %w", err) } out = append(out, name) } return out, rows.Err() } func scanVolumeSnapshot(rows *sql.Rows) (VolumeSnapshot, error) { var v VolumeSnapshot if err := rows.Scan(&v.ID, &v.WorkloadID, &v.Label, &v.Filename, &v.SizeBytes, &v.Manifest, &v.CreatedAt); err != nil { return VolumeSnapshot{}, fmt.Errorf("scan volume snapshot: %w", err) } return v, nil }