Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions cmd/server/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,60 @@ func (db *DB) GetObservationsForHash(hash string) []map[string]interface{} {
return obsByTx[txID]
}

// ObservationRawHexForHash returns the stored wire bytes per observation id for
// one transmission, keyed by observations.id. Empty when the schema has no
// observations.raw_hex column (#881 made it optional) or nothing is stored.
//
// Why this is read on demand instead of held in memory (upstream #1999): the
// store deliberately does not retain obs.RawHex (#881, ~98MB on a
// ~1.7M-observation store), on the belief that one content hash implies one
// frame. It does not: the firmware hashes payload and type independently of the
// relay path, so observations of one transmission legitimately carry different
// bytes. Keeping the memory saving and paying one query on the packet-detail
// path, which is a single packet a human is looking at, is the trade this makes.
//
// Two indexed lookups regardless of observation count: transmissions.hash,
// then observations.transmission_id. Which index SQLite picks is the planner's
// choice; on the migrated e2e fixture EXPLAIN QUERY PLAN shows the UNIQUE
// autoindex sqlite_autoindex_transmissions_1 and the composite
// idx_observations_tx_ts (idx_transmissions_hash and
// idx_observations_transmission_id also cover these columns).
func (db *DB) ObservationRawHexForHash(hash string) (map[int]string, error) {
if db == nil || db.conn == nil || !db.hasObsRawHex() || hash == "" {
return nil, nil
}
var txID int
if err := db.conn.QueryRow("SELECT id FROM transmissions WHERE hash = ?",
strings.ToLower(hash)).Scan(&txID); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, fmt.Errorf("lookup transmission for observation frames: %w", err)
}
rows, err := db.conn.Query(
`SELECT id, raw_hex FROM observations
WHERE transmission_id = ? AND raw_hex IS NOT NULL AND raw_hex <> ''`, txID)
if err != nil {
return nil, fmt.Errorf("query observation frames: %w", err)
}
defer rows.Close()
out := make(map[int]string)
for rows.Next() {
var id int
var hx sql.NullString
if err := rows.Scan(&id, &hx); err != nil {
return nil, fmt.Errorf("scan observation frame: %w", err)
}
if hx.Valid && hx.String != "" {
out[id] = hx.String
}
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate observation frames: %w", err)
}
return out, nil
}

// GetNodes returns filtered, paginated node list.
func (db *DB) GetNodes(limit, offset int, role, search, before, lastHeard, sortBy, region string) ([]map[string]interface{}, int, map[string]int, error) {
var where []string
Expand Down
Loading
Loading