This commit is contained in:
2026-05-28 08:48:41 +02:00
parent 28da6f6165
commit a8b7622667
14 changed files with 2702 additions and 35 deletions
+72
View File
@@ -800,6 +800,78 @@ func sortStrings(s []string) {
}
}
// IterateAll streams every QSO in the database through fn, ordered by
// qso_date ascending so an ADIF export is chronological. Constant memory
// regardless of table size — the alternative (loading all 25k+ rows into
// a slice) wastes ~20MB for no good reason.
func (r *Repo) IterateAll(ctx context.Context, fn func(QSO) error) error {
rows, err := r.db.QueryContext(ctx,
`SELECT `+selectCols+` FROM qso ORDER BY qso_date ASC, id ASC`)
if err != nil {
return fmt.Errorf("query qso: %w", err)
}
defer rows.Close()
for rows.Next() {
q, err := scanQSO(rows)
if err != nil {
return err
}
if err := fn(q); err != nil {
return err
}
}
return rows.Err()
}
// EntitySlot bundles every (band, mode) tuple ever worked for a given
// DXCC entity name. Used by the cluster spot colouring code to decide
// NEW / NEW SLOT / WORKED in constant time after one batched query.
type EntitySlot struct {
Country string
Bands map[string]struct{} // bands worked, any mode
Slots map[string]map[string]struct{} // band → modes worked
}
// EntitySlotMap returns slot data for every QSO grouped by lowercase
// country name (cty.dat-style key). Cheap on a 25k-row table: one
// scan, no joins. Callers can compare a spot's entity to this map to
// decide if it's NEW / NEW SLOT / WORKED.
func (r *Repo) EntitySlotMap(ctx context.Context) (map[string]*EntitySlot, error) {
rows, err := r.db.QueryContext(ctx,
`SELECT lower(country), lower(band), upper(mode) FROM qso
WHERE country IS NOT NULL AND country != ''
AND band IS NOT NULL AND band != ''
AND mode IS NOT NULL AND mode != ''`)
if err != nil {
return nil, err
}
defer rows.Close()
out := make(map[string]*EntitySlot, 256)
for rows.Next() {
var country, band, mode string
if err := rows.Scan(&country, &band, &mode); err != nil {
return nil, err
}
e, ok := out[country]
if !ok {
e = &EntitySlot{
Country: country,
Bands: make(map[string]struct{}),
Slots: make(map[string]map[string]struct{}),
}
out[country] = e
}
e.Bands[band] = struct{}{}
bandSlots, ok := e.Slots[band]
if !ok {
bandSlots = make(map[string]struct{})
e.Slots[band] = bandSlots
}
bandSlots[mode] = struct{}{}
}
return out, rows.Err()
}
// Count returns the total number of QSOs in the database.
func (r *Repo) Count(ctx context.Context) (int64, error) {
var n int64