update
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user