chore: release v0.26.2

This commit is contained in:
2026-08-21 18:21:33 +02:00
parent c49faf9a14
commit 0cd243fe00
28 changed files with 1600 additions and 345 deletions
+38 -2
View File
@@ -2301,7 +2301,7 @@ func (r *Repo) IterateAll(ctx context.Context, fn func(QSO) error) error {
const awardCols = `id, callsign, qso_date, band, freq_hz, mode, ` +
`grid, vucc_grids, country, state, cnty, cont, cqz, ituz, dxcc, iota, sota_ref, pota_ref, ` +
`name, qth, address, comment, notes, ` +
`qsl_rcvd, lotw_rcvd, eqsl_rcvd, extras_json`
`qsl_rcvd, lotw_rcvd, eqsl_rcvd, extras_json, award_refs`
// IterateForAwards streams a lightweight projection of every QSO — only the
// fields award computation reads (see awardCols). All other QSO fields are left
@@ -2326,6 +2326,37 @@ func (r *Repo) IterateForAwards(ctx context.Context, fn func(QSO) error) error {
return rows.Err()
}
// IterateForAwardsWithRef streams only the QSOs whose stored award_refs already
// mention one award code.
//
// For a change that RELABELS an award rather than re-matching it — the grid
// column switching from the reference to its description — the rows that can
// possibly change are exactly these. On a 30 000-QSO log with 2 488 French
// departments in it, that is the difference between reading the whole logbook
// and reading a twelfth of it.
//
// Matched with LIKE on the JSON key, which is exact enough: award_refs is a
// compact object written by us, so a code appears as "DDFM": and nowhere else.
func (r *Repo) IterateForAwardsWithRef(ctx context.Context, code string, fn func(QSO) error) error {
like := "%\"" + strings.ToUpper(strings.TrimSpace(code)) + "\":%"
rows, err := r.db.QueryContext(ctx,
`SELECT `+awardCols+` FROM qso WHERE award_refs LIKE ? ORDER BY qso_date ASC, id ASC`, like)
if err != nil {
return fmt.Errorf("query qso (awards, one code): %w", err)
}
defer rows.Close()
for rows.Next() {
q, err := scanAwardQSO(rows)
if err != nil {
return err
}
if err := fn(q); err != nil {
return err
}
}
return rows.Err()
}
// scanAwardQSO reads one row produced by awardCols into a QSO, populating only
// the award-relevant fields. Column order MUST match awardCols.
func scanAwardQSO(s scanner) (QSO, error) {
@@ -2341,16 +2372,21 @@ func scanAwardQSO(s scanner) (QSO, error) {
comment, notes sql.NullString
qslRcvd, lotwRcvd, eqslRcvd sql.NullString
extrasJSON sql.NullString
awardRefs sql.NullString
)
if err := s.Scan(
&q.ID, &q.Callsign, &qsoDateStr, &q.Band, &freqHz, &q.Mode,
&grid, &vucc, &country, &state, &cnty, &cont, &cqz, &ituz, &dxcc, &iotaRef, &sota, &pota,
&name, &qth, &address, &comment, &notes,
&qslRcvd, &lotwRcvd, &eqslRcvd, &extrasJSON,
&qslRcvd, &lotwRcvd, &eqslRcvd, &extrasJSON, &awardRefs,
); err != nil {
return QSO{}, fmt.Errorf("scan qso (awards): %w", err)
}
q.QSODate = parseTimeLoose(qsoDateStr)
// award_refs rides along so a recompute can compare what it produced against
// what is stored WITHOUT falling back to the full-record scan — the whole
// point of this projection.
q.AwardRefs = awardRefs.String
if freqHz.Valid {
v := freqHz.Int64
q.FreqHz = &v