Files
beacon-server/db/sqlc/queries.sql.go
T
MrAlders0n ff8758b932 fix(db): read observer activity raw rows back to the rollup watermark
Hourly activity read raw rows only 3h back, so a longer rollup backlog left
hours drawn as zero. The raw tail now starts at the newest complete rollup
hour, capped at the 24h raw holdback, and the response reports rolledUntil
and rawFrom so clients can show anything still uncovered.
2026-10-02 19:03:46 -04:00

5957 lines
190 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: queries.sql
package db
import (
"context"
"encoding/json"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
const addChannelConfigScopes = `-- name: AddChannelConfigScopes :exec
INSERT INTO channel_config_scopes (key_fingerprint, region_slug)
SELECT unnest($1::bytea[]), NULLIF(unnest($2::text[]), '')
`
type AddChannelConfigScopesParams struct {
Fingerprints [][]byte `json:"fingerprints"`
Regions []string `json:"regions"`
}
// An empty region places the channel Beacon-wide.
func (q *Queries) AddChannelConfigScopes(ctx context.Context, arg AddChannelConfigScopesParams) error {
_, err := q.db.Exec(ctx, addChannelConfigScopes, arg.Fingerprints, arg.Regions)
return err
}
const addChannelMembers = `-- name: AddChannelMembers :exec
INSERT INTO meshmapper_channel_members (iata, key_fingerprint)
SELECT $1, unnest($2::bytea[])
ON CONFLICT (iata, key_fingerprint) DO NOTHING
`
type AddChannelMembersParams struct {
Iata string `json:"iata"`
Fingerprints [][]byte `json:"fingerprints"`
}
func (q *Queries) AddChannelMembers(ctx context.Context, arg AddChannelMembersParams) error {
_, err := q.db.Exec(ctx, addChannelMembers, arg.Iata, arg.Fingerprints)
return err
}
const addIATAs = `-- name: AddIATAs :exec
INSERT INTO iata_codes (iata)
SELECT unnest($1::bpchar[])
ON CONFLICT (iata) DO NOTHING
`
func (q *Queries) AddIATAs(ctx context.Context, iatas []string) error {
_, err := q.db.Exec(ctx, addIATAs, iatas)
return err
}
const addRegionIATAs = `-- name: AddRegionIATAs :exec
INSERT INTO region_iatas (region_id, iata)
SELECT $1, unnest($2::bpchar[])
ON CONFLICT (region_id, iata) DO NOTHING
`
type AddRegionIATAsParams struct {
RegionID int32 `json:"region_id"`
Iatas []string `json:"iatas"`
}
func (q *Queries) AddRegionIATAs(ctx context.Context, arg AddRegionIATAsParams) error {
_, err := q.db.Exec(ctx, addRegionIATAs, arg.RegionID, arg.Iatas)
return err
}
const ambiguousPrefixes = `-- name: AmbiguousPrefixes :many
SELECT iata::text AS iata, 1::int AS len, prefix_1 AS prefix FROM node_short_ids GROUP BY iata, prefix_1 HAVING COUNT(*) > 1
UNION ALL
SELECT iata::text, 2::int, prefix_2 FROM node_short_ids GROUP BY iata, prefix_2 HAVING COUNT(*) > 1
UNION ALL
SELECT iata::text, 3::int, prefix_3 FROM node_short_ids GROUP BY iata, prefix_3 HAVING COUNT(*) > 1
UNION ALL
SELECT iata::text, 4::int, prefix_4 FROM node_short_ids GROUP BY iata, prefix_4 HAVING COUNT(*) > 1
`
type AmbiguousPrefixesRow struct {
Iata string `json:"iata"`
Len int32 `json:"len"`
Prefix []byte `json:"prefix"`
}
// Hop prefixes that match >1 node in an IATA, per width. Computed once per reconfirm run.
func (q *Queries) AmbiguousPrefixes(ctx context.Context) ([]AmbiguousPrefixesRow, error) {
rows, err := q.db.Query(ctx, ambiguousPrefixes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []AmbiguousPrefixesRow{}
for rows.Next() {
var i AmbiguousPrefixesRow
if err := rows.Scan(&i.Iata, &i.Len, &i.Prefix); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const createAccount = `-- name: CreateAccount :one
INSERT INTO accounts (name) VALUES ($1)
ON CONFLICT (name) WHERE deactivated_at IS NULL DO NOTHING
RETURNING id, name, created_at, deactivated_at
`
func (q *Queries) CreateAccount(ctx context.Context, name string) (Account, error) {
row := q.db.QueryRow(ctx, createAccount, name)
var i Account
err := row.Scan(
&i.ID,
&i.Name,
&i.CreatedAt,
&i.DeactivatedAt,
)
return i, err
}
const deactivateAccount = `-- name: DeactivateAccount :one
WITH target AS MATERIALIZED (
SELECT a.id, a.deactivated_at FROM accounts a WHERE a.id = $1 FOR UPDATE
), changed AS (
UPDATE accounts a SET deactivated_at = NOW()
FROM target t WHERE a.id = t.id AND t.deactivated_at IS NULL
RETURNING a.id
)
SELECT EXISTS(SELECT 1 FROM target) AS found,
EXISTS(SELECT 1 FROM changed) AS deactivated
`
type DeactivateAccountRow struct {
Found bool `json:"found"`
Deactivated bool `json:"deactivated"`
}
// Lock the current row before deciding the outcome, including when another
// deactivation commits while this statement is waiting for its row lock.
func (q *Queries) DeactivateAccount(ctx context.Context, id uuid.UUID) (DeactivateAccountRow, error) {
row := q.db.QueryRow(ctx, deactivateAccount, id)
var i DeactivateAccountRow
err := row.Scan(&i.Found, &i.Deactivated)
return i, err
}
const deleteAllChannelMembers = `-- name: DeleteAllChannelMembers :exec
DELETE FROM meshmapper_channel_members
`
func (q *Queries) DeleteAllChannelMembers(ctx context.Context) error {
_, err := q.db.Exec(ctx, deleteAllChannelMembers)
return err
}
const deleteChannelConfigScopes = `-- name: DeleteChannelConfigScopes :exec
DELETE FROM channel_config_scopes
`
func (q *Queries) DeleteChannelConfigScopes(ctx context.Context) error {
_, err := q.db.Exec(ctx, deleteChannelConfigScopes)
return err
}
const deleteChannelMembersNotIn = `-- name: DeleteChannelMembersNotIn :exec
DELETE FROM meshmapper_channel_members WHERE iata = $1 AND NOT (key_fingerprint = ANY($2::bytea[]))
`
type DeleteChannelMembersNotInParams struct {
Iata string `json:"iata"`
Keep [][]byte `json:"keep"`
}
func (q *Queries) DeleteChannelMembersNotIn(ctx context.Context, arg DeleteChannelMembersNotInParams) error {
_, err := q.db.Exec(ctx, deleteChannelMembersNotIn, arg.Iata, arg.Keep)
return err
}
const deleteOldChannelIATAs = `-- name: DeleteOldChannelIATAs :exec
DELETE FROM channel_iatas WHERE last_heard < $1
`
// Keeps the channel IATA filter in step with packet retention.
func (q *Queries) DeleteOldChannelIATAs(ctx context.Context, lastHeard pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, deleteOldChannelIATAs, lastHeard)
return err
}
const deleteOldNodes = `-- name: DeleteOldNodes :exec
DELETE FROM nodes
WHERE last_seen < $1
AND id NOT IN (SELECT owner_node_id FROM observer_owners WHERE owner_node_id IS NOT NULL)
`
// Deletes nodes not seen since the given cutoff. node_iatas and node_neighbors cascade-
// delete via FK. Excludes nodes referenced by observer_owners.owner_node_id -- that FK has
// no ON DELETE action, so deleting one directly would fail the whole statement anyway, and
// an operator manually recorded ownership for that node, so leave it alone even if stale.
// known_routes.node_ids is a plain UUID[] with no FK; a deleted node's id can be left
// dangling in old routes there, but ReconfirmTask already prunes stale/ambiguous routes
// periodically and will clean those up on its own schedule.
func (q *Queries) DeleteOldNodes(ctx context.Context, lastSeen pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, deleteOldNodes, lastSeen)
return err
}
const deleteOldObservers = `-- name: DeleteOldObservers :many
WITH expired AS (
SELECT o.id
FROM observers o
WHERE o.last_seen < $1
AND (o.last_status_at IS NULL OR o.last_status_at < $1)
AND NOT EXISTS (SELECT 1 FROM packet_observations po WHERE po.observer_id = o.id)
AND NOT EXISTS (SELECT 1 FROM observer_telemetry ot WHERE ot.observer_id = o.id)
AND NOT EXISTS (SELECT 1 FROM observer_owners oo WHERE oo.observer_id = o.id)
ORDER BY o.last_seen, o.id
LIMIT 1000
FOR UPDATE OF o SKIP LOCKED
)
DELETE FROM observers o USING expired e
WHERE o.id = e.id
RETURNING o.id
`
// Opt-in age-out: preserve retained history and manually recorded ownership.
// Bound deletions per cleanup tick and skip observers being updated by ingest.
func (q *Queries) DeleteOldObservers(ctx context.Context, lastSeen pgtype.Timestamptz) ([]uuid.UUID, error) {
rows, err := q.db.Query(ctx, deleteOldObservers, lastSeen)
if err != nil {
return nil, err
}
defer rows.Close()
items := []uuid.UUID{}
for rows.Next() {
var id uuid.UUID
if err := rows.Scan(&id); err != nil {
return nil, err
}
items = append(items, id)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const deleteOldPackets = `-- name: DeleteOldPackets :one
WITH victims AS MATERIALIZED (
SELECT vp.packet_hash, vp.last_heard_at FROM packets vp
WHERE vp.last_heard_at < $1::timestamptz
ORDER BY vp.last_heard_at, vp.packet_hash
LIMIT $2::integer
FOR UPDATE OF vp SKIP LOCKED
), deleted AS (
DELETE FROM packets dp USING victims v WHERE dp.packet_hash = v.packet_hash
RETURNING dp.last_heard_at
), mark AS (
UPDATE analytics_raw_state
SET raw_deleted_before = GREATEST(raw_deleted_before, (SELECT max(last_heard_at) FROM deleted))
WHERE EXISTS (SELECT 1 FROM deleted)
)
SELECT count(*) FROM deleted
`
type DeleteOldPacketsParams struct {
Cutoff pgtype.Timestamptz `json:"cutoff"`
BatchSize int32 `json:"batch_size"`
}
// Deletes one bounded cohort (observations cascade). SKIP LOCKED leaves packets an in-flight
// observation insert holds. raw_deleted_before tells the rollup which hours lost raw rows.
func (q *Queries) DeleteOldPackets(ctx context.Context, arg DeleteOldPacketsParams) (int64, error) {
row := q.db.QueryRow(ctx, deleteOldPackets, arg.Cutoff, arg.BatchSize)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteOldRoutes = `-- name: DeleteOldRoutes :execrows
WITH expired AS (
SELECT r.iata, r.path_key
FROM known_routes r
WHERE r.last_seen < GREATEST($1::timestamptz, $2::timestamptz)
AND (r.last_seen < $1 OR
(r.observation_count < $3 AND r.last_seen < $2))
LIMIT $4
FOR UPDATE OF r SKIP LOCKED
)
DELETE FROM known_routes kr USING expired e
WHERE kr.iata = e.iata AND kr.path_key = e.path_key
`
type DeleteOldRoutesParams struct {
RetentionCutoff pgtype.Timestamptz `json:"retention_cutoff"`
GraceCutoff pgtype.Timestamptz `json:"grace_cutoff"`
MinObservations int64 `json:"min_observations"`
BatchSize int32 `json:"batch_size"`
}
// One batch of routes past retention, or past grace with too few observations.
// GREATEST keeps the scan on idx_known_routes_last_seen.
func (q *Queries) DeleteOldRoutes(ctx context.Context, arg DeleteOldRoutesParams) (int64, error) {
result, err := q.db.Exec(ctx, deleteOldRoutes,
arg.RetentionCutoff,
arg.GraceCutoff,
arg.MinObservations,
arg.BatchSize,
)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const deleteOldTelemetry = `-- name: DeleteOldTelemetry :exec
DELETE FROM observer_telemetry WHERE reported_at < $1
`
// Deletes telemetry rows older than the given cutoff. Called by the cleanup goroutine.
func (q *Queries) DeleteOldTelemetry(ctx context.Context, reportedAt pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, deleteOldTelemetry, reportedAt)
return err
}
const deleteOldTraceIATAs = `-- name: DeleteOldTraceIATAs :exec
DELETE FROM trace_iatas WHERE last_heard < $1
`
// Keeps the trace IATA filter in step with packet retention.
func (q *Queries) DeleteOldTraceIATAs(ctx context.Context, lastHeard pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, deleteOldTraceIATAs, lastHeard)
return err
}
const deleteOldTraceTags = `-- name: DeleteOldTraceTags :exec
DELETE FROM trace_tags WHERE last_heard_at < $1
`
func (q *Queries) DeleteOldTraceTags(ctx context.Context, lastHeardAt pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, deleteOldTraceTags, lastHeardAt)
return err
}
const deleteRegionIATAsNotIn = `-- name: DeleteRegionIATAsNotIn :exec
DELETE FROM region_iatas WHERE region_id = $1 AND NOT (iata = ANY($2::bpchar[]))
`
type DeleteRegionIATAsNotInParams struct {
RegionID int32 `json:"region_id"`
Keep []string `json:"keep"`
}
func (q *Queries) DeleteRegionIATAsNotIn(ctx context.Context, arg DeleteRegionIATAsNotInParams) error {
_, err := q.db.Exec(ctx, deleteRegionIATAsNotIn, arg.RegionID, arg.Keep)
return err
}
const getAccount = `-- name: GetAccount :one
SELECT id, name, created_at, deactivated_at FROM accounts WHERE id = $1
`
func (q *Queries) GetAccount(ctx context.Context, id uuid.UUID) (Account, error) {
row := q.db.QueryRow(ctx, getAccount, id)
var i Account
err := row.Scan(
&i.ID,
&i.Name,
&i.CreatedAt,
&i.DeactivatedAt,
)
return i, err
}
const getChannelByID = `-- name: GetChannelByID :one
SELECT id, channel_hash, key_fingerprint, name, hashtag, is_hashtag, is_public, key_known, first_seen, last_seen, message_count FROM channels WHERE id = $1
`
func (q *Queries) GetChannelByID(ctx context.Context, id int32) (Channel, error) {
row := q.db.QueryRow(ctx, getChannelByID, id)
var i Channel
err := row.Scan(
&i.ID,
&i.ChannelHash,
&i.KeyFingerprint,
&i.Name,
&i.Hashtag,
&i.IsHashtag,
&i.IsPublic,
&i.KeyKnown,
&i.FirstSeen,
&i.LastSeen,
&i.MessageCount,
)
return i, err
}
const getCrossIATANeighbors = `-- name: GetCrossIATANeighbors :many
SELECT
n.id, n.name, n.node_type, n.latitude, n.longitude,
nn.iata AS neighbor_iata, nn.observation_count, nn.last_seen, nn.snr
FROM node_neighbors nn
JOIN nodes n ON n.id = nn.neighbor_id
WHERE nn.node_id = $1
AND nn.iata != $2
ORDER BY nn.last_seen DESC
`
type GetCrossIATANeighborsParams struct {
NodeID uuid.UUID `json:"node_id"`
Iata string `json:"iata"`
}
type GetCrossIATANeighborsRow struct {
ID uuid.UUID `json:"id"`
Name *string `json:"name"`
NodeType int16 `json:"node_type"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
NeighborIata string `json:"neighbor_iata"`
ObservationCount int64 `json:"observation_count"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
Snr *float32 `json:"snr"`
}
// Returns neighbors of a node that are in a different IATA.
func (q *Queries) GetCrossIATANeighbors(ctx context.Context, arg GetCrossIATANeighborsParams) ([]GetCrossIATANeighborsRow, error) {
rows, err := q.db.Query(ctx, getCrossIATANeighbors, arg.NodeID, arg.Iata)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetCrossIATANeighborsRow{}
for rows.Next() {
var i GetCrossIATANeighborsRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.NodeType,
&i.Latitude,
&i.Longitude,
&i.NeighborIata,
&i.ObservationCount,
&i.LastSeen,
&i.Snr,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getHourlyStats = `-- name: GetHourlyStats :many
SELECT iata, hour, observation_count
FROM analytics_hourly_iata_observations
WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR iata = ANY($1::bpchar[]))
AND hour >= $2::timestamptz
ORDER BY iata, hour
`
type GetHourlyStatsParams struct {
Iatas []string `json:"iatas"`
Since pgtype.Timestamptz `json:"since"`
}
type GetHourlyStatsRow struct {
Iata string `json:"iata"`
Hour pgtype.Timestamptz `json:"hour"`
ObservationCount int64 `json:"observation_count"`
}
// ============================================================
// STATS
// ============================================================
func (q *Queries) GetHourlyStats(ctx context.Context, arg GetHourlyStatsParams) ([]GetHourlyStatsRow, error) {
rows, err := q.db.Query(ctx, getHourlyStats, arg.Iatas, arg.Since)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetHourlyStatsRow{}
for rows.Next() {
var i GetHourlyStatsRow
if err := rows.Scan(&i.Iata, &i.Hour, &i.ObservationCount); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getIATA = `-- name: GetIATA :one
SELECT iata, display_name, approx_lat, approx_lng, added_at, border FROM iata_codes WHERE iata = $1
`
func (q *Queries) GetIATA(ctx context.Context, iata string) (IataCode, error) {
row := q.db.QueryRow(ctx, getIATA, iata)
var i IataCode
err := row.Scan(
&i.Iata,
&i.DisplayName,
&i.ApproxLat,
&i.ApproxLng,
&i.AddedAt,
&i.Border,
)
return i, err
}
const getIATABorder = `-- name: GetIATABorder :one
SELECT COALESCE(z.feature, i.border)::jsonb AS border
FROM iata_codes i
LEFT JOIN meshmapper_zone_boundaries z ON z.iata = i.iata
WHERE i.iata = $1
`
// An imported MeshMapper boundary overrides the configured one. border is NULL
// when neither exists; a missing row (unknown IATA) is sql.ErrNoRows, same
// not-found distinction GetIATA already makes.
func (q *Queries) GetIATABorder(ctx context.Context, iata string) (json.RawMessage, error) {
row := q.db.QueryRow(ctx, getIATABorder, iata)
var border json.RawMessage
err := row.Scan(&border)
return border, err
}
const getKnownRoutesByNode = `-- name: GetKnownRoutesByNode :many
SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count
FROM known_routes
WHERE iata = $1
AND $2::uuid = ANY(node_ids)
ORDER BY hop_count ASC, last_seen DESC
`
type GetKnownRoutesByNodeParams struct {
Iata string `json:"iata"`
Column2 uuid.UUID `json:"column_2"`
}
type GetKnownRoutesByNodeRow struct {
ID int64 `json:"id"`
NodeIds []uuid.UUID `json:"node_ids"`
HashPrefix [][]byte `json:"hash_prefix"`
Iata string `json:"iata"`
HopCount int32 `json:"hop_count"`
FirstSeen pgtype.Timestamptz `json:"first_seen"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
ObservationCount int64 `json:"observation_count"`
}
func (q *Queries) GetKnownRoutesByNode(ctx context.Context, arg GetKnownRoutesByNodeParams) ([]GetKnownRoutesByNodeRow, error) {
rows, err := q.db.Query(ctx, getKnownRoutesByNode, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetKnownRoutesByNodeRow{}
for rows.Next() {
var i GetKnownRoutesByNodeRow
if err := rows.Scan(
&i.ID,
&i.NodeIds,
&i.HashPrefix,
&i.Iata,
&i.HopCount,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getNodeByID = `-- name: GetNodeByID :one
SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.location_source, n.last_advert_at, n.supports_multibyte_paths, n.supports_multibyte_traces, n.default_scope_id, n.min_firmware_version, n.first_seen, n.last_seen, n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, n.metadata, n.device_clock_drift_seconds, ts.name AS default_scope_name,
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC)
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas,
(SELECT COUNT(DISTINCT nn.neighbor_id) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count
FROM nodes n
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
WHERE n.id = $1
`
type GetNodeByIDRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
NodeType int16 `json:"node_type"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
LocationSource *string `json:"location_source"`
LastAdvertAt pgtype.Timestamptz `json:"last_advert_at"`
SupportsMultibytePaths bool `json:"supports_multibyte_paths"`
SupportsMultibyteTraces bool `json:"supports_multibyte_traces"`
DefaultScopeID *int32 `json:"default_scope_id"`
MinFirmwareVersion *string `json:"min_firmware_version"`
FirstSeen pgtype.Timestamptz `json:"first_seen"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
Metadata []byte `json:"metadata"`
DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"`
DefaultScopeName *string `json:"default_scope_name"`
IsObserver bool `json:"is_observer"`
ObserverID uuid.UUID `json:"observer_id"`
Iatas []byte `json:"iatas"`
KnownNeighborCount int64 `json:"known_neighbor_count"`
}
func (q *Queries) GetNodeByID(ctx context.Context, id uuid.UUID) (GetNodeByIDRow, error) {
row := q.db.QueryRow(ctx, getNodeByID, id)
var i GetNodeByIDRow
err := row.Scan(
&i.ID,
&i.PublicKey,
&i.NodeType,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.LocationSource,
&i.LastAdvertAt,
&i.SupportsMultibytePaths,
&i.SupportsMultibyteTraces,
&i.DefaultScopeID,
&i.MinFirmwareVersion,
&i.FirstSeen,
&i.LastSeen,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.Metadata,
&i.DeviceClockDriftSeconds,
&i.DefaultScopeName,
&i.IsObserver,
&i.ObserverID,
&i.Iatas,
&i.KnownNeighborCount,
)
return i, err
}
const getNodeByPubkey = `-- name: GetNodeByPubkey :one
SELECT id FROM nodes WHERE public_key = $1
`
func (q *Queries) GetNodeByPubkey(ctx context.Context, publicKey []byte) (uuid.UUID, error) {
row := q.db.QueryRow(ctx, getNodeByPubkey, publicKey)
var id uuid.UUID
err := row.Scan(&id)
return id, err
}
const getNodeNeighbors = `-- name: GetNodeNeighbors :many
SELECT
n.id, n.public_key, n.name, n.node_type, n.latitude, n.longitude,
nn.iata, nn.observation_count, nn.first_seen, nn.last_seen, nn.snr
FROM node_neighbors nn
JOIN nodes n ON n.id = nn.neighbor_id
WHERE nn.node_id = $1
ORDER BY nn.last_seen DESC
`
type GetNodeNeighborsRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
Name *string `json:"name"`
NodeType int16 `json:"node_type"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
Iata string `json:"iata"`
ObservationCount int64 `json:"observation_count"`
FirstSeen pgtype.Timestamptz `json:"first_seen"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
Snr *float32 `json:"snr"`
}
// Returns the neighbors of a node with details, ordered by most recently seen.
func (q *Queries) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]GetNodeNeighborsRow, error) {
rows, err := q.db.Query(ctx, getNodeNeighbors, nodeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetNodeNeighborsRow{}
for rows.Next() {
var i GetNodeNeighborsRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.Name,
&i.NodeType,
&i.Latitude,
&i.Longitude,
&i.Iata,
&i.ObservationCount,
&i.FirstSeen,
&i.LastSeen,
&i.Snr,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getNodesByIDs = `-- name: GetNodesByIDs :many
SELECT id, public_key, name, latitude, longitude
FROM nodes
WHERE id = ANY($1::uuid[])
`
type GetNodesByIDsRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
}
func (q *Queries) GetNodesByIDs(ctx context.Context, dollar_1 []uuid.UUID) ([]GetNodesByIDsRow, error) {
rows, err := q.db.Query(ctx, getNodesByIDs, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetNodesByIDsRow{}
for rows.Next() {
var i GetNodesByIDsRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.Name,
&i.Latitude,
&i.Longitude,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getNodesByPubkeys = `-- name: GetNodesByPubkeys :many
SELECT id, public_key, name, latitude, longitude
FROM nodes
WHERE public_key = ANY($1::bytea[])
`
type GetNodesByPubkeysRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
}
func (q *Queries) GetNodesByPubkeys(ctx context.Context, pubkeys [][]byte) ([]GetNodesByPubkeysRow, error) {
rows, err := q.db.Query(ctx, getNodesByPubkeys, pubkeys)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetNodesByPubkeysRow{}
for rows.Next() {
var i GetNodesByPubkeysRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.Name,
&i.Latitude,
&i.Longitude,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverActivityHourly = `-- name: GetObserverActivityHourly :many
WITH src AS (
SELECT a.hour AS t, a.observations, a.airtime_ms, a.airtime_n, a.snr_sum, a.snr_n, a.snr_min, a.rssi_sum, a.rssi_n
FROM analytics_hourly_observer_activity a
WHERE a.observer_id = $1 AND a.hour >= $2::timestamptz AND a.hour < LEAST($4::timestamptz, $5::timestamptz)
UNION ALL
SELECT o.heard_at, 1::bigint, o.airtime_ms, (o.airtime_ms IS NOT NULL)::int::bigint,
s.snr, (s.snr IS NOT NULL)::int::bigint, s.snr, s.rssi::bigint, (s.rssi IS NOT NULL)::int::bigint
FROM packet_observations o CROSS JOIN
LATERAL (SELECT CASE WHEN NOT (COALESCE(o.rssi, 0) = 0 AND COALESCE(o.snr, 0) = 0) THEN o.snr END AS snr,
CASE WHEN NOT (COALESCE(o.rssi, 0) = 0 AND COALESCE(o.snr, 0) = 0) THEN o.rssi END AS rssi) s
WHERE o.observer_id = $1 AND o.heard_at >= GREATEST($2::timestamptz, $5::timestamptz) AND o.heard_at < $4::timestamptz
)
SELECT
date_bin($3::interval, t, TIMESTAMPTZ 'epoch')::timestamptz AS bucket,
SUM(observations)::bigint AS observations,
COALESCE(SUM(airtime_ms), 0)::real AS airtime_ms,
SUM(airtime_n)::bigint AS airtime_n,
COALESCE(SUM(snr_sum), 0)::real AS snr_sum,
SUM(snr_n)::bigint AS snr_n,
COALESCE(MIN(snr_min), 0)::real AS snr_min,
COALESCE(SUM(rssi_sum), 0)::bigint AS rssi_sum,
SUM(rssi_n)::bigint AS rssi_n
FROM src
GROUP BY 1
ORDER BY 1
`
type GetObserverActivityHourlyParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Column3 pgtype.Interval `json:"column_3"`
Until pgtype.Timestamptz `json:"until"`
TailStart pgtype.Timestamptz `json:"tail_start"`
}
type GetObserverActivityHourlyRow struct {
Bucket pgtype.Timestamptz `json:"bucket"`
Observations int64 `json:"observations"`
AirtimeMs float32 `json:"airtime_ms"`
AirtimeN int64 `json:"airtime_n"`
SnrSum float32 `json:"snr_sum"`
SnrN int64 `json:"snr_n"`
SnrMin float32 `json:"snr_min"`
RssiSum int64 `json:"rssi_sum"`
RssiN int64 `json:"rssi_n"`
}
// Hour-or-coarser buckets summed from the hourly rollup; same COALESCE-plus-count shape as the raw query.
// Hours from @tail_start on aren't rolled yet and read raw rows.
func (q *Queries) GetObserverActivityHourly(ctx context.Context, arg GetObserverActivityHourlyParams) ([]GetObserverActivityHourlyRow, error) {
rows, err := q.db.Query(ctx, getObserverActivityHourly,
arg.ObserverID,
arg.Column2,
arg.Column3,
arg.Until,
arg.TailStart,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverActivityHourlyRow{}
for rows.Next() {
var i GetObserverActivityHourlyRow
if err := rows.Scan(
&i.Bucket,
&i.Observations,
&i.AirtimeMs,
&i.AirtimeN,
&i.SnrSum,
&i.SnrN,
&i.SnrMin,
&i.RssiSum,
&i.RssiN,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverActivityHourlyPayloadTypes = `-- name: GetObserverActivityHourlyPayloadTypes :many
WITH src AS (
SELECT a.payload_type, a.observations AS n
FROM analytics_hourly_observer_activity a
WHERE a.observer_id = $1 AND a.hour >= $2::timestamptz AND a.hour < LEAST($3::timestamptz, $4::timestamptz)
UNION ALL
SELECT COALESCE(o.payload_type, -1)::smallint, 1::bigint
FROM packet_observations o
WHERE o.observer_id = $1 AND o.heard_at >= GREATEST($2::timestamptz, $4::timestamptz) AND o.heard_at < $3::timestamptz
)
SELECT payload_type, SUM(n)::bigint AS count
FROM src
GROUP BY payload_type
ORDER BY count DESC
`
type GetObserverActivityHourlyPayloadTypesParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Until pgtype.Timestamptz `json:"until"`
TailStart pgtype.Timestamptz `json:"tail_start"`
}
type GetObserverActivityHourlyPayloadTypesRow struct {
PayloadType int16 `json:"payload_type"`
Count int64 `json:"count"`
}
// Same rollup/raw-tail split as GetObserverActivityHourly.
func (q *Queries) GetObserverActivityHourlyPayloadTypes(ctx context.Context, arg GetObserverActivityHourlyPayloadTypesParams) ([]GetObserverActivityHourlyPayloadTypesRow, error) {
rows, err := q.db.Query(ctx, getObserverActivityHourlyPayloadTypes,
arg.ObserverID,
arg.Column2,
arg.Until,
arg.TailStart,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverActivityHourlyPayloadTypesRow{}
for rows.Next() {
var i GetObserverActivityHourlyPayloadTypesRow
if err := rows.Scan(&i.PayloadType, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverActivityLiveSummary = `-- name: GetObserverActivityLiveSummary :one
WITH latest AS (
SELECT heard_at FROM packet_observations
WHERE observer_id = $1::uuid AND heard_at <= $2::timestamptz
ORDER BY heard_at DESC LIMIT 1
), hourly AS (
SELECT COUNT(*)::bigint AS n FROM packet_observations
WHERE observer_id = $1::uuid
AND heard_at >= $3::timestamptz AND heard_at < $4::timestamptz
)
SELECT (SELECT heard_at FROM latest)::timestamptz AS latest_recorded_at,
hourly.n AS last_complete_hour FROM hourly
`
type GetObserverActivityLiveSummaryParams struct {
ObserverID uuid.UUID `json:"observer_id"`
GeneratedAt pgtype.Timestamptz `json:"generated_at"`
HourStart pgtype.Timestamptz `json:"hour_start"`
HourEnd pgtype.Timestamptz `json:"hour_end"`
}
type GetObserverActivityLiveSummaryRow struct {
LatestRecordedAt pgtype.Timestamptz `json:"latest_recorded_at"`
LastCompleteHour int64 `json:"last_complete_hour"`
}
// Two indexed ranges, bounded to one observer; no legacy presence counters.
func (q *Queries) GetObserverActivityLiveSummary(ctx context.Context, arg GetObserverActivityLiveSummaryParams) (GetObserverActivityLiveSummaryRow, error) {
row := q.db.QueryRow(ctx, getObserverActivityLiveSummary,
arg.ObserverID,
arg.GeneratedAt,
arg.HourStart,
arg.HourEnd,
)
var i GetObserverActivityLiveSummaryRow
err := row.Scan(&i.LatestRecordedAt, &i.LastCompleteHour)
return i, err
}
const getObserverActivityRaw = `-- name: GetObserverActivityRaw :many
SELECT
date_bin($3::interval, heard_at, TIMESTAMPTZ 'epoch')::timestamptz AS bucket,
COUNT(*)::bigint AS observations,
COALESCE(SUM(airtime_ms), 0)::real AS airtime_ms,
COUNT(airtime_ms)::bigint AS airtime_n,
COALESCE(AVG(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS snr_avg,
COALESCE(MIN(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS snr_min,
COUNT(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0))::bigint AS snr_n,
COALESCE(AVG(rssi) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS rssi_avg,
COUNT(rssi) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0))::bigint AS rssi_n
FROM packet_observations
WHERE observer_id = $1 AND heard_at >= $2::timestamptz AND heard_at < $4::timestamptz
GROUP BY bucket
ORDER BY bucket
`
type GetObserverActivityRawParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Column3 pgtype.Interval `json:"column_3"`
Until pgtype.Timestamptz `json:"until"`
}
type GetObserverActivityRawRow struct {
Bucket pgtype.Timestamptz `json:"bucket"`
Observations int64 `json:"observations"`
AirtimeMs float32 `json:"airtime_ms"`
AirtimeN int64 `json:"airtime_n"`
SnrAvg float32 `json:"snr_avg"`
SnrMin float32 `json:"snr_min"`
SnrN int64 `json:"snr_n"`
RssiAvg float32 `json:"rssi_avg"`
RssiN int64 `json:"rssi_n"`
}
// Sub-hour activity buckets straight off idx_observations_observer; no join to packets.
// Aggregates are COALESCEd and paired with a count column: sqlc types a cast expression as
// NOT NULL, so the counts are what tell the store a bucket had no costed or no signal rows.
func (q *Queries) GetObserverActivityRaw(ctx context.Context, arg GetObserverActivityRawParams) ([]GetObserverActivityRawRow, error) {
rows, err := q.db.Query(ctx, getObserverActivityRaw,
arg.ObserverID,
arg.Column2,
arg.Column3,
arg.Until,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverActivityRawRow{}
for rows.Next() {
var i GetObserverActivityRawRow
if err := rows.Scan(
&i.Bucket,
&i.Observations,
&i.AirtimeMs,
&i.AirtimeN,
&i.SnrAvg,
&i.SnrMin,
&i.SnrN,
&i.RssiAvg,
&i.RssiN,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverActivityRawPayloadTypes = `-- name: GetObserverActivityRawPayloadTypes :many
SELECT payload_type, COUNT(*)::bigint AS count
FROM packet_observations
WHERE observer_id = $1 AND heard_at >= $2::timestamptz AND heard_at < $3::timestamptz
GROUP BY payload_type
ORDER BY count DESC
`
type GetObserverActivityRawPayloadTypesParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Until pgtype.Timestamptz `json:"until"`
}
type GetObserverActivityRawPayloadTypesRow struct {
PayloadType *int16 `json:"payload_type"`
Count int64 `json:"count"`
}
func (q *Queries) GetObserverActivityRawPayloadTypes(ctx context.Context, arg GetObserverActivityRawPayloadTypesParams) ([]GetObserverActivityRawPayloadTypesRow, error) {
rows, err := q.db.Query(ctx, getObserverActivityRawPayloadTypes, arg.ObserverID, arg.Column2, arg.Until)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverActivityRawPayloadTypesRow{}
for rows.Next() {
var i GetObserverActivityRawPayloadTypesRow
if err := rows.Scan(&i.PayloadType, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverBrokers = `-- name: GetObserverBrokers :many
SELECT broker_name, last_seen, last_packet_at
FROM observer_brokers
WHERE observer_id = $1
ORDER BY last_seen DESC
`
type GetObserverBrokersRow struct {
BrokerName string `json:"broker_name"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
LastPacketAt pgtype.Timestamptz `json:"last_packet_at"`
}
func (q *Queries) GetObserverBrokers(ctx context.Context, observerID uuid.UUID) ([]GetObserverBrokersRow, error) {
rows, err := q.db.Query(ctx, getObserverBrokers, observerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverBrokersRow{}
for rows.Next() {
var i GetObserverBrokersRow
if err := rows.Scan(&i.BrokerName, &i.LastSeen, &i.LastPacketAt); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverByID = `-- name: GetObserverByID :one
SELECT id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at FROM observers WHERE id = $1
`
func (q *Queries) GetObserverByID(ctx context.Context, id uuid.UUID) (Observer, error) {
row := q.db.QueryRow(ctx, getObserverByID, id)
var i Observer
err := row.Scan(
&i.ID,
&i.PublicKey,
&i.DisplayName,
&i.ObserverType,
&i.SoftwareVersion,
&i.HardwareModel,
&i.FirmwareVersion,
&i.FirmwareBuild,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.RadioCr,
&i.BatteryLevel,
&i.UptimeSeconds,
&i.StatusMetadata,
&i.LastStatusAt,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
&i.Metadata,
&i.RegionScope,
&i.LastIata,
&i.LastIataAt,
)
return i, err
}
const getObserverByPubkey = `-- name: GetObserverByPubkey :one
SELECT id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at FROM observers WHERE public_key = $1
`
func (q *Queries) GetObserverByPubkey(ctx context.Context, publicKey []byte) (Observer, error) {
row := q.db.QueryRow(ctx, getObserverByPubkey, publicKey)
var i Observer
err := row.Scan(
&i.ID,
&i.PublicKey,
&i.DisplayName,
&i.ObserverType,
&i.SoftwareVersion,
&i.HardwareModel,
&i.FirmwareVersion,
&i.FirmwareBuild,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.RadioCr,
&i.BatteryLevel,
&i.UptimeSeconds,
&i.StatusMetadata,
&i.LastStatusAt,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
&i.Metadata,
&i.RegionScope,
&i.LastIata,
&i.LastIataAt,
)
return i, err
}
const getObserverLastIATA = `-- name: GetObserverLastIATA :one
SELECT COALESCE(last_iata, '')::text FROM observers WHERE id = $1
`
func (q *Queries) GetObserverLastIATA(ctx context.Context, id uuid.UUID) (string, error) {
row := q.db.QueryRow(ctx, getObserverLastIATA, id)
var column_1 string
err := row.Scan(&column_1)
return column_1, err
}
const getObserverRadio = `-- name: GetObserverRadio :one
SELECT radio_freq_mhz, radio_bw_khz, radio_sf, radio_cr
FROM observers
WHERE id = $1
`
type GetObserverRadioRow struct {
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
RadioSf *int16 `json:"radio_sf"`
RadioCr *int16 `json:"radio_cr"`
}
func (q *Queries) GetObserverRadio(ctx context.Context, id uuid.UUID) (GetObserverRadioRow, error) {
row := q.db.QueryRow(ctx, getObserverRadio, id)
var i GetObserverRadioRow
err := row.Scan(
&i.RadioFreqMhz,
&i.RadioBwKhz,
&i.RadioSf,
&i.RadioCr,
)
return i, err
}
const getObserverScopes = `-- name: GetObserverScopes :many
SELECT ts.name FROM observer_scopes os
JOIN transport_scopes ts ON ts.id = os.scope_id
WHERE os.observer_id = $1
ORDER BY ts.name
`
func (q *Queries) GetObserverScopes(ctx context.Context, observerID uuid.UUID) ([]string, error) {
rows, err := q.db.Query(ctx, getObserverScopes, observerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
items = append(items, name)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverTelemetry = `-- name: GetObserverTelemetry :many
SELECT id, reported_at, battery_voltage_mv, airtime_tx_secs, airtime_rx_secs,
noise_floor_db, uptime_seconds, queue_length, debug_flags, receive_errors
FROM observer_telemetry
WHERE observer_id = $1
AND ($2::timestamptz IS NULL OR reported_at >= $2)
AND ($3::timestamptz IS NULL OR reported_at <= $3)
AND ($4 = 0 OR id > $4)
ORDER BY reported_at ASC
`
type GetObserverTelemetryParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Column3 pgtype.Timestamptz `json:"column_3"`
Column4 interface{} `json:"column_4"`
}
type GetObserverTelemetryRow struct {
ID int64 `json:"id"`
ReportedAt pgtype.Timestamptz `json:"reported_at"`
BatteryVoltageMv *int32 `json:"battery_voltage_mv"`
AirtimeTxSecs *float32 `json:"airtime_tx_secs"`
AirtimeRxSecs *float32 `json:"airtime_rx_secs"`
NoiseFloorDb *float32 `json:"noise_floor_db"`
UptimeSeconds *int64 `json:"uptime_seconds"`
QueueLength *int32 `json:"queue_length"`
DebugFlags *int32 `json:"debug_flags"`
ReceiveErrors *int32 `json:"receive_errors"`
}
func (q *Queries) GetObserverTelemetry(ctx context.Context, arg GetObserverTelemetryParams) ([]GetObserverTelemetryRow, error) {
rows, err := q.db.Query(ctx, getObserverTelemetry,
arg.ObserverID,
arg.Column2,
arg.Column3,
arg.Column4,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetObserverTelemetryRow{}
for rows.Next() {
var i GetObserverTelemetryRow
if err := rows.Scan(
&i.ID,
&i.ReportedAt,
&i.BatteryVoltageMv,
&i.AirtimeTxSecs,
&i.AirtimeRxSecs,
&i.NoiseFloorDb,
&i.UptimeSeconds,
&i.QueueLength,
&i.DebugFlags,
&i.ReceiveErrors,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getPacketByHash = `-- name: GetPacketByHash :one
SELECT p.packet_hash, p.payload_type, p.payload_version, p.route_type, p.transport_codes_present, p.region_code, p.sub_region_code, p.scope_id, p.origin_pubkey, p.raw_payload, p.raw_header, p.parsed_payload, p.decrypted, p.channel_hash, p.trace_tag, p.first_heard_at, p.last_heard_at, p.observation_count, ts.name AS scope_name,
cm.sender_name AS cm_sender_name,
cm.content AS cm_content,
cm.sent_at AS cm_sent_at
FROM packets p
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
LEFT JOIN channel_messages cm ON cm.packet_hash = p.packet_hash
WHERE p.packet_hash = $1
`
type GetPacketByHashRow struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
PayloadVersion int16 `json:"payload_version"`
RouteType int16 `json:"route_type"`
TransportCodesPresent *bool `json:"transport_codes_present"`
RegionCode *int32 `json:"region_code"`
SubRegionCode *int32 `json:"sub_region_code"`
ScopeID *int32 `json:"scope_id"`
OriginPubkey []byte `json:"origin_pubkey"`
RawPayload []byte `json:"raw_payload"`
RawHeader []byte `json:"raw_header"`
ParsedPayload []byte `json:"parsed_payload"`
Decrypted *bool `json:"decrypted"`
ChannelHash []byte `json:"channel_hash"`
TraceTag []byte `json:"trace_tag"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ObservationCount int64 `json:"observation_count"`
ScopeName *string `json:"scope_name"`
CmSenderName *string `json:"cm_sender_name"`
CmContent *string `json:"cm_content"`
CmSentAt pgtype.Timestamptz `json:"cm_sent_at"`
}
func (q *Queries) GetPacketByHash(ctx context.Context, packetHash []byte) (GetPacketByHashRow, error) {
row := q.db.QueryRow(ctx, getPacketByHash, packetHash)
var i GetPacketByHashRow
err := row.Scan(
&i.PacketHash,
&i.PayloadType,
&i.PayloadVersion,
&i.RouteType,
&i.TransportCodesPresent,
&i.RegionCode,
&i.SubRegionCode,
&i.ScopeID,
&i.OriginPubkey,
&i.RawPayload,
&i.RawHeader,
&i.ParsedPayload,
&i.Decrypted,
&i.ChannelHash,
&i.TraceTag,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.ObservationCount,
&i.ScopeName,
&i.CmSenderName,
&i.CmContent,
&i.CmSentAt,
)
return i, err
}
const getPacketsByTraceTag = `-- name: GetPacketsByTraceTag :many
SELECT encode(p.packet_hash, 'hex') AS packet_hash_hex,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.parsed_payload,
p.scope_id,
ts.name AS scope_name,
ARRAY(
SELECT po.iata
FROM packet_observations po
WHERE po.packet_hash = p.packet_hash
GROUP BY po.iata
ORDER BY MIN(po.heard_at), po.iata
)::bpchar[] AS iatas
FROM packets p
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE p.trace_tag = decode($1, 'hex')
ORDER BY p.first_heard_at ASC
`
type GetPacketsByTraceTagRow struct {
PacketHashHex string `json:"packet_hash_hex"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ParsedPayload []byte `json:"parsed_payload"`
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
Iatas []string `json:"iatas"`
}
// Return distinct observation IATAs in first-heard order for path resolution,
// without fetching full observations separately for every trace packet.
func (q *Queries) GetPacketsByTraceTag(ctx context.Context, decode string) ([]GetPacketsByTraceTagRow, error) {
rows, err := q.db.Query(ctx, getPacketsByTraceTag, decode)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetPacketsByTraceTagRow{}
for rows.Next() {
var i GetPacketsByTraceTagRow
if err := rows.Scan(
&i.PacketHashHex,
&i.RouteType,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.ParsedPayload,
&i.ScopeID,
&i.ScopeName,
&i.Iatas,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getRadioPresets = `-- name: GetRadioPresets :many
SELECT preset, iata, source_type, count
FROM mv_radio_presets
WHERE ($1::text = '' OR preset = $1::text)
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[]))
ORDER BY preset, iata, source_type
`
type GetRadioPresetsParams struct {
Column1 string `json:"column_1"`
Column2 []string `json:"column_2"`
}
func (q *Queries) GetRadioPresets(ctx context.Context, arg GetRadioPresetsParams) ([]MvRadioPreset, error) {
rows, err := q.db.Query(ctx, getRadioPresets, arg.Column1, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MvRadioPreset{}
for rows.Next() {
var i MvRadioPreset
if err := rows.Scan(
&i.Preset,
&i.Iata,
&i.SourceType,
&i.Count,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getRegion = `-- name: GetRegion :one
SELECT id, slug, name, description, center_lat, center_lng, zoom_level
FROM regions
WHERE id = $1
`
type GetRegionRow struct {
ID int32 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Description *string `json:"description"`
CenterLat *float64 `json:"center_lat"`
CenterLng *float64 `json:"center_lng"`
ZoomLevel *int32 `json:"zoom_level"`
}
func (q *Queries) GetRegion(ctx context.Context, id int32) (GetRegionRow, error) {
row := q.db.QueryRow(ctx, getRegion, id)
var i GetRegionRow
err := row.Scan(
&i.ID,
&i.Slug,
&i.Name,
&i.Description,
&i.CenterLat,
&i.CenterLng,
&i.ZoomLevel,
)
return i, err
}
const getRegionBySlug = `-- name: GetRegionBySlug :one
SELECT id, slug, name, description, center_lat, center_lng, zoom_level
FROM regions
WHERE slug = $1
`
type GetRegionBySlugRow struct {
ID int32 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Description *string `json:"description"`
CenterLat *float64 `json:"center_lat"`
CenterLng *float64 `json:"center_lng"`
ZoomLevel *int32 `json:"zoom_level"`
}
func (q *Queries) GetRegionBySlug(ctx context.Context, slug string) (GetRegionBySlugRow, error) {
row := q.db.QueryRow(ctx, getRegionBySlug, slug)
var i GetRegionBySlugRow
err := row.Scan(
&i.ID,
&i.Slug,
&i.Name,
&i.Description,
&i.CenterLat,
&i.CenterLng,
&i.ZoomLevel,
)
return i, err
}
const getRegionIATAs = `-- name: GetRegionIATAs :many
SELECT iata FROM region_iatas
WHERE region_id = $1
ORDER BY iata
`
func (q *Queries) GetRegionIATAs(ctx context.Context, regionID int32) ([]string, error) {
rows, err := q.db.Query(ctx, getRegionIATAs, regionID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var iata string
if err := rows.Scan(&iata); err != nil {
return nil, err
}
items = append(items, iata)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getScopeByName = `-- name: GetScopeByName :one
SELECT
ts.name,
COALESCE((SELECT SUM(s.packets) FROM analytics_hourly_scope_sets s WHERE s.scope_id = ts.id), 0)::bigint AS packet_count,
(SELECT COUNT(*) FROM observer_scopes os WHERE os.scope_id = ts.id)::bigint AS observer_count,
(SELECT COUNT(*) FROM nodes n WHERE n.default_scope_id = ts.id)::bigint AS node_count,
cardinality(i.iatas)::bigint AS iata_count,
i.iatas
FROM transport_scopes ts
CROSS JOIN LATERAL (
SELECT COALESCE(array_agg(DISTINCT x.iata::text ORDER BY x.iata::text), '{}')::text[] AS iatas
FROM analytics_hourly_scope_sets s CROSS JOIN LATERAL unnest(s.iatas) AS x(iata)
WHERE s.scope_id = ts.id
) i
WHERE ts.name = $1
`
type GetScopeByNameRow struct {
Name string `json:"name"`
PacketCount int64 `json:"packet_count"`
ObserverCount int64 `json:"observer_count"`
NodeCount int64 `json:"node_count"`
IataCount int64 `json:"iata_count"`
Iatas []string `json:"iatas"`
}
// Packets and IATAs cover every retained rollup hour; IATAs are those that heard the
// scope's own packets. Observer and node counts are current memberships.
func (q *Queries) GetScopeByName(ctx context.Context, name string) (GetScopeByNameRow, error) {
row := q.db.QueryRow(ctx, getScopeByName, name)
var i GetScopeByNameRow
err := row.Scan(
&i.Name,
&i.PacketCount,
&i.ObserverCount,
&i.NodeCount,
&i.IataCount,
&i.Iatas,
)
return i, err
}
const getScopeCatalogue = `-- name: GetScopeCatalogue :one
SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_scope_catalogues WHERE iata = $1 AND url = $2
`
type GetScopeCatalogueParams struct {
Iata string `json:"iata"`
Url string `json:"url"`
}
func (q *Queries) GetScopeCatalogue(ctx context.Context, arg GetScopeCatalogueParams) (MeshmapperScopeCatalogue, error) {
row := q.db.QueryRow(ctx, getScopeCatalogue, arg.Iata, arg.Url)
var i MeshmapperScopeCatalogue
err := row.Scan(
&i.Iata,
&i.Url,
&i.Payload,
&i.Etag,
&i.CheckedAt,
&i.AttemptedAt,
&i.NextAttempt,
&i.LastError,
)
return i, err
}
const getScopeNames = `-- name: GetScopeNames :many
SELECT name FROM transport_scopes ORDER BY name
`
func (q *Queries) GetScopeNames(ctx context.Context) ([]string, error) {
rows, err := q.db.Query(ctx, getScopeNames)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
items = append(items, name)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getScopeStats = `-- name: GetScopeStats :many
WITH packet_counts AS (
SELECT s.scope_id, SUM(s.packets)::bigint AS packet_count
FROM analytics_hourly_scope_sets s
WHERE s.hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR s.iatas && $2::bpchar[])
GROUP BY s.scope_id
), observer_counts AS (
SELECT os.scope_id, COUNT(*)::bigint AS observer_count
FROM observer_scopes os JOIN observers o ON o.id = os.observer_id
WHERE COALESCE(cardinality($2::bpchar[]), 0) = 0 OR o.last_iata = ANY($2::bpchar[])
GROUP BY os.scope_id
), node_counts AS (
SELECT n.default_scope_id AS scope_id, COUNT(*)::bigint AS node_count
FROM nodes n
WHERE n.default_scope_id IS NOT NULL
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0
OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[])))
GROUP BY n.default_scope_id
)
SELECT
ts.name,
COALESCE(pc.packet_count, 0)::bigint AS packet_count,
COALESCE(oc.observer_count, 0)::bigint AS observer_count,
COALESCE(nc.node_count, 0)::bigint AS node_count
FROM transport_scopes ts
LEFT JOIN packet_counts pc ON pc.scope_id = ts.id
LEFT JOIN observer_counts oc ON oc.scope_id = ts.id
LEFT JOIN node_counts nc ON nc.scope_id = ts.id
ORDER BY ts.name
`
type GetScopeStatsParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
}
type GetScopeStatsRow struct {
Name string `json:"name"`
PacketCount int64 `json:"packet_count"`
ObserverCount int64 `json:"observer_count"`
NodeCount int64 `json:"node_count"`
}
// Packets since the given hour come from the IATA-set rollup (counted once per hour heard).
// Observer and node counts are current memberships; observers filter by their latest IATA.
func (q *Queries) GetScopeStats(ctx context.Context, arg GetScopeStatsParams) ([]GetScopeStatsRow, error) {
rows, err := q.db.Query(ctx, getScopeStats, arg.Since, arg.Iatas)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetScopeStatsRow{}
for rows.Next() {
var i GetScopeStatsRow
if err := rows.Scan(
&i.Name,
&i.PacketCount,
&i.ObserverCount,
&i.NodeCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getScopeStatsHourly = `-- name: GetScopeStatsHourly :many
WITH pk AS (
SELECT s.scope_id, s.hour, SUM(s.packets)::bigint AS n
FROM analytics_hourly_scope_sets s
WHERE s.hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR s.iatas && $2::bpchar[])
GROUP BY s.scope_id, s.hour
), ob AS (
SELECT o.scope_id, o.hour, COUNT(DISTINCT o.observer_id)::bigint AS n
FROM analytics_hourly_scope_observers o
WHERE o.hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR o.iata = ANY($2::bpchar[]))
GROUP BY o.scope_id, o.hour
), nd AS (
SELECT d.scope_id, d.hour, COUNT(DISTINCT d.origin_pubkey)::bigint AS n
FROM analytics_hourly_scope_nodes d
WHERE d.hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR d.iata = ANY($2::bpchar[]))
GROUP BY d.scope_id, d.hour
), k AS (
SELECT scope_id, hour FROM pk UNION SELECT scope_id, hour FROM ob UNION SELECT scope_id, hour FROM nd
)
SELECT ts.name, k.hour,
COALESCE(pk.n, 0)::bigint AS packets,
COALESCE(ob.n, 0)::bigint AS observers,
COALESCE(nd.n, 0)::bigint AS nodes
FROM k
JOIN transport_scopes ts ON ts.id = k.scope_id
LEFT JOIN pk ON pk.scope_id = k.scope_id AND pk.hour = k.hour
LEFT JOIN ob ON ob.scope_id = k.scope_id AND ob.hour = k.hour
LEFT JOIN nd ON nd.scope_id = k.scope_id AND nd.hour = k.hour
WHERE COALESCE(pk.n, 0) > 0 OR COALESCE(ob.n, 0) > 0 OR COALESCE(nd.n, 0) > 0
ORDER BY ts.name, k.hour
`
type GetScopeStatsHourlyParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
}
type GetScopeStatsHourlyRow struct {
Name string `json:"name"`
Hour pgtype.Timestamptz `json:"hour"`
Packets int64 `json:"packets"`
Observers int64 `json:"observers"`
Nodes int64 `json:"nodes"`
}
// GetScopeStats packet counts split by hour (same window and IATA-set filter), with the
// distinct observers and advertising nodes active in each scope that hour. Empty hours omitted.
func (q *Queries) GetScopeStatsHourly(ctx context.Context, arg GetScopeStatsHourlyParams) ([]GetScopeStatsHourlyRow, error) {
rows, err := q.db.Query(ctx, getScopeStatsHourly, arg.Since, arg.Iatas)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetScopeStatsHourlyRow{}
for rows.Next() {
var i GetScopeStatsHourlyRow
if err := rows.Scan(
&i.Name,
&i.Hour,
&i.Packets,
&i.Observers,
&i.Nodes,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsClockDrift = `-- name: GetStatsClockDrift :many
WITH page AS (
SELECT
n.id,
n.name,
n.node_type,
n.device_clock_drift_seconds,
n.last_advert_at
FROM nodes n
WHERE n.node_type IN (2, 3)
AND n.device_clock_drift_seconds IS NOT NULL
AND ABS(n.device_clock_drift_seconds) > $1::int
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[])))
ORDER BY ABS(n.device_clock_drift_seconds) DESC
LIMIT $3
)
SELECT n.id, n.name, n.node_type, n.device_clock_drift_seconds, n.last_advert_at,
(SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC)
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas
FROM page n
ORDER BY ABS(n.device_clock_drift_seconds) DESC
`
type GetStatsClockDriftParams struct {
Column1 int32 `json:"column_1"`
Column2 []string `json:"column_2"`
Limit int32 `json:"limit"`
}
type GetStatsClockDriftRow struct {
ID uuid.UUID `json:"id"`
Name *string `json:"name"`
NodeType int16 `json:"node_type"`
DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"`
LastAdvertAt pgtype.Timestamptz `json:"last_advert_at"`
Iatas []byte `json:"iatas"`
}
// Repeaters/room servers (node_type 2/3) whose current advert-derived clock drift exceeds
// the given threshold in magnitude, worst first. Not time-windowed -- reflects each node's
// latest measured drift, not an aggregate over a period.
// Select the filtered page before aggregating its IATA memberships.
func (q *Queries) GetStatsClockDrift(ctx context.Context, arg GetStatsClockDriftParams) ([]GetStatsClockDriftRow, error) {
rows, err := q.db.Query(ctx, getStatsClockDrift, arg.Column1, arg.Column2, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsClockDriftRow{}
for rows.Next() {
var i GetStatsClockDriftRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.NodeType,
&i.DeviceClockDriftSeconds,
&i.LastAdvertAt,
&i.Iatas,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsNodeTypes = `-- name: GetStatsNodeTypes :many
SELECT
n.node_type,
COUNT(DISTINCT n.id)::bigint AS count
FROM nodes n
LEFT JOIN node_iatas ni ON ni.node_id = n.id
WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR ni.iata = ANY($1::bpchar[]))
GROUP BY n.node_type
ORDER BY count DESC
`
type GetStatsNodeTypesRow struct {
NodeType int16 `json:"node_type"`
Count int64 `json:"count"`
}
// Returns node counts grouped by type, optionally filtered by IATA.
func (q *Queries) GetStatsNodeTypes(ctx context.Context, dollar_1 []string) ([]GetStatsNodeTypesRow, error) {
rows, err := q.db.Query(ctx, getStatsNodeTypes, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsNodeTypesRow{}
for rows.Next() {
var i GetStatsNodeTypesRow
if err := rows.Scan(&i.NodeType, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsPayloadBreakdown = `-- name: GetStatsPayloadBreakdown :many
SELECT
payload_type,
SUM(count)::bigint AS count
FROM analytics_hourly_payload_breakdown
WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR iata = ANY($1::bpchar[]))
AND hour >= $2::timestamptz
GROUP BY payload_type
ORDER BY count DESC
`
type GetStatsPayloadBreakdownParams struct {
Iatas []string `json:"iatas"`
Since pgtype.Timestamptz `json:"since"`
}
type GetStatsPayloadBreakdownRow struct {
PayloadType int16 `json:"payload_type"`
Count int64 `json:"count"`
}
// Payload-type observation counts since the given hour, from the hourly rollup.
func (q *Queries) GetStatsPayloadBreakdown(ctx context.Context, arg GetStatsPayloadBreakdownParams) ([]GetStatsPayloadBreakdownRow, error) {
rows, err := q.db.Query(ctx, getStatsPayloadBreakdown, arg.Iatas, arg.Since)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsPayloadBreakdownRow{}
for rows.Next() {
var i GetStatsPayloadBreakdownRow
if err := rows.Scan(&i.PayloadType, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsTopAdvertisers = `-- name: GetStatsTopAdvertisers :many
WITH ranked AS (
SELECT origin_pubkey, SUM(advert_packets)::bigint AS advert_count,
SUM(flood_packets)::bigint AS flood_advert_count, SUM(direct_packets)::bigint AS direct_advert_count
FROM analytics_hourly_advert_sets
WHERE hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iatas && $2::bpchar[])
GROUP BY origin_pubkey
ORDER BY advert_count DESC, origin_pubkey
LIMIT $3
), heard AS (
SELECT h.origin_pubkey, MAX(h.last_heard) AS last_heard, MAX(h.iata) AS iata,
MAX(h.name) AS name, MAX(h.node_type) AS node_type
FROM analytics_hourly_advert_hearings h
WHERE h.hour >= $1::timestamptz AND h.origin_pubkey IN (SELECT origin_pubkey FROM ranked)
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR h.iata = ANY($2::bpchar[]))
GROUP BY h.origin_pubkey
)
SELECT n.id AS node_id, encode(r.origin_pubkey, 'hex') AS public_key,
COALESCE(n.name, h.name, '')::text AS name, COALESCE(n.node_type, h.node_type, 0)::smallint AS node_type,
r.advert_count, r.flood_advert_count, r.direct_advert_count,
h.last_heard::timestamptz AS last_heard, COALESCE(h.iata, '')::bpchar AS iata
FROM ranked r
LEFT JOIN heard h ON h.origin_pubkey = r.origin_pubkey
LEFT JOIN nodes n ON n.public_key = r.origin_pubkey
ORDER BY r.advert_count DESC, r.origin_pubkey
`
type GetStatsTopAdvertisersParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
RowLimit int32 `json:"row_limit"`
}
type GetStatsTopAdvertisersRow struct {
NodeID pgtype.UUID `json:"node_id"`
PublicKey string `json:"public_key"`
Name string `json:"name"`
NodeType int16 `json:"node_type"`
AdvertCount int64 `json:"advert_count"`
FloodAdvertCount int64 `json:"flood_advert_count"`
DirectAdvertCount int64 `json:"direct_advert_count"`
LastHeard pgtype.Timestamptz `json:"last_heard"`
Iata string `json:"iata"`
}
// Top N advertisers since the given hour. Each ADVERT packet counts once per hour heard,
// however many of the requested IATAs heard it (IATA-set rollup). Live names win.
func (q *Queries) GetStatsTopAdvertisers(ctx context.Context, arg GetStatsTopAdvertisersParams) ([]GetStatsTopAdvertisersRow, error) {
rows, err := q.db.Query(ctx, getStatsTopAdvertisers, arg.Since, arg.Iatas, arg.RowLimit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsTopAdvertisersRow{}
for rows.Next() {
var i GetStatsTopAdvertisersRow
if err := rows.Scan(
&i.NodeID,
&i.PublicKey,
&i.Name,
&i.NodeType,
&i.AdvertCount,
&i.FloodAdvertCount,
&i.DirectAdvertCount,
&i.LastHeard,
&i.Iata,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsTopObservers = `-- name: GetStatsTopObservers :many
WITH ranked AS (
SELECT observer_id, SUM(observation_count)::bigint AS observation_count, MAX(iata) AS iata,
MAX(display_name) AS display_name, MAX(observer_type) AS observer_type
FROM analytics_hourly_observer_identity
WHERE hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[]))
GROUP BY observer_id
ORDER BY observation_count DESC, observer_id
LIMIT $3
)
SELECT r.observer_id AS id, COALESCE(o.display_name, r.display_name, '')::text AS display_name,
COALESCE(o.observer_type, r.observer_type, '')::text AS observer_type,
r.observation_count, r.iata::bpchar AS iata
FROM ranked r
LEFT JOIN observers o ON o.id = r.observer_id
ORDER BY r.observation_count DESC, r.observer_id
`
type GetStatsTopObserversParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
RowLimit int32 `json:"row_limit"`
}
type GetStatsTopObserversRow struct {
ID uuid.UUID `json:"id"`
DisplayName string `json:"display_name"`
ObserverType string `json:"observer_type"`
ObservationCount int64 `json:"observation_count"`
Iata string `json:"iata"`
}
// Top N observers since the given hour. Counts sum across matched IATAs; iata is a
// representative one. Live names win over the rolled snapshot.
func (q *Queries) GetStatsTopObservers(ctx context.Context, arg GetStatsTopObserversParams) ([]GetStatsTopObserversRow, error) {
rows, err := q.db.Query(ctx, getStatsTopObservers, arg.Since, arg.Iatas, arg.RowLimit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsTopObserversRow{}
for rows.Next() {
var i GetStatsTopObserversRow
if err := rows.Scan(
&i.ID,
&i.DisplayName,
&i.ObserverType,
&i.ObservationCount,
&i.Iata,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatsTopTalkers = `-- name: GetStatsTopTalkers :many
SELECT
sender_name,
SUM(messages)::bigint AS message_count,
MAX(last_sent)::timestamptz AS last_sent
FROM analytics_hourly_talker_sets
WHERE hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iatas && $2::bpchar[])
GROUP BY sender_name
ORDER BY message_count DESC, sender_name
LIMIT $3
`
type GetStatsTopTalkersParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
RowLimit int32 `json:"row_limit"`
}
type GetStatsTopTalkersRow struct {
SenderName string `json:"sender_name"`
MessageCount int64 `json:"message_count"`
LastSent pgtype.Timestamptz `json:"last_sent"`
}
// Top N senders since the given hour. Each message counts once per hour heard, however many
// of the requested IATAs heard it (IATA-set rollup).
func (q *Queries) GetStatsTopTalkers(ctx context.Context, arg GetStatsTopTalkersParams) ([]GetStatsTopTalkersRow, error) {
rows, err := q.db.Query(ctx, getStatsTopTalkers, arg.Since, arg.Iatas, arg.RowLimit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetStatsTopTalkersRow{}
for rows.Next() {
var i GetStatsTopTalkersRow
if err := rows.Scan(&i.SenderName, &i.MessageCount, &i.LastSent); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTopNodes = `-- name: GetTopNodes :many
WITH ranked AS (
SELECT origin_pubkey, SUM(observations)::bigint AS observation_count, MAX(last_heard) AS last_heard,
MAX(iata) AS iata, MAX(name) AS name, MAX(node_type) AS node_type
FROM analytics_hourly_advert_hearings
WHERE hour >= $1::timestamptz
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[]))
GROUP BY origin_pubkey
ORDER BY observation_count DESC, origin_pubkey
LIMIT $3
)
SELECT n.id AS node_id, encode(r.origin_pubkey, 'hex') AS public_key,
COALESCE(n.name, r.name, '')::text AS name, COALESCE(n.node_type, r.node_type, 0)::smallint AS node_type,
r.iata::bpchar AS iata, r.observation_count, r.last_heard::timestamptz AS last_heard
FROM ranked r
LEFT JOIN nodes n ON n.public_key = r.origin_pubkey
ORDER BY r.observation_count DESC, r.origin_pubkey
`
type GetTopNodesParams struct {
Since pgtype.Timestamptz `json:"since"`
Iatas []string `json:"iatas"`
RowLimit int32 `json:"row_limit"`
}
type GetTopNodesRow struct {
NodeID pgtype.UUID `json:"node_id"`
PublicKey string `json:"public_key"`
Name string `json:"name"`
NodeType int16 `json:"node_type"`
Iata string `json:"iata"`
ObservationCount int64 `json:"observation_count"`
LastHeard pgtype.Timestamptz `json:"last_heard"`
}
// Nodes by ADVERT hearings since the given hour. Live names win over the rolled snapshot;
// node_id is NULL once the node row is gone. iata is a representative one.
func (q *Queries) GetTopNodes(ctx context.Context, arg GetTopNodesParams) ([]GetTopNodesRow, error) {
rows, err := q.db.Query(ctx, getTopNodes, arg.Since, arg.Iatas, arg.RowLimit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetTopNodesRow{}
for rows.Next() {
var i GetTopNodesRow
if err := rows.Scan(
&i.NodeID,
&i.PublicKey,
&i.Name,
&i.NodeType,
&i.Iata,
&i.ObservationCount,
&i.LastHeard,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTransportScopeByName = `-- name: GetTransportScopeByName :one
SELECT id FROM transport_scopes WHERE name = $1
`
func (q *Queries) GetTransportScopeByName(ctx context.Context, name string) (int32, error) {
row := q.db.QueryRow(ctx, getTransportScopeByName, name)
var id int32
err := row.Scan(&id)
return id, err
}
const getTransportScopes = `-- name: GetTransportScopes :many
SELECT name, transport_key, key_fingerprint FROM transport_scopes WHERE NOT imported_only ORDER BY name
`
type GetTransportScopesRow struct {
Name string `json:"name"`
TransportKey []byte `json:"transport_key"`
KeyFingerprint []byte `json:"key_fingerprint"`
}
func (q *Queries) GetTransportScopes(ctx context.Context) ([]GetTransportScopesRow, error) {
rows, err := q.db.Query(ctx, getTransportScopes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetTransportScopesRow{}
for rows.Next() {
var i GetTransportScopesRow
if err := rows.Scan(&i.Name, &i.TransportKey, &i.KeyFingerprint); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertChannelMessage = `-- name: InsertChannelMessage :one
WITH inserted AS (
INSERT INTO channel_messages (channel_id, packet_hash, sender_name, content, sent_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (packet_hash) DO NOTHING
RETURNING id, packet_hash, channel_id
), bump AS (
UPDATE channels c SET message_count = c.message_count + 1
FROM inserted WHERE c.id = inserted.channel_id
), dirty AS (
INSERT INTO analytics_dirty_hours (hour)
SELECT DISTINCT date_trunc('hour', po.heard_at, 'UTC')
FROM packet_observations po JOIN inserted i ON i.packet_hash = po.packet_hash
WHERE $6::boolean
ON CONFLICT (hour) DO UPDATE SET enqueued_at = now()
)
SELECT inserted.id, ts.name AS scope_name, p.transport_codes_present
FROM inserted
JOIN packets p ON p.packet_hash = inserted.packet_hash
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
`
type InsertChannelMessageParams struct {
ChannelID int32 `json:"channel_id"`
PacketHash []byte `json:"packet_hash"`
SenderName *string `json:"sender_name"`
Content *string `json:"content"`
SentAt pgtype.Timestamptz `json:"sent_at"`
Column6 bool `json:"column_6"`
}
type InsertChannelMessageRow struct {
ID int64 `json:"id"`
ScopeName *string `json:"scope_name"`
TransportCodesPresent *bool `json:"transport_codes_present"`
}
// ============================================================
// CHANNEL MESSAGES
// ============================================================
// Read the immutable first-packet scope in the same statement as insertion.
// A later reception's transport code must not give live and historical messages different tags.
// message_count is a lifetime count, bumped only for a new message. A historical (backfilled)
// message queues its observation hours for a re-roll in the same statement, so the two can't diverge.
func (q *Queries) InsertChannelMessage(ctx context.Context, arg InsertChannelMessageParams) (InsertChannelMessageRow, error) {
row := q.db.QueryRow(ctx, insertChannelMessage,
arg.ChannelID,
arg.PacketHash,
arg.SenderName,
arg.Content,
arg.SentAt,
arg.Column6,
)
var i InsertChannelMessageRow
err := row.Scan(&i.ID, &i.ScopeName, &i.TransportCodesPresent)
return i, err
}
const insertObservation = `-- name: InsertObservation :one
WITH ins AS (
INSERT INTO packet_observations (
packet_hash,
observer_id,
iata,
heard_at,
path_length_byte,
hash_size,
hop_count,
path_bytes,
rssi,
snr,
propagation_time_ms,
radio_freq_mhz,
spread_factor,
bandwidth_khz,
coding_rate,
source_broker,
payload_type,
airtime_ms
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
)
ON CONFLICT (packet_hash, observer_id) DO NOTHING
RETURNING packet_hash
), bump AS (
UPDATE packets p SET observation_count = p.observation_count + 1
FROM ins WHERE p.packet_hash = ins.packet_hash
RETURNING p.observation_count
)
SELECT EXISTS (SELECT 1 FROM ins) AS inserted,
COALESCE((SELECT b.observation_count FROM bump b),
(SELECT pk.observation_count FROM packets pk WHERE pk.packet_hash = $1), 0)::bigint AS observation_count
`
type InsertObservationParams struct {
PacketHash []byte `json:"packet_hash"`
ObserverID uuid.UUID `json:"observer_id"`
Iata string `json:"iata"`
HeardAt pgtype.Timestamptz `json:"heard_at"`
PathLengthByte int16 `json:"path_length_byte"`
HashSize int16 `json:"hash_size"`
HopCount int16 `json:"hop_count"`
PathBytes []byte `json:"path_bytes"`
Rssi *int16 `json:"rssi"`
Snr *float32 `json:"snr"`
PropagationTimeMs *int32 `json:"propagation_time_ms"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
SpreadFactor *int16 `json:"spread_factor"`
BandwidthKhz *float32 `json:"bandwidth_khz"`
CodingRate *int16 `json:"coding_rate"`
SourceBroker *string `json:"source_broker"`
PayloadType *int16 `json:"payload_type"`
AirtimeMs *float32 `json:"airtime_ms"`
}
type InsertObservationRow struct {
Inserted bool `json:"inserted"`
ObservationCount int64 `json:"observation_count"`
}
// ============================================================
// PACKET OBSERVATIONS
// ============================================================
// Bumps packets.observation_count only for a new row; a duplicate returns the current count.
func (q *Queries) InsertObservation(ctx context.Context, arg InsertObservationParams) (InsertObservationRow, error) {
row := q.db.QueryRow(ctx, insertObservation,
arg.PacketHash,
arg.ObserverID,
arg.Iata,
arg.HeardAt,
arg.PathLengthByte,
arg.HashSize,
arg.HopCount,
arg.PathBytes,
arg.Rssi,
arg.Snr,
arg.PropagationTimeMs,
arg.RadioFreqMhz,
arg.SpreadFactor,
arg.BandwidthKhz,
arg.CodingRate,
arg.SourceBroker,
arg.PayloadType,
arg.AirtimeMs,
)
var i InsertObservationRow
err := row.Scan(&i.Inserted, &i.ObservationCount)
return i, err
}
const insertObserverTelemetry = `-- name: InsertObserverTelemetry :exec
INSERT INTO observer_telemetry (
observer_id, reported_at, battery_voltage_mv, airtime_tx_secs,
airtime_rx_secs, noise_floor_db, uptime_seconds, queue_length,
debug_flags, receive_errors
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT (observer_id, reported_at) DO NOTHING
`
type InsertObserverTelemetryParams struct {
ObserverID uuid.UUID `json:"observer_id"`
ReportedAt pgtype.Timestamptz `json:"reported_at"`
BatteryVoltageMv *int32 `json:"battery_voltage_mv"`
AirtimeTxSecs *float32 `json:"airtime_tx_secs"`
AirtimeRxSecs *float32 `json:"airtime_rx_secs"`
NoiseFloorDb *float32 `json:"noise_floor_db"`
UptimeSeconds *int64 `json:"uptime_seconds"`
QueueLength *int32 `json:"queue_length"`
DebugFlags *int32 `json:"debug_flags"`
ReceiveErrors *int32 `json:"receive_errors"`
}
// Inserts a telemetry snapshot for an observer. The reported_at timestamp should
// be truncated to the configured resolution before calling to ensure deduplication.
func (q *Queries) InsertObserverTelemetry(ctx context.Context, arg InsertObserverTelemetryParams) error {
_, err := q.db.Exec(ctx, insertObserverTelemetry,
arg.ObserverID,
arg.ReportedAt,
arg.BatteryVoltageMv,
arg.AirtimeTxSecs,
arg.AirtimeRxSecs,
arg.NoiseFloorDb,
arg.UptimeSeconds,
arg.QueueLength,
arg.DebugFlags,
arg.ReceiveErrors,
)
return err
}
const listAccounts = `-- name: ListAccounts :many
SELECT id, name, created_at, deactivated_at FROM accounts
ORDER BY created_at DESC, id DESC
`
func (q *Queries) ListAccounts(ctx context.Context) ([]Account, error) {
rows, err := q.db.Query(ctx, listAccounts)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Account{}
for rows.Next() {
var i Account
if err := rows.Scan(
&i.ID,
&i.Name,
&i.CreatedAt,
&i.DeactivatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAllChannelMessages = `-- name: ListAllChannelMessages :many
SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present,
p.observation_count AS observation_count
FROM channel_messages cm
JOIN channels c ON c.id = cm.channel_id
JOIN packet_observations po ON po.packet_hash = cm.packet_hash
JOIN packets p ON p.packet_hash = cm.packet_hash
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE ($1::timestamptz IS NULL OR cm.sent_at >= $1)
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR po.iata = ANY($2::bpchar[]))
AND ($3::text = '' OR ts.name = $3::text)
AND ($4 = 0 OR cm.id < $4)
ORDER BY cm.id DESC
LIMIT $5
`
type ListAllChannelMessagesParams struct {
Column1 pgtype.Timestamptz `json:"column_1"`
Column2 []string `json:"column_2"`
Column3 string `json:"column_3"`
Column4 interface{} `json:"column_4"`
Limit int32 `json:"limit"`
}
type ListAllChannelMessagesRow struct {
ID int64 `json:"id"`
ChannelID int32 `json:"channel_id"`
PacketHash []byte `json:"packet_hash"`
SenderName *string `json:"sender_name"`
SenderPubkey []byte `json:"sender_pubkey"`
Content *string `json:"content"`
SentAt pgtype.Timestamptz `json:"sent_at"`
PacketHashHex string `json:"packet_hash_hex"`
ChannelHash []byte `json:"channel_hash"`
ScopeName *string `json:"scope_name"`
TransportCodesPresent *bool `json:"transport_codes_present"`
ObservationCount int64 `json:"observation_count"`
}
// Returns all messages across all channels with optional time, IATA, scope and cursor filters.
// Pass empty string for iata or scope to skip those filters.
// Pass cursor=0 to start from the beginning.
func (q *Queries) ListAllChannelMessages(ctx context.Context, arg ListAllChannelMessagesParams) ([]ListAllChannelMessagesRow, error) {
rows, err := q.db.Query(ctx, listAllChannelMessages,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAllChannelMessagesRow{}
for rows.Next() {
var i ListAllChannelMessagesRow
if err := rows.Scan(
&i.ID,
&i.ChannelID,
&i.PacketHash,
&i.SenderName,
&i.SenderPubkey,
&i.Content,
&i.SentAt,
&i.PacketHashHex,
&i.ChannelHash,
&i.ScopeName,
&i.TransportCodesPresent,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listChannelCatalogues = `-- name: ListChannelCatalogues :many
SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_channel_catalogues ORDER BY iata, attempted_at
`
func (q *Queries) ListChannelCatalogues(ctx context.Context) ([]MeshmapperChannelCatalogue, error) {
rows, err := q.db.Query(ctx, listChannelCatalogues)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MeshmapperChannelCatalogue{}
for rows.Next() {
var i MeshmapperChannelCatalogue
if err := rows.Scan(
&i.Iata,
&i.Url,
&i.Payload,
&i.Etag,
&i.CheckedAt,
&i.AttemptedAt,
&i.NextAttempt,
&i.LastError,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listChannelMessages = `-- name: ListChannelMessages :many
SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present,
p.observation_count AS observation_count
FROM channel_messages cm
JOIN channels c ON c.id = cm.channel_id
JOIN packet_observations po ON po.packet_hash = cm.packet_hash
JOIN packets p ON p.packet_hash = cm.packet_hash
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE cm.channel_id = $1
AND ($2::timestamptz IS NULL OR cm.sent_at >= $2)
AND (COALESCE(cardinality($3::bpchar[]), 0) = 0 OR po.iata = ANY($3::bpchar[]))
AND ($4::text = '' OR ts.name = $4::text)
AND ($5::bigint = 0 OR cm.id < $5::bigint)
ORDER BY cm.id DESC
LIMIT $6
`
type ListChannelMessagesParams struct {
ChannelID int32 `json:"channel_id"`
Column2 pgtype.Timestamptz `json:"column_2"`
Column3 []string `json:"column_3"`
Column4 string `json:"column_4"`
Column5 int64 `json:"column_5"`
Limit int32 `json:"limit"`
}
type ListChannelMessagesRow struct {
ID int64 `json:"id"`
ChannelID int32 `json:"channel_id"`
PacketHash []byte `json:"packet_hash"`
SenderName *string `json:"sender_name"`
SenderPubkey []byte `json:"sender_pubkey"`
Content *string `json:"content"`
SentAt pgtype.Timestamptz `json:"sent_at"`
PacketHashHex string `json:"packet_hash_hex"`
ChannelHash []byte `json:"channel_hash"`
ScopeName *string `json:"scope_name"`
TransportCodesPresent *bool `json:"transport_codes_present"`
ObservationCount int64 `json:"observation_count"`
}
// Returns messages for a channel identified by integer ID.
// Pass a zero/null timestamp for since to return all messages up to limit.
// Pass empty string for iata to skip IATA filtering.
// Pass cursor=0 to start from the beginning.
func (q *Queries) ListChannelMessages(ctx context.Context, arg ListChannelMessagesParams) ([]ListChannelMessagesRow, error) {
rows, err := q.db.Query(ctx, listChannelMessages,
arg.ChannelID,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListChannelMessagesRow{}
for rows.Next() {
var i ListChannelMessagesRow
if err := rows.Scan(
&i.ID,
&i.ChannelID,
&i.PacketHash,
&i.SenderName,
&i.SenderPubkey,
&i.Content,
&i.SentAt,
&i.PacketHashHex,
&i.ChannelHash,
&i.ScopeName,
&i.TransportCodesPresent,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listChannelMessagesByHash = `-- name: ListChannelMessagesByHash :many
SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, c.channel_hash, ts.name AS scope_name, p.transport_codes_present,
p.observation_count AS observation_count
FROM channel_messages cm
JOIN channels c ON c.id = cm.channel_id
JOIN packet_observations po ON po.packet_hash = cm.packet_hash
JOIN packets p ON p.packet_hash = cm.packet_hash
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE c.channel_hash = $1
AND ($2::timestamptz IS NULL OR cm.sent_at >= $2)
AND (COALESCE(cardinality($3::bpchar[]), 0) = 0 OR po.iata = ANY($3::bpchar[]))
AND ($4::text = '' OR ts.name = $4::text)
AND ($5::bigint = 0 OR cm.id < $5::bigint)
ORDER BY cm.id DESC
LIMIT $6
`
type ListChannelMessagesByHashParams struct {
ChannelHash []byte `json:"channel_hash"`
Column2 pgtype.Timestamptz `json:"column_2"`
Column3 []string `json:"column_3"`
Column4 string `json:"column_4"`
Column5 int64 `json:"column_5"`
Limit int32 `json:"limit"`
}
type ListChannelMessagesByHashRow struct {
ID int64 `json:"id"`
ChannelID int32 `json:"channel_id"`
PacketHash []byte `json:"packet_hash"`
SenderName *string `json:"sender_name"`
SenderPubkey []byte `json:"sender_pubkey"`
Content *string `json:"content"`
SentAt pgtype.Timestamptz `json:"sent_at"`
ChannelHash []byte `json:"channel_hash"`
ScopeName *string `json:"scope_name"`
TransportCodesPresent *bool `json:"transport_codes_present"`
ObservationCount int64 `json:"observation_count"`
}
// Returns messages for all channels matching a hash byte.
// May return messages from multiple channels if the hash collides across different keys.
// Pass empty string for iata or scope to skip those filters.
// Pass cursor=0 to start from the beginning.
func (q *Queries) ListChannelMessagesByHash(ctx context.Context, arg ListChannelMessagesByHashParams) ([]ListChannelMessagesByHashRow, error) {
rows, err := q.db.Query(ctx, listChannelMessagesByHash,
arg.ChannelHash,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListChannelMessagesByHashRow{}
for rows.Next() {
var i ListChannelMessagesByHashRow
if err := rows.Scan(
&i.ID,
&i.ChannelID,
&i.PacketHash,
&i.SenderName,
&i.SenderPubkey,
&i.Content,
&i.SentAt,
&i.ChannelHash,
&i.ScopeName,
&i.TransportCodesPresent,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listChannels = `-- name: ListChannels :many
SELECT c.id, c.channel_hash, c.key_fingerprint, c.name, c.hashtag, c.is_hashtag, c.is_public, c.key_known, c.first_seen, c.last_seen, c.message_count FROM channels c
WHERE ($1::bytea IS NULL OR c.channel_hash = $1)
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0
OR EXISTS (SELECT 1 FROM meshmapper_channel_members m
WHERE m.key_fingerprint = c.key_fingerprint AND m.iata::bpchar = ANY($2::bpchar[]))
OR EXISTS (SELECT 1 FROM channel_config_scopes s
WHERE s.key_fingerprint = c.key_fingerprint AND (s.region_slug IS NULL OR s.region_slug IN (
SELECT r.slug FROM regions r JOIN region_iatas ri ON ri.region_id = r.id
WHERE ri.iata = ANY($2::bpchar[])))))
AND ($3::boolean IS NULL OR COALESCE(c.key_known, false) = $3)
AND ($4::timestamptz IS NULL OR c.last_seen < $4)
ORDER BY c.last_seen DESC, c.id DESC
LIMIT $5
`
type ListChannelsParams struct {
ChannelHash []byte `json:"channel_hash"`
Iatas []string `json:"iatas"`
KeyKnown *bool `json:"key_known"`
CursorTs pgtype.Timestamptz `json:"cursor_ts"`
PageLimit int32 `json:"page_limit"`
}
// Channels ordered by last seen, optionally filtered by hash and/or IATAs.
// A channel belongs to an IATA when MeshMapper lists it there or config scopes it
// to a region containing it (or Beacon-wide). NULL hash / empty array / NULL key_known skip those filters.
// Pass cursor=0 to start from the beginning (cursor is last_seen epoch ms).
func (q *Queries) ListChannels(ctx context.Context, arg ListChannelsParams) ([]Channel, error) {
rows, err := q.db.Query(ctx, listChannels,
arg.ChannelHash,
arg.Iatas,
arg.KeyKnown,
arg.CursorTs,
arg.PageLimit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Channel{}
for rows.Next() {
var i Channel
if err := rows.Scan(
&i.ID,
&i.ChannelHash,
&i.KeyFingerprint,
&i.Name,
&i.Hashtag,
&i.IsHashtag,
&i.IsPublic,
&i.KeyKnown,
&i.FirstSeen,
&i.LastSeen,
&i.MessageCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listChannelsAfter = `-- name: ListChannelsAfter :many
SELECT c.id, c.channel_hash, c.key_fingerprint, c.name, c.hashtag, c.is_hashtag, c.is_public, c.key_known, c.first_seen, c.last_seen, c.message_count FROM channels c
WHERE (c.last_seen, c.id) < ($1::timestamptz, $2::integer)
AND ($3::bytea IS NULL OR c.channel_hash = $3)
AND (COALESCE(cardinality($4::bpchar[]), 0) = 0
OR EXISTS (SELECT 1 FROM meshmapper_channel_members m
WHERE m.key_fingerprint = c.key_fingerprint AND m.iata::bpchar = ANY($4::bpchar[]))
OR EXISTS (SELECT 1 FROM channel_config_scopes s
WHERE s.key_fingerprint = c.key_fingerprint AND (s.region_slug IS NULL OR s.region_slug IN (
SELECT r.slug FROM regions r JOIN region_iatas ri ON ri.region_id = r.id
WHERE ri.iata = ANY($4::bpchar[])))))
AND ($5::boolean IS NULL OR COALESCE(c.key_known, false) = $5)
ORDER BY c.last_seen DESC, c.id DESC
LIMIT $6
`
type ListChannelsAfterParams struct {
CursorTs pgtype.Timestamptz `json:"cursor_ts"`
CursorID int32 `json:"cursor_id"`
ChannelHash []byte `json:"channel_hash"`
Iatas []string `json:"iatas"`
KeyKnown *bool `json:"key_known"`
PageLimit int32 `json:"page_limit"`
}
// Keep the non-null tuple boundary separate from the legacy optional cursor so
// generic prepared plans can seek directly into the composite ordered index.
func (q *Queries) ListChannelsAfter(ctx context.Context, arg ListChannelsAfterParams) ([]Channel, error) {
rows, err := q.db.Query(ctx, listChannelsAfter,
arg.CursorTs,
arg.CursorID,
arg.ChannelHash,
arg.Iatas,
arg.KeyKnown,
arg.PageLimit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Channel{}
for rows.Next() {
var i Channel
if err := rows.Scan(
&i.ID,
&i.ChannelHash,
&i.KeyFingerprint,
&i.Name,
&i.Hashtag,
&i.IsHashtag,
&i.IsPublic,
&i.KeyKnown,
&i.FirstSeen,
&i.LastSeen,
&i.MessageCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listHeardIATAs = `-- name: ListHeardIATAs :many
SELECT DISTINCT last_iata::text AS iata FROM observers WHERE last_iata IS NOT NULL ORDER BY 1
`
func (q *Queries) ListHeardIATAs(ctx context.Context) ([]string, error) {
rows, err := q.db.Query(ctx, listHeardIATAs)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var iata string
if err := rows.Scan(&iata); err != nil {
return nil, err
}
items = append(items, iata)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listIATAs = `-- name: ListIATAs :many
SELECT iata, display_name, approx_lat, approx_lng, added_at, border FROM iata_codes ORDER BY iata
`
func (q *Queries) ListIATAs(ctx context.Context) ([]IataCode, error) {
rows, err := q.db.Query(ctx, listIATAs)
if err != nil {
return nil, err
}
defer rows.Close()
items := []IataCode{}
for rows.Next() {
var i IataCode
if err := rows.Scan(
&i.Iata,
&i.DisplayName,
&i.ApproxLat,
&i.ApproxLng,
&i.AddedAt,
&i.Border,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listKnownRoutes = `-- name: ListKnownRoutes :many
SELECT r.id, r.node_ids, r.hash_prefix, r.iata, r.hop_count, r.first_seen, r.last_seen, r.observation_count
FROM ((
SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count
FROM known_routes
WHERE $1 = ''
AND ($2 = 0 OR hop_count = $2)
AND ($3::timestamptz IS NULL
OR date_trunc('milliseconds', last_seen, 'UTC') < $3
OR ($5::bigint > 0 AND date_trunc('milliseconds', last_seen, 'UTC') = $3 AND id < $5))
ORDER BY date_trunc('milliseconds', last_seen, 'UTC') DESC, id DESC
LIMIT $4
)
UNION ALL
(
SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count
FROM known_routes
WHERE $1 <> '' AND iata::text = $1
AND iata >= $1::bpchar AND iata <= $1::bpchar
AND ($2 = 0 OR hop_count = $2)
AND ($3::timestamptz IS NULL
OR date_trunc('milliseconds', last_seen, 'UTC') < $3
OR ($5::bigint > 0 AND date_trunc('milliseconds', last_seen, 'UTC') = $3 AND id < $5))
ORDER BY iata, date_trunc('milliseconds', last_seen, 'UTC') DESC, id DESC
LIMIT $4
)) r
ORDER BY date_trunc('milliseconds', r.last_seen, 'UTC') DESC, r.id DESC
LIMIT $4
`
type ListKnownRoutesParams struct {
Column1 interface{} `json:"column_1"`
Column2 interface{} `json:"column_2"`
Column3 pgtype.Timestamptz `json:"column_3"`
Limit int32 `json:"limit"`
Column5 int64 `json:"column_5"`
}
type ListKnownRoutesRow struct {
ID int64 `json:"id"`
NodeIds []uuid.UUID `json:"node_ids"`
HashPrefix [][]byte `json:"hash_prefix"`
Iata string `json:"iata"`
HopCount int32 `json:"hop_count"`
FirstSeen pgtype.Timestamptz `json:"first_seen"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
ObservationCount int64 `json:"observation_count"`
}
// Only one branch runs. Keep the IATA range ordered by the composite index:
// generic plans can otherwise prefer scanning the global timestamp index.
// The text equality preserves exact input matching, including trailing spaces.
// Pages by (last_seen to the ms, id) so routes sharing the cursor's millisecond
// aren't skipped; cursor id 0 keeps the plain timestamp cursor.
func (q *Queries) ListKnownRoutes(ctx context.Context, arg ListKnownRoutesParams) ([]ListKnownRoutesRow, error) {
rows, err := q.db.Query(ctx, listKnownRoutes,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Limit,
arg.Column5,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListKnownRoutesRow{}
for rows.Next() {
var i ListKnownRoutesRow
if err := rows.Scan(
&i.ID,
&i.NodeIds,
&i.HashPrefix,
&i.Iata,
&i.HopCount,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMessagesAfterID = `-- name: ListMessagesAfterID :many
SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present,
p.observation_count AS observation_count
FROM channel_messages cm
JOIN channels c ON c.id = cm.channel_id
JOIN packet_observations po ON po.packet_hash = cm.packet_hash
JOIN packets p ON p.packet_hash = cm.packet_hash
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE cm.id > $1
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR po.iata = ANY($2::bpchar[]))
AND ($3::text = '' OR ts.name = $3::text)
ORDER BY cm.id ASC
LIMIT $4
`
type ListMessagesAfterIDParams struct {
ID int64 `json:"id"`
Column2 []string `json:"column_2"`
Column3 string `json:"column_3"`
Limit int32 `json:"limit"`
}
type ListMessagesAfterIDRow struct {
ID int64 `json:"id"`
ChannelID int32 `json:"channel_id"`
PacketHash []byte `json:"packet_hash"`
SenderName *string `json:"sender_name"`
SenderPubkey []byte `json:"sender_pubkey"`
Content *string `json:"content"`
SentAt pgtype.Timestamptz `json:"sent_at"`
PacketHashHex string `json:"packet_hash_hex"`
ChannelHash []byte `json:"channel_hash"`
ScopeName *string `json:"scope_name"`
TransportCodesPresent *bool `json:"transport_codes_present"`
ObservationCount int64 `json:"observation_count"`
}
// Returns messages after the given message ID, ordered oldest first.
// Used for WS reconnect backfill.
func (q *Queries) ListMessagesAfterID(ctx context.Context, arg ListMessagesAfterIDParams) ([]ListMessagesAfterIDRow, error) {
rows, err := q.db.Query(ctx, listMessagesAfterID,
arg.ID,
arg.Column2,
arg.Column3,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListMessagesAfterIDRow{}
for rows.Next() {
var i ListMessagesAfterIDRow
if err := rows.Scan(
&i.ID,
&i.ChannelID,
&i.PacketHash,
&i.SenderName,
&i.SenderPubkey,
&i.Content,
&i.SentAt,
&i.PacketHashHex,
&i.ChannelHash,
&i.ScopeName,
&i.TransportCodesPresent,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listNodeObservations = `-- name: ListNodeObservations :many
SELECT po.id, encode(po.packet_hash, 'hex') AS packet_hash_hex,
p.payload_type, po.iata, po.heard_at, po.rssi, po.snr, po.hop_count
FROM packet_observations po
JOIN packets p ON p.packet_hash = po.packet_hash
JOIN nodes n ON n.public_key = p.origin_pubkey
WHERE n.id = $1
AND ($2 = 0 OR po.id < $2)
ORDER BY po.id DESC
LIMIT $3
`
type ListNodeObservationsParams struct {
ID uuid.UUID `json:"id"`
Column2 interface{} `json:"column_2"`
Limit int32 `json:"limit"`
}
type ListNodeObservationsRow struct {
ID int64 `json:"id"`
PacketHashHex string `json:"packet_hash_hex"`
PayloadType int16 `json:"payload_type"`
Iata string `json:"iata"`
HeardAt pgtype.Timestamptz `json:"heard_at"`
Rssi *int16 `json:"rssi"`
Snr *float32 `json:"snr"`
HopCount int16 `json:"hop_count"`
}
func (q *Queries) ListNodeObservations(ctx context.Context, arg ListNodeObservationsParams) ([]ListNodeObservationsRow, error) {
rows, err := q.db.Query(ctx, listNodeObservations, arg.ID, arg.Column2, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListNodeObservationsRow{}
for rows.Next() {
var i ListNodeObservationsRow
if err := rows.Scan(
&i.ID,
&i.PacketHashHex,
&i.PayloadType,
&i.Iata,
&i.HeardAt,
&i.Rssi,
&i.Snr,
&i.HopCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listNodes = `-- name: ListNodes :many
WITH page AS (
SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_seen,
n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, ts.name AS default_scope_name
FROM nodes n
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
WHERE
($1 = 0 OR n.node_type = $1)
AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[])))
AND (
$3::text = 'any'
OR ($3::text = 'true' AND n.supports_multibyte_paths = TRUE)
OR ($3::text = 'false' AND n.supports_multibyte_paths = FALSE)
)
AND (
$4::text = 'any'
OR ($4::text = 'true' AND n.supports_multibyte_traces = TRUE)
OR ($4::text = 'false' AND n.supports_multibyte_traces = FALSE)
)
AND ($5::bytea IS NULL OR n.public_key = $5)
AND ($6 = '' OR n.name ILIKE '%' || $6 || '%')
AND ($7::timestamptz IS NULL OR n.last_seen < $7)
AND ($9::text = '' OR ts.name = $9::text)
AND ($11::text = '' OR encode(n.public_key, 'hex') ILIKE $11 || '%')
ORDER BY n.last_seen DESC
LIMIT $8
)
SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_seen,
n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, n.default_scope_name,
(SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC)
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas,
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT COUNT(DISTINCT nn.neighbor_id) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count,
(CASE WHEN $10::bool THEN
(SELECT COALESCE(array_agg(DISTINCT nn.neighbor_id), '{}'::uuid[]) FROM node_neighbors nn WHERE nn.node_id = n.id)
ELSE NULL END)::uuid[] AS neighbor_ids
FROM page n
ORDER BY n.last_seen DESC
`
type ListNodesParams struct {
Column1 interface{} `json:"column_1"`
Column2 []string `json:"column_2"`
Column3 string `json:"column_3"`
Column4 string `json:"column_4"`
Column5 []byte `json:"column_5"`
Column6 interface{} `json:"column_6"`
Column7 pgtype.Timestamptz `json:"column_7"`
Limit int32 `json:"limit"`
Column9 string `json:"column_9"`
Column10 bool `json:"column_10"`
Column11 string `json:"column_11"`
}
type ListNodesRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
NodeType int16 `json:"node_type"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
DefaultScopeName *string `json:"default_scope_name"`
Iatas []byte `json:"iatas"`
IsObserver bool `json:"is_observer"`
ObserverID uuid.UUID `json:"observer_id"`
KnownNeighborCount int64 `json:"known_neighbor_count"`
NeighborIds []uuid.UUID `json:"neighbor_ids"`
}
// Limit the filtered node page before enriching IATA membership and neighbours.
func (q *Queries) ListNodes(ctx context.Context, arg ListNodesParams) ([]ListNodesRow, error) {
rows, err := q.db.Query(ctx, listNodes,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Column6,
arg.Column7,
arg.Limit,
arg.Column9,
arg.Column10,
arg.Column11,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListNodesRow{}
for rows.Next() {
var i ListNodesRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.NodeType,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.LastSeen,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.DefaultScopeName,
&i.Iatas,
&i.IsObserver,
&i.ObserverID,
&i.KnownNeighborCount,
&i.NeighborIds,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listObservationsForPacket = `-- name: ListObservationsForPacket :many
SELECT po.id, po.packet_hash, po.observer_id, po.iata, po.heard_at, po.path_length_byte, po.hash_size, po.hop_count, po.path_bytes, po.rssi, po.snr, po.propagation_time_ms, po.radio_freq_mhz, po.spread_factor, po.bandwidth_khz, po.coding_rate, po.source_broker, po.payload_type, po.airtime_ms, o.display_name AS observer_name
FROM packet_observations po
LEFT JOIN observers o ON o.id = po.observer_id
WHERE po.packet_hash = $1
ORDER BY po.heard_at ASC
`
type ListObservationsForPacketRow struct {
ID int64 `json:"id"`
PacketHash []byte `json:"packet_hash"`
ObserverID uuid.UUID `json:"observer_id"`
Iata string `json:"iata"`
HeardAt pgtype.Timestamptz `json:"heard_at"`
PathLengthByte int16 `json:"path_length_byte"`
HashSize int16 `json:"hash_size"`
HopCount int16 `json:"hop_count"`
PathBytes []byte `json:"path_bytes"`
Rssi *int16 `json:"rssi"`
Snr *float32 `json:"snr"`
PropagationTimeMs *int32 `json:"propagation_time_ms"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
SpreadFactor *int16 `json:"spread_factor"`
BandwidthKhz *float32 `json:"bandwidth_khz"`
CodingRate *int16 `json:"coding_rate"`
SourceBroker *string `json:"source_broker"`
PayloadType *int16 `json:"payload_type"`
AirtimeMs *float32 `json:"airtime_ms"`
ObserverName *string `json:"observer_name"`
}
func (q *Queries) ListObservationsForPacket(ctx context.Context, packetHash []byte) ([]ListObservationsForPacketRow, error) {
rows, err := q.db.Query(ctx, listObservationsForPacket, packetHash)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListObservationsForPacketRow{}
for rows.Next() {
var i ListObservationsForPacketRow
if err := rows.Scan(
&i.ID,
&i.PacketHash,
&i.ObserverID,
&i.Iata,
&i.HeardAt,
&i.PathLengthByte,
&i.HashSize,
&i.HopCount,
&i.PathBytes,
&i.Rssi,
&i.Snr,
&i.PropagationTimeMs,
&i.RadioFreqMhz,
&i.SpreadFactor,
&i.BandwidthKhz,
&i.CodingRate,
&i.SourceBroker,
&i.PayloadType,
&i.AirtimeMs,
&i.ObserverName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listObserverAdverts = `-- name: ListObserverAdverts :many
SELECT
po.id,
encode(po.packet_hash, 'hex') AS packet_hash_hex,
p.payload_type,
po.iata,
po.heard_at,
po.rssi,
po.snr,
po.hop_count,
n.name AS node_name,
COALESCE(encode(p.origin_pubkey, 'hex'), '')::text AS node_public_key
FROM packet_observations po
JOIN packets p ON p.packet_hash = po.packet_hash
LEFT JOIN nodes n ON n.public_key = p.origin_pubkey
WHERE po.observer_id = $1
AND p.payload_type = 4
AND ($2 = 0 OR po.id > $2)
ORDER BY po.id ASC
LIMIT $3
`
type ListObserverAdvertsParams struct {
ObserverID uuid.UUID `json:"observer_id"`
Column2 interface{} `json:"column_2"`
Limit int32 `json:"limit"`
}
type ListObserverAdvertsRow struct {
ID int64 `json:"id"`
PacketHashHex string `json:"packet_hash_hex"`
PayloadType int16 `json:"payload_type"`
Iata string `json:"iata"`
HeardAt pgtype.Timestamptz `json:"heard_at"`
Rssi *int16 `json:"rssi"`
Snr *float32 `json:"snr"`
HopCount int16 `json:"hop_count"`
NodeName *string `json:"node_name"`
NodePublicKey string `json:"node_public_key"`
}
// Returns advert packets (payload_type=4) heard by a specific observer.
// Pass cursor=0 to start from the beginning, or the last seen id for pagination.
// Keep missing-origin adverts; the generated key field expects a string, not NULL.
func (q *Queries) ListObserverAdverts(ctx context.Context, arg ListObserverAdvertsParams) ([]ListObserverAdvertsRow, error) {
rows, err := q.db.Query(ctx, listObserverAdverts, arg.ObserverID, arg.Column2, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListObserverAdvertsRow{}
for rows.Next() {
var i ListObserverAdvertsRow
if err := rows.Scan(
&i.ID,
&i.PacketHashHex,
&i.PayloadType,
&i.Iata,
&i.HeardAt,
&i.Rssi,
&i.Snr,
&i.HopCount,
&i.NodeName,
&i.NodePublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listObservers = `-- name: ListObservers :many
SELECT
o.id,
o.display_name,
o.observer_type,
o.last_status_at,
o.radio_freq_mhz,
o.radio_sf,
o.radio_bw_khz,
array_remove(array_agg(DISTINCT ts.name ORDER BY ts.name), NULL)::text[] AS scopes,
COALESCE(CASE
WHEN GREATEST(COALESCE(o.last_status_at, o.last_seen), o.last_seen) > NOW() - INTERVAL '5 minutes' THEN 'online'
ELSE 'offline'
END, 'offline')::text AS status,
COALESCE(o.last_iata, '')::text AS iata
FROM observers o
LEFT JOIN observer_brokers ob ON ob.observer_id = o.id
LEFT JOIN observer_scopes os ON os.observer_id = o.id
LEFT JOIN transport_scopes ts ON ts.id = os.scope_id
WHERE
(COALESCE(cardinality($1::bpchar[]), 0) = 0 OR o.last_iata = ANY($1::bpchar[]))
AND ($2 = '' OR o.observer_type = $2)
AND ($3 = '' OR ob.broker_name = $3)
AND ($4 = '' OR CASE
WHEN GREATEST(COALESCE(o.last_status_at, o.last_seen), o.last_seen) > NOW() - INTERVAL '5 minutes' THEN 'online'
ELSE 'offline'
END = $4)
AND ($5 = '' OR o.display_name ILIKE '%' || $5 || '%')
AND ($6::timestamptz IS NULL OR o.last_seen < $6)
AND ($8::text = '' OR EXISTS (
SELECT 1 FROM observer_scopes os2
JOIN transport_scopes ts2 ON ts2.id = os2.scope_id
WHERE os2.observer_id = o.id AND ts2.name = $8::text
))
GROUP BY o.id
ORDER BY o.last_seen DESC
LIMIT $7
`
type ListObserversParams struct {
Column1 []string `json:"column_1"`
Column2 interface{} `json:"column_2"`
Column3 interface{} `json:"column_3"`
Column4 interface{} `json:"column_4"`
Column5 interface{} `json:"column_5"`
Column6 pgtype.Timestamptz `json:"column_6"`
Limit int32 `json:"limit"`
Column8 string `json:"column_8"`
}
type ListObserversRow struct {
ID uuid.UUID `json:"id"`
DisplayName *string `json:"display_name"`
ObserverType *string `json:"observer_type"`
LastStatusAt pgtype.Timestamptz `json:"last_status_at"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
Scopes []string `json:"scopes"`
Status string `json:"status"`
Iata string `json:"iata"`
}
// Pass cursor=0 to start from the beginning, or the last seen observer's rownum for pagination.
// Note: observers use UUID PKs so we order by last_seen and use a keyset on last_seen+id.
func (q *Queries) ListObservers(ctx context.Context, arg ListObserversParams) ([]ListObserversRow, error) {
rows, err := q.db.Query(ctx, listObservers,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Column6,
arg.Limit,
arg.Column8,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListObserversRow{}
for rows.Next() {
var i ListObserversRow
if err := rows.Scan(
&i.ID,
&i.DisplayName,
&i.ObserverType,
&i.LastStatusAt,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.Scopes,
&i.Status,
&i.Iata,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listPackets = `-- name: ListPackets :many
SELECT
p.packet_hash,
p.payload_type,
COALESCE(CASE
WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string'
THEN p.parsed_payload #>> '{appData,name}'
WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK'
AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string'
AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$'
THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum')
WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING')
AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string'
AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$'
THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag')
END, '')::text AS summary,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.scope_id,
ts.name AS scope_name,
p.observation_count AS observation_count,
-- sqlc loses LATERAL nullability; these scalar defaults are ignored when observer_id is NULL.
po.observer_id AS latest_observer_id,
o.display_name AS latest_observer_name,
COALESCE(po.iata, ''::bpchar) AS latest_observer_iata,
COALESCE(po.path_length_byte, 0::smallint) AS latest_observer_path_length_byte,
COALESCE(po.hash_size, 0::smallint) AS latest_observer_hash_size,
COALESCE(po.hop_count, 0::smallint) AS latest_observer_hop_count,
po.path_bytes AS latest_observer_path_bytes,
p.raw_payload,
p.origin_pubkey
FROM packets p
LEFT JOIN LATERAL (
SELECT observer_id, iata, path_length_byte, hash_size, hop_count, path_bytes
FROM packet_observations
WHERE packet_hash = p.packet_hash
ORDER BY heard_at DESC
LIMIT 1
) po ON true
LEFT JOIN observers o ON o.id = po.observer_id
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE
(COALESCE(cardinality($1::smallint[]), 0) = 0 OR p.payload_type = ANY($1::smallint[]))
AND (COALESCE(cardinality($2::smallint[]), 0) = 0 OR p.route_type = ANY($2::smallint[]))
AND ($3::timestamptz IS NULL OR p.first_heard_at >= $3)
AND ($4::timestamptz IS NULL OR p.first_heard_at <= $4)
AND ($5::timestamptz IS NULL OR p.last_heard_at < $5)
AND (COALESCE(cardinality($7::text[]), 0) = 0 OR ts.name = ANY($7::text[]))
ORDER BY p.last_heard_at DESC
LIMIT $6
`
type ListPacketsParams struct {
Column1 []int16 `json:"column_1"`
Column2 []int16 `json:"column_2"`
Column3 pgtype.Timestamptz `json:"column_3"`
Column4 pgtype.Timestamptz `json:"column_4"`
Column5 pgtype.Timestamptz `json:"column_5"`
Limit int32 `json:"limit"`
Column7 []string `json:"column_7"`
}
type ListPacketsRow struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
Summary string `json:"summary"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
ObservationCount int64 `json:"observation_count"`
LatestObserverID uuid.UUID `json:"latest_observer_id"`
LatestObserverName *string `json:"latest_observer_name"`
LatestObserverIata string `json:"latest_observer_iata"`
LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"`
LatestObserverHashSize int16 `json:"latest_observer_hash_size"`
LatestObserverHopCount int16 `json:"latest_observer_hop_count"`
LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"`
RawPayload []byte `json:"raw_payload"`
OriginPubkey []byte `json:"origin_pubkey"`
}
// Returns packets with the latest observation rolled in for display.
// Pass cursor=0 to start from the beginning. IATA-filtered requests are
// served by ListPacketsByIATAs instead.
func (q *Queries) ListPackets(ctx context.Context, arg ListPacketsParams) ([]ListPacketsRow, error) {
rows, err := q.db.Query(ctx, listPackets,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Limit,
arg.Column7,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListPacketsRow{}
for rows.Next() {
var i ListPacketsRow
if err := rows.Scan(
&i.PacketHash,
&i.PayloadType,
&i.Summary,
&i.RouteType,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.ScopeID,
&i.ScopeName,
&i.ObservationCount,
&i.LatestObserverID,
&i.LatestObserverName,
&i.LatestObserverIata,
&i.LatestObserverPathLengthByte,
&i.LatestObserverHashSize,
&i.LatestObserverHopCount,
&i.LatestObserverPathBytes,
&i.RawPayload,
&i.OriginPubkey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listPacketsAfterID = `-- name: ListPacketsAfterID :many
SELECT
p.packet_hash,
p.payload_type,
COALESCE(CASE
WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string'
THEN p.parsed_payload #>> '{appData,name}'
WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK'
AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string'
AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$'
THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum')
WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING')
AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string'
AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$'
THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag')
END, '')::text AS summary,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.observation_count AS observation_count,
po.observer_id AS latest_observer_id,
o.display_name AS latest_observer_name,
po.iata AS latest_observer_iata,
po.path_length_byte AS latest_observer_path_length_byte,
po.hash_size AS latest_observer_hash_size,
po.hop_count AS latest_observer_hop_count,
po.path_bytes AS latest_observer_path_bytes,
p.raw_payload,
p.origin_pubkey,
ts.name AS scope_name
FROM packets p
JOIN packet_observations po ON po.packet_hash = p.packet_hash
LEFT JOIN observers o ON o.id = po.observer_id
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
WHERE po.id > $1
AND ($2::smallint = -1 OR p.payload_type = $2::smallint)
AND ($3::smallint = -1 OR p.route_type = $3::smallint)
AND (COALESCE(cardinality($4::bpchar[]), 0) = 0 OR po.iata = ANY($4::bpchar[]))
AND ($5::text = '' OR ts.name = $5::text)
ORDER BY po.id ASC
LIMIT $6
`
type ListPacketsAfterIDParams struct {
ID int64 `json:"id"`
Column2 int16 `json:"column_2"`
Column3 int16 `json:"column_3"`
Column4 []string `json:"column_4"`
Column5 string `json:"column_5"`
Limit int32 `json:"limit"`
}
type ListPacketsAfterIDRow struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
Summary string `json:"summary"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ObservationCount int64 `json:"observation_count"`
LatestObserverID uuid.UUID `json:"latest_observer_id"`
LatestObserverName *string `json:"latest_observer_name"`
LatestObserverIata string `json:"latest_observer_iata"`
LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"`
LatestObserverHashSize int16 `json:"latest_observer_hash_size"`
LatestObserverHopCount int16 `json:"latest_observer_hop_count"`
LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"`
RawPayload []byte `json:"raw_payload"`
OriginPubkey []byte `json:"origin_pubkey"`
ScopeName *string `json:"scope_name"`
}
// Returns packets with observations after the given observation ID, ordered oldest first.
// Used for WS reconnect backfill. Pass afterObservationId=0 to start from the beginning.
func (q *Queries) ListPacketsAfterID(ctx context.Context, arg ListPacketsAfterIDParams) ([]ListPacketsAfterIDRow, error) {
rows, err := q.db.Query(ctx, listPacketsAfterID,
arg.ID,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListPacketsAfterIDRow{}
for rows.Next() {
var i ListPacketsAfterIDRow
if err := rows.Scan(
&i.PacketHash,
&i.PayloadType,
&i.Summary,
&i.RouteType,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.ObservationCount,
&i.LatestObserverID,
&i.LatestObserverName,
&i.LatestObserverIata,
&i.LatestObserverPathLengthByte,
&i.LatestObserverHashSize,
&i.LatestObserverHopCount,
&i.LatestObserverPathBytes,
&i.RawPayload,
&i.OriginPubkey,
&i.ScopeName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listPacketsByIATAs = `-- name: ListPacketsByIATAs :many
WITH scanned AS (
SELECT req.iata AS req_iata, hits.packet_hash, hits.heard_at
FROM unnest($1::bpchar[]) AS req(iata)
CROSS JOIN LATERAL (
SELECT po3.packet_hash, po3.heard_at
FROM packet_observations po3
JOIN packets p2 ON p2.packet_hash = po3.packet_hash
WHERE po3.iata = req.iata
AND po3.heard_at < COALESCE($2::timestamptz, 'infinity'::timestamptz)
AND (COALESCE(cardinality($3::smallint[]), 0) = 0 OR p2.payload_type = ANY($3::smallint[]))
AND (COALESCE(cardinality($4::smallint[]), 0) = 0 OR p2.route_type = ANY($4::smallint[]))
AND ($5::timestamptz IS NULL OR p2.first_heard_at >= $5)
AND ($6::timestamptz IS NULL OR p2.first_heard_at <= $6)
AND (COALESCE(cardinality($7::text[]), 0) = 0 OR EXISTS (
SELECT 1 FROM transport_scopes ts2
WHERE ts2.id = p2.scope_id AND ts2.name = ANY($7::text[])))
ORDER BY po3.heard_at DESC
LIMIT $8
) hits
),
saturation AS (
SELECT
COUNT(*) > 0 AS scan_saturated,
MAX(floor_ts)::timestamptz AS scan_floor
FROM (
SELECT MIN(heard_at) AS floor_ts
FROM scanned
GROUP BY req_iata
HAVING COUNT(*) >= $8
) filled
),
page AS (
SELECT scanned.packet_hash, MAX(scanned.heard_at)::timestamptz AS site_heard_at
FROM scanned
GROUP BY scanned.packet_hash
HAVING ($2::timestamptz IS NULL OR NOT EXISTS (
SELECT 1 FROM packet_observations px
WHERE px.packet_hash = scanned.packet_hash
AND px.iata = ANY($1::bpchar[])
AND px.heard_at >= $2))
ORDER BY site_heard_at DESC
LIMIT $9
)
SELECT
p.packet_hash,
p.payload_type,
COALESCE(CASE
WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string'
THEN p.parsed_payload #>> '{appData,name}'
WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK'
AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string'
AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$'
THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum')
WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING')
AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string'
AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$'
THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag')
END, '')::text AS summary,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.scope_id,
ts.name AS scope_name,
sh.site_heard_at,
sat.scan_saturated,
sat.scan_floor,
p.observation_count AS observation_count,
-- sqlc loses LATERAL nullability; these scalar defaults are ignored when observer_id is NULL.
po.observer_id AS latest_observer_id,
o.display_name AS latest_observer_name,
COALESCE(po.iata, ''::bpchar) AS latest_observer_iata,
COALESCE(po.path_length_byte, 0::smallint) AS latest_observer_path_length_byte,
COALESCE(po.hash_size, 0::smallint) AS latest_observer_hash_size,
COALESCE(po.hop_count, 0::smallint) AS latest_observer_hop_count,
po.path_bytes AS latest_observer_path_bytes,
p.raw_payload,
p.origin_pubkey
FROM page sh
CROSS JOIN saturation sat
JOIN packets p ON p.packet_hash = sh.packet_hash
LEFT JOIN LATERAL (
SELECT observer_id, iata, path_length_byte, hash_size, hop_count, path_bytes
FROM packet_observations
WHERE packet_hash = p.packet_hash
ORDER BY heard_at DESC
LIMIT 1
) po ON true
LEFT JOIN observers o ON o.id = po.observer_id
LEFT JOIN transport_scopes ts ON ts.id = p.scope_id
ORDER BY sh.site_heard_at DESC
`
type ListPacketsByIATAsParams struct {
Iatas []string `json:"iatas"`
CursorTs pgtype.Timestamptz `json:"cursor_ts"`
PayloadTypes []int16 `json:"payload_types"`
RouteTypes []int16 `json:"route_types"`
SinceTs pgtype.Timestamptz `json:"since_ts"`
UntilTs pgtype.Timestamptz `json:"until_ts"`
ScopeNames []string `json:"scope_names"`
ScanDepth int32 `json:"scan_depth"`
PageLimit int32 `json:"page_limit"`
}
type ListPacketsByIATAsRow struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
Summary string `json:"summary"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
SiteHeardAt pgtype.Timestamptz `json:"site_heard_at"`
ScanSaturated bool `json:"scan_saturated"`
ScanFloor pgtype.Timestamptz `json:"scan_floor"`
ObservationCount int64 `json:"observation_count"`
LatestObserverID uuid.UUID `json:"latest_observer_id"`
LatestObserverName *string `json:"latest_observer_name"`
LatestObserverIata string `json:"latest_observer_iata"`
LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"`
LatestObserverHashSize int16 `json:"latest_observer_hash_size"`
LatestObserverHopCount int16 `json:"latest_observer_hop_count"`
LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"`
RawPayload []byte `json:"raw_payload"`
OriginPubkey []byte `json:"origin_pubkey"`
}
// IATA-filtered packet list, driven from idx_observations_iata_heard.
// Walking packets newest-first and probing for the site probes ~589k packets
// to fill a page for a quiet site; walking the site's own observation log is
// proportional to the page size instead. Results are ordered by when the
// requested sites heard the packet (site-local recency) and the cursor
// follows that ordering. scan_depth caps how deep each site's observation
// log is walked. A packet repeats once per observer that heard it, so a
// page can collapse to fewer distinct packets than were asked for without
// the site being exhausted. scan_saturated reports whether any site hit
// that cap and scan_floor the oldest heard_at they all cover, so a short
// page can keep paging instead of reading as the end of the data.
// A site that filled scan_depth still has unread history below its floor.
// The newest such floor is the point above which every site is covered.
func (q *Queries) ListPacketsByIATAs(ctx context.Context, arg ListPacketsByIATAsParams) ([]ListPacketsByIATAsRow, error) {
rows, err := q.db.Query(ctx, listPacketsByIATAs,
arg.Iatas,
arg.CursorTs,
arg.PayloadTypes,
arg.RouteTypes,
arg.SinceTs,
arg.UntilTs,
arg.ScopeNames,
arg.ScanDepth,
arg.PageLimit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListPacketsByIATAsRow{}
for rows.Next() {
var i ListPacketsByIATAsRow
if err := rows.Scan(
&i.PacketHash,
&i.PayloadType,
&i.Summary,
&i.RouteType,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.ScopeID,
&i.ScopeName,
&i.SiteHeardAt,
&i.ScanSaturated,
&i.ScanFloor,
&i.ObservationCount,
&i.LatestObserverID,
&i.LatestObserverName,
&i.LatestObserverIata,
&i.LatestObserverPathLengthByte,
&i.LatestObserverHashSize,
&i.LatestObserverHopCount,
&i.LatestObserverPathBytes,
&i.RawPayload,
&i.OriginPubkey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listRegionState = `-- name: ListRegionState :many
SELECT r.slug, r.name, COALESCE(r.display_order, 0)::int AS display_order, r.imported, r.center_lat, r.center_lng,
COALESCE(array_agg(ri.iata::text ORDER BY ri.iata) FILTER (WHERE ri.iata IS NOT NULL), '{}')::text[] AS iatas
FROM regions r
LEFT JOIN region_iatas ri ON ri.region_id = r.id
GROUP BY r.id
ORDER BY r.slug
`
type ListRegionStateRow struct {
Slug string `json:"slug"`
Name string `json:"name"`
DisplayOrder int32 `json:"display_order"`
Imported bool `json:"imported"`
CenterLat *float64 `json:"center_lat"`
CenterLng *float64 `json:"center_lng"`
Iatas []string `json:"iatas"`
}
// Every region with its members, for reconciling imported MeshMapper groups.
func (q *Queries) ListRegionState(ctx context.Context) ([]ListRegionStateRow, error) {
rows, err := q.db.Query(ctx, listRegionState)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListRegionStateRow{}
for rows.Next() {
var i ListRegionStateRow
if err := rows.Scan(
&i.Slug,
&i.Name,
&i.DisplayOrder,
&i.Imported,
&i.CenterLat,
&i.CenterLng,
&i.Iatas,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listRegions = `-- name: ListRegions :many
SELECT id, slug, name
FROM regions
ORDER BY display_order, name
`
type ListRegionsRow struct {
ID int32 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
}
// ============================================================
// REGIONS
// ============================================================
func (q *Queries) ListRegions(ctx context.Context) ([]ListRegionsRow, error) {
rows, err := q.db.Query(ctx, listRegions)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListRegionsRow{}
for rows.Next() {
var i ListRegionsRow
if err := rows.Scan(&i.ID, &i.Slug, &i.Name); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopeCatalogues = `-- name: ListScopeCatalogues :many
SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_scope_catalogues ORDER BY iata, attempted_at
`
func (q *Queries) ListScopeCatalogues(ctx context.Context) ([]MeshmapperScopeCatalogue, error) {
rows, err := q.db.Query(ctx, listScopeCatalogues)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MeshmapperScopeCatalogue{}
for rows.Next() {
var i MeshmapperScopeCatalogue
if err := rows.Scan(
&i.Iata,
&i.Url,
&i.Payload,
&i.Etag,
&i.CheckedAt,
&i.AttemptedAt,
&i.NextAttempt,
&i.LastError,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTraceTags = `-- name: ListTraceTags :many
SELECT
encode(t.trace_tag, 'hex') AS trace_tag,
t.first_heard_at,
t.last_heard_at,
t.packet_count,
(SELECT COUNT(*)
FROM trace_iatas ti
WHERE ti.trace_tag = t.trace_tag
AND (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR ti.iata = ANY($1::bpchar[]))) AS iata_count,
COALESCE(t.trace_type, '')::text AS trace_type,
t.best_payload
FROM trace_tags t
WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR EXISTS (
SELECT 1 FROM trace_iatas ti WHERE ti.trace_tag = t.trace_tag AND ti.iata = ANY($1::bpchar[])))
AND ($2::text = '' OR t.scope_id = (SELECT id FROM transport_scopes WHERE name = $2))
AND ($3::timestamptz IS NULL OR t.first_heard_at >= $3)
AND ($4::timestamptz IS NULL OR t.first_heard_at <= $4)
-- Keyset on the millisecond clients see, tie-broken by tag; matches idx_trace_tags_keyset.
AND ($5::timestamptz IS NULL
OR date_trunc('milliseconds', t.last_heard_at, 'UTC') < $5
OR ($8::bytea IS NOT NULL AND date_trunc('milliseconds', t.last_heard_at, 'UTC') = $5 AND t.trace_tag < $8))
AND ($7::text = '' OR t.trace_type = $7)
ORDER BY date_trunc('milliseconds', t.last_heard_at, 'UTC') DESC, t.trace_tag DESC
LIMIT $6
`
type ListTraceTagsParams struct {
Column1 []string `json:"column_1"`
Column2 string `json:"column_2"`
Column3 pgtype.Timestamptz `json:"column_3"`
Column4 pgtype.Timestamptz `json:"column_4"`
Column5 pgtype.Timestamptz `json:"column_5"`
Limit int32 `json:"limit"`
Column7 string `json:"column_7"`
Column8 []byte `json:"column_8"`
}
type ListTraceTagsRow struct {
TraceTag string `json:"trace_tag"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
PacketCount int64 `json:"packet_count"`
IataCount int64 `json:"iata_count"`
TraceType string `json:"trace_type"`
BestPayload []byte `json:"best_payload"`
}
// ============================================================
// TRACES
// ============================================================
// Tags, most recent first. Filters match the tag summary (see RecordTrace).
func (q *Queries) ListTraceTags(ctx context.Context, arg ListTraceTagsParams) ([]ListTraceTagsRow, error) {
rows, err := q.db.Query(ctx, listTraceTags,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
arg.Limit,
arg.Column7,
arg.Column8,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListTraceTagsRow{}
for rows.Next() {
var i ListTraceTagsRow
if err := rows.Scan(
&i.TraceTag,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.PacketCount,
&i.IataCount,
&i.TraceType,
&i.BestPayload,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listUndecryptedGroupTextPackets = `-- name: ListUndecryptedGroupTextPackets :many
SELECT packet_hash, raw_payload FROM packets
WHERE payload_type = 5 AND decrypted IS NOT TRUE
`
type ListUndecryptedGroupTextPacketsRow struct {
PacketHash []byte `json:"packet_hash"`
RawPayload []byte `json:"raw_payload"`
}
// Returns GRP_TXT packets (payload_type=5) never successfully decrypted. Used at boot to
// retry decryption against the current keystore for packets whose channel key was only added
// to the config after they'd already been ingested -- see
// internal/ingest.BackfillChannelMessages.
func (q *Queries) ListUndecryptedGroupTextPackets(ctx context.Context) ([]ListUndecryptedGroupTextPacketsRow, error) {
rows, err := q.db.Query(ctx, listUndecryptedGroupTextPackets)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListUndecryptedGroupTextPacketsRow{}
for rows.Next() {
var i ListUndecryptedGroupTextPacketsRow
if err := rows.Scan(&i.PacketHash, &i.RawPayload); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listUndecryptedGroupTextPacketsByHash = `-- name: ListUndecryptedGroupTextPacketsByHash :many
SELECT packet_hash, raw_payload FROM packets
WHERE payload_type = 5 AND decrypted IS NOT TRUE AND channel_hash = ANY($1::bytea[])
`
type ListUndecryptedGroupTextPacketsByHashRow struct {
PacketHash []byte `json:"packet_hash"`
RawPayload []byte `json:"raw_payload"`
}
// Like ListUndecryptedGroupTextPackets, limited to channels that just gained a key.
func (q *Queries) ListUndecryptedGroupTextPacketsByHash(ctx context.Context, hashes [][]byte) ([]ListUndecryptedGroupTextPacketsByHashRow, error) {
rows, err := q.db.Query(ctx, listUndecryptedGroupTextPacketsByHash, hashes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListUndecryptedGroupTextPacketsByHashRow{}
for rows.Next() {
var i ListUndecryptedGroupTextPacketsByHashRow
if err := rows.Scan(&i.PacketHash, &i.RawPayload); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listZoneBoundaries = `-- name: ListZoneBoundaries :many
SELECT iata, url, feature, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_zone_boundaries ORDER BY iata
`
func (q *Queries) ListZoneBoundaries(ctx context.Context) ([]MeshmapperZoneBoundary, error) {
rows, err := q.db.Query(ctx, listZoneBoundaries)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MeshmapperZoneBoundary{}
for rows.Next() {
var i MeshmapperZoneBoundary
if err := rows.Scan(
&i.Iata,
&i.Url,
&i.Feature,
&i.Etag,
&i.CheckedAt,
&i.AttemptedAt,
&i.NextAttempt,
&i.LastError,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listZoneLists = `-- name: ListZoneLists :many
SELECT country, payload, etag, fetched_at, attempted_at, next_attempt, last_error FROM meshmapper_zone_lists ORDER BY country
`
func (q *Queries) ListZoneLists(ctx context.Context) ([]MeshmapperZoneList, error) {
rows, err := q.db.Query(ctx, listZoneLists)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MeshmapperZoneList{}
for rows.Next() {
var i MeshmapperZoneList
if err := rows.Scan(
&i.Country,
&i.Payload,
&i.Etag,
&i.FetchedAt,
&i.AttemptedAt,
&i.NextAttempt,
&i.LastError,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const pruneImportedRegions = `-- name: PruneImportedRegions :many
DELETE FROM regions WHERE imported AND NOT (slug = ANY($1::text[])) RETURNING slug
`
func (q *Queries) PruneImportedRegions(ctx context.Context, keep []string) ([]string, error) {
rows, err := q.db.Query(ctx, pruneImportedRegions, keep)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var slug string
if err := rows.Scan(&slug); err != nil {
return nil, err
}
items = append(items, slug)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const pruneZoneBoundaries = `-- name: PruneZoneBoundaries :many
DELETE FROM meshmapper_zone_boundaries WHERE NOT (iata = ANY($1::text[])) RETURNING iata
`
// Drops imports for IATAs no longer configured, so their manual border returns.
func (q *Queries) PruneZoneBoundaries(ctx context.Context, keep []string) ([]string, error) {
rows, err := q.db.Query(ctx, pruneZoneBoundaries, keep)
if err != nil {
return nil, err
}
defer rows.Close()
items := []string{}
for rows.Next() {
var iata string
if err := rows.Scan(&iata); err != nil {
return nil, err
}
items = append(items, iata)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const reconfirmNeighbors = `-- name: ReconfirmNeighbors :exec
DELETE FROM node_neighbors nn
WHERE NOT EXISTS (
SELECT 1 FROM node_short_ids ns
WHERE ns.node_id = nn.neighbor_id
AND ns.iata = nn.iata
)
OR (
SELECT COUNT(*) FROM node_short_ids ns
WHERE ns.iata = nn.iata
AND ns.prefix_4 = (
SELECT prefix_4 FROM node_short_ids
WHERE node_id = nn.neighbor_id
AND iata = nn.iata
)
) > 1
`
// Delete node_neighbors where the neighbor has departed from node_short_ids
// for that IATA, or where its prefix_4 is now ambiguous.
func (q *Queries) ReconfirmNeighbors(ctx context.Context) error {
_, err := q.db.Exec(ctx, reconfirmNeighbors)
return err
}
const reconfirmRoutes = `-- name: ReconfirmRoutes :one
WITH batch AS MATERIALIZED (
SELECT r.iata, r.path_key, r.node_ids, r.hash_prefix
FROM known_routes r
WHERE r.last_reconfirmed_at < $1
ORDER BY r.last_reconfirmed_at
LIMIT $2
FOR UPDATE OF r SKIP LOCKED
),
amb AS (
SELECT a.iata::char(3) AS iata, a.len, a.p
FROM ROWS FROM (unnest($3::text[]), unnest($4::int[]), unnest($5::bytea[])) AS a(iata, len, p)
),
dead AS (
SELECT b.iata, b.path_key
FROM batch b
WHERE EXISTS (
SELECT 1
FROM unnest(b.node_ids) AS hop_node_id
WHERE NOT EXISTS (
SELECT 1 FROM node_short_ids ns
WHERE ns.node_id = hop_node_id
AND ns.iata = b.iata
)
)
UNION
SELECT DISTINCT b.iata, b.path_key
FROM batch b
CROSS JOIN LATERAL unnest(b.hash_prefix) AS hp
JOIN amb a ON a.iata = b.iata AND a.len = length(hp) AND a.p = hp
),
deleted AS (
DELETE FROM known_routes kr
USING dead d
WHERE kr.iata = d.iata AND kr.path_key = d.path_key
),
updated AS (
UPDATE known_routes kr
SET last_reconfirmed_at = GREATEST(NOW(), $1::timestamptz)
FROM batch b
WHERE kr.iata = b.iata AND kr.path_key = b.path_key
AND NOT EXISTS (
SELECT 1 FROM dead d
WHERE d.iata = b.iata AND d.path_key = b.path_key
)
)
SELECT count(*) FROM batch
`
type ReconfirmRoutesParams struct {
Before pgtype.Timestamptz `json:"before"`
BatchSize int32 `json:"batch_size"`
AmbIata []string `json:"amb_iata"`
AmbLen []int32 `json:"amb_len"`
AmbPrefix [][]byte `json:"amb_prefix"`
}
// Checks one batch of least-recently-reconfirmed routes: deletes those with a departed
// hop node or a hop prefix now matching >1 node in that IATA (length-aware:
// 1/2/3/4-byte hop prefixes check prefix_1/2/3/4; ambiguity set supplied by AmbiguousPrefixes),
// and stamps the survivors.
func (q *Queries) ReconfirmRoutes(ctx context.Context, arg ReconfirmRoutesParams) (int64, error) {
row := q.db.QueryRow(ctx, reconfirmRoutes,
arg.Before,
arg.BatchSize,
arg.AmbIata,
arg.AmbLen,
arg.AmbPrefix,
)
var count int64
err := row.Scan(&count)
return count, err
}
const recordTrace = `-- name: RecordTrace :exec
WITH iata AS (
INSERT INTO trace_iatas (trace_tag, iata, last_heard)
VALUES ($1, $3, $2)
ON CONFLICT (trace_tag, iata) DO UPDATE SET
last_heard = EXCLUDED.last_heard
WHERE EXCLUDED.last_heard > trace_iatas.last_heard + INTERVAL '1 hour'
)
INSERT INTO trace_tags (trace_tag, first_heard_at, last_heard_at, heard)
VALUES ($1, $2, $2, true)
ON CONFLICT (trace_tag) DO UPDATE SET
first_heard_at = CASE WHEN trace_tags.heard THEN LEAST(trace_tags.first_heard_at, EXCLUDED.first_heard_at)
ELSE EXCLUDED.first_heard_at END,
last_heard_at = CASE WHEN trace_tags.heard THEN GREATEST(trace_tags.last_heard_at, EXCLUDED.last_heard_at)
ELSE EXCLUDED.last_heard_at END,
heard = true
`
type RecordTraceParams struct {
TraceTag []byte `json:"trace_tag"`
HeardAt pgtype.Timestamptz `json:"heard_at"`
Iata string `json:"iata"`
}
// One hearing of a TRACE packet: trace_iatas refreshes at most hourly and the tag's heard
// window widens (the first hearing replaces the packet's provisional times). Packet counts
// and payloads come from UpsertPacket.
func (q *Queries) RecordTrace(ctx context.Context, arg RecordTraceParams) error {
_, err := q.db.Exec(ctx, recordTrace, arg.TraceTag, arg.HeardAt, arg.Iata)
return err
}
const refreshRadioPresets = `-- name: RefreshRadioPresets :exec
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_radio_presets
`
func (q *Queries) RefreshRadioPresets(ctx context.Context) error {
_, err := q.db.Exec(ctx, refreshRadioPresets)
return err
}
const resolveEndpointHashPairs = `-- name: ResolveEndpointHashPairs :many
SELECT ns.iata, ns.prefix_1 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
CROSS JOIN LATERAL (
SELECT id, name, latitude, longitude, public_key
FROM nodes WHERE id = ns.node_id
LIMIT 1
) n
WHERE ns.iata = ANY($1::bpchar[])
AND ns.prefix_1 = ANY($2::bytea[])
`
type ResolveEndpointHashPairsParams struct {
Iatas []string `json:"iatas"`
Hashes [][]byte `json:"hashes"`
}
type ResolveEndpointHashPairsRow struct {
Iata string `json:"iata"`
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
// Batch form of ResolveEndpointHashes for a page of packets. Matches the cross
// product of IATAs and hashes; callers pick out the pairs they asked for.
func (q *Queries) ResolveEndpointHashPairs(ctx context.Context, arg ResolveEndpointHashPairsParams) ([]ResolveEndpointHashPairsRow, error) {
rows, err := q.db.Query(ctx, resolveEndpointHashPairs, arg.Iatas, arg.Hashes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolveEndpointHashPairsRow{}
for rows.Next() {
var i ResolveEndpointHashPairsRow
if err := rows.Scan(
&i.Iata,
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolveEndpointHashes = `-- name: ResolveEndpointHashes :many
SELECT ns.prefix_1 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
CROSS JOIN LATERAL (
SELECT id, name, latitude, longitude, public_key
FROM nodes WHERE id = ns.node_id
LIMIT 1
) n
WHERE ns.iata = $1
AND ns.prefix_1 = ANY($2::bytea[])
`
type ResolveEndpointHashesParams struct {
Iata string `json:"iata"`
Column2 [][]byte `json:"column_2"`
}
type ResolveEndpointHashesRow struct {
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
// Logical endpoints can be any advertised role, unlike intermediate relay hops.
// Endpoint hashes are always one byte; use the existing (iata, prefix_1) index.
// LIMIT 1 keeps generic plans on a node PK lookup per candidate instead of
// flattening the join into a scan of all nodes. The PK already guarantees one row.
func (q *Queries) ResolveEndpointHashes(ctx context.Context, arg ResolveEndpointHashesParams) ([]ResolveEndpointHashesRow, error) {
rows, err := q.db.Query(ctx, resolveEndpointHashes, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolveEndpointHashesRow{}
for rows.Next() {
var i ResolveEndpointHashesRow
if err := rows.Scan(
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolvePathHashesP1 = `-- name: ResolvePathHashesP1 :many
SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
JOIN nodes n ON n.id = ns.node_id
WHERE ns.iata = $1
AND n.node_type IN (2, 3)
AND ns.prefix_1 = ANY($2::bytea[])
`
type ResolvePathHashesP1Params struct {
Iata string `json:"iata"`
Column2 [][]byte `json:"column_2"`
}
type ResolvePathHashesP1Row struct {
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
// ============================================================
// HELPERS
// ============================================================
// Path hash resolution is split per prefix width so each query gets a
// cacheable generic plan on its (iata, prefix_N) index; a single CASE
// predicate forced a fresh custom plan on every call.
func (q *Queries) ResolvePathHashesP1(ctx context.Context, arg ResolvePathHashesP1Params) ([]ResolvePathHashesP1Row, error) {
rows, err := q.db.Query(ctx, resolvePathHashesP1, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolvePathHashesP1Row{}
for rows.Next() {
var i ResolvePathHashesP1Row
if err := rows.Scan(
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolvePathHashesP2 = `-- name: ResolvePathHashesP2 :many
SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
JOIN nodes n ON n.id = ns.node_id
WHERE ns.iata = $1
AND n.node_type IN (2, 3)
AND ns.prefix_2 = ANY($2::bytea[])
`
type ResolvePathHashesP2Params struct {
Iata string `json:"iata"`
Column2 [][]byte `json:"column_2"`
}
type ResolvePathHashesP2Row struct {
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
func (q *Queries) ResolvePathHashesP2(ctx context.Context, arg ResolvePathHashesP2Params) ([]ResolvePathHashesP2Row, error) {
rows, err := q.db.Query(ctx, resolvePathHashesP2, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolvePathHashesP2Row{}
for rows.Next() {
var i ResolvePathHashesP2Row
if err := rows.Scan(
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolvePathHashesP3 = `-- name: ResolvePathHashesP3 :many
SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
JOIN nodes n ON n.id = ns.node_id
WHERE ns.iata = $1
AND n.node_type IN (2, 3)
AND ns.prefix_3 = ANY($2::bytea[])
`
type ResolvePathHashesP3Params struct {
Iata string `json:"iata"`
Column2 [][]byte `json:"column_2"`
}
type ResolvePathHashesP3Row struct {
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
func (q *Queries) ResolvePathHashesP3(ctx context.Context, arg ResolvePathHashesP3Params) ([]ResolvePathHashesP3Row, error) {
rows, err := q.db.Query(ctx, resolvePathHashesP3, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolvePathHashesP3Row{}
for rows.Next() {
var i ResolvePathHashesP3Row
if err := rows.Scan(
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolvePathHashesP4 = `-- name: ResolvePathHashesP4 :many
SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM node_short_ids ns
JOIN nodes n ON n.id = ns.node_id
WHERE ns.iata = $1
AND n.node_type IN (2, 3)
AND ns.prefix_4 = ANY($2::bytea[])
`
type ResolvePathHashesP4Params struct {
Iata string `json:"iata"`
Column2 [][]byte `json:"column_2"`
}
type ResolvePathHashesP4Row struct {
Hash []byte `json:"hash"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
func (q *Queries) ResolvePathHashesP4(ctx context.Context, arg ResolvePathHashesP4Params) ([]ResolvePathHashesP4Row, error) {
rows, err := q.db.Query(ctx, resolvePathHashesP4, arg.Iata, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolvePathHashesP4Row{}
for rows.Next() {
var i ResolvePathHashesP4Row
if err := rows.Scan(
&i.Hash,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const saveChannelCatalogue = `-- name: SaveChannelCatalogue :exec
INSERT INTO meshmapper_channel_catalogues (iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error)
VALUES ($1, $2, $3::jsonb, $4::text,
$5::timestamptz, $6, $7, $8)
ON CONFLICT (iata, url) DO UPDATE SET
payload = COALESCE(EXCLUDED.payload, meshmapper_channel_catalogues.payload),
etag = COALESCE(EXCLUDED.etag, meshmapper_channel_catalogues.etag),
checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_channel_catalogues.checked_at),
attempted_at = EXCLUDED.attempted_at,
next_attempt = EXCLUDED.next_attempt,
last_error = EXCLUDED.last_error
`
type SaveChannelCatalogueParams struct {
Iata string `json:"iata"`
Url string `json:"url"`
Payload []byte `json:"payload"`
Etag *string `json:"etag"`
CheckedAt pgtype.Timestamptz `json:"checked_at"`
AttemptedAt pgtype.Timestamptz `json:"attempted_at"`
NextAttempt pgtype.Timestamptz `json:"next_attempt"`
LastError string `json:"last_error"`
}
// NULL payload/etag/checked_at retain the last good list after an error or 304.
func (q *Queries) SaveChannelCatalogue(ctx context.Context, arg SaveChannelCatalogueParams) error {
_, err := q.db.Exec(ctx, saveChannelCatalogue,
arg.Iata,
arg.Url,
arg.Payload,
arg.Etag,
arg.CheckedAt,
arg.AttemptedAt,
arg.NextAttempt,
arg.LastError,
)
return err
}
const saveScopeCatalogue = `-- name: SaveScopeCatalogue :exec
WITH inserted AS (
INSERT INTO transport_scopes (name, transport_key, key_fingerprint, imported_only)
SELECT entry.name, entry.key, entry.fingerprint, TRUE
FROM (SELECT unnest($9::text[]) AS name, unnest($10::bytea[]) AS key,
unnest($11::bytea[]) AS fingerprint) AS entry
ON CONFLICT (name) DO NOTHING
)
INSERT INTO meshmapper_scope_catalogues (iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error)
VALUES ($1, $2, $3::jsonb, $4::text,
$5::timestamptz, $6, $7, $8)
ON CONFLICT (iata, url) DO UPDATE SET
payload = COALESCE(EXCLUDED.payload, meshmapper_scope_catalogues.payload),
etag = COALESCE(EXCLUDED.etag, meshmapper_scope_catalogues.etag),
checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_scope_catalogues.checked_at),
attempted_at = EXCLUDED.attempted_at,
next_attempt = EXCLUDED.next_attempt,
last_error = EXCLUDED.last_error
`
type SaveScopeCatalogueParams struct {
Iata string `json:"iata"`
Url string `json:"url"`
Payload []byte `json:"payload"`
Etag *string `json:"etag"`
CheckedAt pgtype.Timestamptz `json:"checked_at"`
AttemptedAt pgtype.Timestamptz `json:"attempted_at"`
NextAttempt pgtype.Timestamptz `json:"next_attempt"`
LastError string `json:"last_error"`
Names []string `json:"names"`
Keys [][]byte `json:"keys"`
Fingerprints [][]byte `json:"fingerprints"`
}
// One statement commits the validated snapshot and its lookup identities together.
// Empty arrays insert nothing. NULL payload/checked_at retain last-known-good data
// after an error or 304. Imported names never replace existing manual metadata.
func (q *Queries) SaveScopeCatalogue(ctx context.Context, arg SaveScopeCatalogueParams) error {
_, err := q.db.Exec(ctx, saveScopeCatalogue,
arg.Iata,
arg.Url,
arg.Payload,
arg.Etag,
arg.CheckedAt,
arg.AttemptedAt,
arg.NextAttempt,
arg.LastError,
arg.Names,
arg.Keys,
arg.Fingerprints,
)
return err
}
const saveZoneBoundary = `-- name: SaveZoneBoundary :exec
INSERT INTO meshmapper_zone_boundaries (iata, url, feature, etag, checked_at, attempted_at, next_attempt, last_error)
VALUES ($1, $2, $3::jsonb, $4::text,
$5::timestamptz, $6, $7, $8)
ON CONFLICT (iata) DO UPDATE SET
url = EXCLUDED.url,
feature = COALESCE(EXCLUDED.feature, meshmapper_zone_boundaries.feature),
etag = COALESCE(EXCLUDED.etag, meshmapper_zone_boundaries.etag),
checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_zone_boundaries.checked_at),
attempted_at = EXCLUDED.attempted_at,
next_attempt = EXCLUDED.next_attempt,
last_error = EXCLUDED.last_error
`
type SaveZoneBoundaryParams struct {
Iata string `json:"iata"`
Url string `json:"url"`
Feature []byte `json:"feature"`
Etag *string `json:"etag"`
CheckedAt pgtype.Timestamptz `json:"checked_at"`
AttemptedAt pgtype.Timestamptz `json:"attempted_at"`
NextAttempt pgtype.Timestamptz `json:"next_attempt"`
LastError string `json:"last_error"`
}
// NULL feature/etag/checked_at retain the last good boundary after an error or 304.
func (q *Queries) SaveZoneBoundary(ctx context.Context, arg SaveZoneBoundaryParams) error {
_, err := q.db.Exec(ctx, saveZoneBoundary,
arg.Iata,
arg.Url,
arg.Feature,
arg.Etag,
arg.CheckedAt,
arg.AttemptedAt,
arg.NextAttempt,
arg.LastError,
)
return err
}
const saveZoneList = `-- name: SaveZoneList :exec
INSERT INTO meshmapper_zone_lists (country, payload, etag, fetched_at, attempted_at, next_attempt, last_error)
VALUES ($1, $2::jsonb, $3::text,
$4::timestamptz, $5, $6, $7)
ON CONFLICT (country) DO UPDATE SET
payload = COALESCE(EXCLUDED.payload, meshmapper_zone_lists.payload),
etag = COALESCE(EXCLUDED.etag, meshmapper_zone_lists.etag),
fetched_at = COALESCE(EXCLUDED.fetched_at, meshmapper_zone_lists.fetched_at),
attempted_at = EXCLUDED.attempted_at,
next_attempt = EXCLUDED.next_attempt,
last_error = EXCLUDED.last_error
`
type SaveZoneListParams struct {
Country string `json:"country"`
Payload []byte `json:"payload"`
Etag *string `json:"etag"`
FetchedAt pgtype.Timestamptz `json:"fetched_at"`
AttemptedAt pgtype.Timestamptz `json:"attempted_at"`
NextAttempt pgtype.Timestamptz `json:"next_attempt"`
LastError string `json:"last_error"`
}
// NULL payload/etag/fetched_at retain the last good list after an error or 304.
func (q *Queries) SaveZoneList(ctx context.Context, arg SaveZoneListParams) error {
_, err := q.db.Exec(ctx, saveZoneList,
arg.Country,
arg.Payload,
arg.Etag,
arg.FetchedAt,
arg.AttemptedAt,
arg.NextAttempt,
arg.LastError,
)
return err
}
const searchKnownRoutes = `-- name: SearchKnownRoutes :many
SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count
FROM known_routes
WHERE iata = $1
AND array_position(hash_prefix, $2::bytea) IS NOT NULL
AND array_position(hash_prefix, $3::bytea) IS NOT NULL
AND array_position(hash_prefix, $2::bytea) < array_position(hash_prefix, $3::bytea)
ORDER BY hop_count ASC, last_seen DESC
`
type SearchKnownRoutesParams struct {
Iata string `json:"iata"`
Column2 []byte `json:"column_2"`
Column3 []byte `json:"column_3"`
}
type SearchKnownRoutesRow struct {
ID int64 `json:"id"`
NodeIds []uuid.UUID `json:"node_ids"`
HashPrefix [][]byte `json:"hash_prefix"`
Iata string `json:"iata"`
HopCount int32 `json:"hop_count"`
FirstSeen pgtype.Timestamptz `json:"first_seen"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
ObservationCount int64 `json:"observation_count"`
}
// Returns known routes containing a subsequence from source to destination hash prefix.
// Verifies source appears before destination in the route.
func (q *Queries) SearchKnownRoutes(ctx context.Context, arg SearchKnownRoutesParams) ([]SearchKnownRoutesRow, error) {
rows, err := q.db.Query(ctx, searchKnownRoutes, arg.Iata, arg.Column2, arg.Column3)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SearchKnownRoutesRow{}
for rows.Next() {
var i SearchKnownRoutesRow
if err := rows.Scan(
&i.ID,
&i.NodeIds,
&i.HashPrefix,
&i.Iata,
&i.HopCount,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setNodeDefaultScope = `-- name: SetNodeDefaultScope :exec
UPDATE nodes SET default_scope_id = $2 WHERE id = $1
`
type SetNodeDefaultScopeParams struct {
ID uuid.UUID `json:"id"`
DefaultScopeID *int32 `json:"default_scope_id"`
}
func (q *Queries) SetNodeDefaultScope(ctx context.Context, arg SetNodeDefaultScopeParams) error {
_, err := q.db.Exec(ctx, setNodeDefaultScope, arg.ID, arg.DefaultScopeID)
return err
}
const setNodeMultibytePaths = `-- name: SetNodeMultibytePaths :exec
UPDATE nodes SET supports_multibyte_paths = TRUE
WHERE id = $1 AND supports_multibyte_paths = FALSE
`
func (q *Queries) SetNodeMultibytePaths(ctx context.Context, id uuid.UUID) error {
_, err := q.db.Exec(ctx, setNodeMultibytePaths, id)
return err
}
const setNodeMultibyteTraces = `-- name: SetNodeMultibyteTraces :exec
UPDATE nodes SET supports_multibyte_traces = TRUE
WHERE id = $1 AND supports_multibyte_traces = FALSE
`
func (q *Queries) SetNodeMultibyteTraces(ctx context.Context, id uuid.UUID) error {
_, err := q.db.Exec(ctx, setNodeMultibyteTraces, id)
return err
}
const setPacketDecrypted = `-- name: SetPacketDecrypted :exec
UPDATE packets SET decrypted = true WHERE packet_hash = $1
`
func (q *Queries) SetPacketDecrypted(ctx context.Context, packetHash []byte) error {
_, err := q.db.Exec(ctx, setPacketDecrypted, packetHash)
return err
}
const touchObserverBrokers = `-- name: TouchObserverBrokers :exec
UPDATE observer_brokers ob SET
last_seen = GREATEST(ob.last_seen, v.seen),
last_packet_at = GREATEST(ob.last_packet_at, v.packet)
FROM (
SELECT unnest($1::uuid[]) AS observer_id,
unnest($2::text[]) AS broker_name,
unnest($3::timestamptz[]) AS seen,
unnest($4::timestamptz[]) AS packet
) v
WHERE ob.observer_id = v.observer_id AND ob.broker_name = v.broker_name
`
type TouchObserverBrokersParams struct {
Column1 []uuid.UUID `json:"column_1"`
Column2 []string `json:"column_2"`
Column3 []pgtype.Timestamptz `json:"column_3"`
Column4 []pgtype.Timestamptz `json:"column_4"`
}
func (q *Queries) TouchObserverBrokers(ctx context.Context, arg TouchObserverBrokersParams) error {
_, err := q.db.Exec(ctx, touchObserverBrokers,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
)
return err
}
const touchObservers = `-- name: TouchObservers :exec
UPDATE observers o SET
last_seen = GREATEST(o.last_seen, v.seen),
observation_count = COALESCE(o.observation_count, 0) + v.delta,
last_iata = CASE WHEN v.iata <> '' AND (o.last_iata_at IS NULL OR v.iata_at > o.last_iata_at)
THEN v.iata ELSE o.last_iata END,
last_iata_at = GREATEST(o.last_iata_at, v.iata_at)
FROM (
SELECT unnest($1::uuid[]) AS id,
unnest($2::timestamptz[]) AS seen,
unnest($3::int[]) AS delta,
unnest($4::text[]) AS iata,
unnest($5::timestamptz[]) AS iata_at
) v
WHERE o.id = v.id
`
type TouchObserversParams struct {
Column1 []uuid.UUID `json:"column_1"`
Column2 []pgtype.Timestamptz `json:"column_2"`
Column3 []int32 `json:"column_3"`
Column4 []string `json:"column_4"`
Column5 []pgtype.Timestamptz `json:"column_5"`
}
// Batched flush of coalesced presence bumps. GREATEST keeps a late flush
// from regressing a newer write-through (e.g. a status update).
func (q *Queries) TouchObservers(ctx context.Context, arg TouchObserversParams) error {
_, err := q.db.Exec(ctx, touchObservers,
arg.Column1,
arg.Column2,
arg.Column3,
arg.Column4,
arg.Column5,
)
return err
}
const touchPackets = `-- name: TouchPackets :exec
UPDATE packets p SET
last_heard_at = GREATEST(p.last_heard_at, v.heard)
FROM (
SELECT unnest($1::bytea[]) AS packet_hash,
unnest($2::timestamptz[]) AS heard
) v
WHERE p.packet_hash = v.packet_hash
`
type TouchPacketsParams struct {
Column1 [][]byte `json:"column_1"`
Column2 []pgtype.Timestamptz `json:"column_2"`
}
func (q *Queries) TouchPackets(ctx context.Context, arg TouchPacketsParams) error {
_, err := q.db.Exec(ctx, touchPackets, arg.Column1, arg.Column2)
return err
}
const updateObserverRegionScope = `-- name: UpdateObserverRegionScope :exec
UPDATE observers SET region_scope = $2 WHERE id = $1
`
type UpdateObserverRegionScopeParams struct {
ID uuid.UUID `json:"id"`
RegionScope *string `json:"region_scope"`
}
// Records the observer's own OTA-reported region scope, from the "self"
// field of a /neighbors report. Always known (not queried OTA), so this
// unconditionally overwrites, unlike the neighbor-side region_scope.
func (q *Queries) UpdateObserverRegionScope(ctx context.Context, arg UpdateObserverRegionScopeParams) error {
_, err := q.db.Exec(ctx, updateObserverRegionScope, arg.ID, arg.RegionScope)
return err
}
const updateObserverStatus = `-- name: UpdateObserverStatus :one
UPDATE observers SET
display_name = COALESCE(NULLIF($2, ''), display_name),
observer_type = COALESCE(NULLIF($3, ''), observer_type),
software_version = COALESCE($4, software_version),
hardware_model = COALESCE($5, hardware_model),
firmware_version = COALESCE($6, firmware_version),
firmware_build = COALESCE($7, firmware_build),
radio_freq_mhz = COALESCE($8, radio_freq_mhz),
radio_sf = COALESCE($9, radio_sf),
radio_bw_khz = COALESCE($10, radio_bw_khz),
radio_cr = COALESCE($11, radio_cr),
battery_level = COALESCE($12, battery_level),
uptime_seconds = COALESCE($13, uptime_seconds),
status_metadata = $14,
last_status_at = NOW(),
last_seen = NOW()
WHERE public_key = $1
RETURNING id
`
type UpdateObserverStatusParams struct {
PublicKey []byte `json:"public_key"`
Column2 interface{} `json:"column_2"`
Column3 interface{} `json:"column_3"`
SoftwareVersion *string `json:"software_version"`
HardwareModel *string `json:"hardware_model"`
FirmwareVersion *string `json:"firmware_version"`
FirmwareBuild *string `json:"firmware_build"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
RadioCr *int16 `json:"radio_cr"`
BatteryLevel *float32 `json:"battery_level"`
UptimeSeconds *int64 `json:"uptime_seconds"`
StatusMetadata []byte `json:"status_metadata"`
}
func (q *Queries) UpdateObserverStatus(ctx context.Context, arg UpdateObserverStatusParams) (uuid.UUID, error) {
row := q.db.QueryRow(ctx, updateObserverStatus,
arg.PublicKey,
arg.Column2,
arg.Column3,
arg.SoftwareVersion,
arg.HardwareModel,
arg.FirmwareVersion,
arg.FirmwareBuild,
arg.RadioFreqMhz,
arg.RadioSf,
arg.RadioBwKhz,
arg.RadioCr,
arg.BatteryLevel,
arg.UptimeSeconds,
arg.StatusMetadata,
)
var id uuid.UUID
err := row.Scan(&id)
return id, err
}
const upsertChannel = `-- name: UpsertChannel :one
INSERT INTO channels (channel_hash, key_fingerprint, name, hashtag, is_hashtag, key_known, last_seen)
VALUES ($1, $2::bytea, $3, $4, $5, ($2 IS NOT NULL), NOW())
ON CONFLICT (channel_hash, key_fingerprint) DO UPDATE SET
last_seen = NOW(),
name = COALESCE(EXCLUDED.name, channels.name)
RETURNING id, channel_hash, key_fingerprint, name, hashtag, is_hashtag, is_public, key_known, first_seen, last_seen, message_count
`
type UpsertChannelParams struct {
ChannelHash []byte `json:"channel_hash"`
Column2 []byte `json:"column_2"`
Name *string `json:"name"`
Hashtag *string `json:"hashtag"`
IsHashtag *bool `json:"is_hashtag"`
}
// ============================================================
// CHANNELS
// ============================================================
// Upsert a channel by (hash, key_fingerprint). Pass NULL fingerprint for
// hash-only records (key unknown). Returns the channel row.
func (q *Queries) UpsertChannel(ctx context.Context, arg UpsertChannelParams) (Channel, error) {
row := q.db.QueryRow(ctx, upsertChannel,
arg.ChannelHash,
arg.Column2,
arg.Name,
arg.Hashtag,
arg.IsHashtag,
)
var i Channel
err := row.Scan(
&i.ID,
&i.ChannelHash,
&i.KeyFingerprint,
&i.Name,
&i.Hashtag,
&i.IsHashtag,
&i.IsPublic,
&i.KeyKnown,
&i.FirstSeen,
&i.LastSeen,
&i.MessageCount,
)
return i, err
}
const upsertChannelHashOnly = `-- name: UpsertChannelHashOnly :one
INSERT INTO channels (channel_hash, last_seen)
VALUES ($1, NOW())
ON CONFLICT (channel_hash) WHERE key_fingerprint IS NULL DO UPDATE SET
last_seen = NOW()
RETURNING id
`
func (q *Queries) UpsertChannelHashOnly(ctx context.Context, channelHash []byte) (int32, error) {
row := q.db.QueryRow(ctx, upsertChannelHashOnly, channelHash)
var id int32
err := row.Scan(&id)
return id, err
}
const upsertChannelIATA = `-- name: UpsertChannelIATA :exec
INSERT INTO channel_iatas (channel_hash, iata, last_heard)
VALUES ($1, $2, $3)
ON CONFLICT (channel_hash, iata) DO UPDATE SET
last_heard = EXCLUDED.last_heard
WHERE EXCLUDED.last_heard > channel_iatas.last_heard + INTERVAL '1 hour'
`
type UpsertChannelIATAParams struct {
ChannelHash []byte `json:"channel_hash"`
Iata string `json:"iata"`
LastHeard pgtype.Timestamptz `json:"last_heard"`
}
// Refreshes at most hourly so repeat hears don't churn the row.
func (q *Queries) UpsertChannelIATA(ctx context.Context, arg UpsertChannelIATAParams) error {
_, err := q.db.Exec(ctx, upsertChannelIATA, arg.ChannelHash, arg.Iata, arg.LastHeard)
return err
}
const upsertIATA = `-- name: UpsertIATA :exec
INSERT INTO iata_codes (iata)
VALUES ($1)
ON CONFLICT (iata) DO NOTHING
`
// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: agpl
// ============================================================
// IATA CODES
// ============================================================
func (q *Queries) UpsertIATA(ctx context.Context, iata string) error {
_, err := q.db.Exec(ctx, upsertIATA, iata)
return err
}
const upsertIATABorder = `-- name: UpsertIATABorder :exec
INSERT INTO iata_codes (iata, border)
VALUES ($1, $2)
ON CONFLICT (iata) DO UPDATE SET
border = EXCLUDED.border
`
type UpsertIATABorderParams struct {
Iata string `json:"iata"`
Border []byte `json:"border"`
}
// Written by the config-file-driven seeder (internal/config/seed.go), not a
// runtime HTTP path. border is a full, pre-validated GeoJSON Feature with
// bbox already computed -- see internal/config/border.go.
func (q *Queries) UpsertIATABorder(ctx context.Context, arg UpsertIATABorderParams) error {
_, err := q.db.Exec(ctx, upsertIATABorder, arg.Iata, arg.Border)
return err
}
const upsertIATADetails = `-- name: UpsertIATADetails :exec
INSERT INTO iata_codes (iata, display_name, approx_lat, approx_lng)
VALUES ($1, $2, $3, $4)
ON CONFLICT (iata) DO UPDATE SET
display_name = EXCLUDED.display_name,
approx_lat = EXCLUDED.approx_lat,
approx_lng = EXCLUDED.approx_lng
`
type UpsertIATADetailsParams struct {
Iata string `json:"iata"`
DisplayName *string `json:"display_name"`
ApproxLat *float64 `json:"approx_lat"`
ApproxLng *float64 `json:"approx_lng"`
}
func (q *Queries) UpsertIATADetails(ctx context.Context, arg UpsertIATADetailsParams) error {
_, err := q.db.Exec(ctx, upsertIATADetails,
arg.Iata,
arg.DisplayName,
arg.ApproxLat,
arg.ApproxLng,
)
return err
}
const upsertImportedRegion = `-- name: UpsertImportedRegion :one
INSERT INTO regions (slug, name, display_order, center_lat, center_lng, zoom_level, imported, updated_at)
VALUES ($1, $2, $3, $4, $5, NULL, TRUE, NOW())
ON CONFLICT (slug) DO UPDATE SET
name = EXCLUDED.name,
display_order = EXCLUDED.display_order,
center_lat = EXCLUDED.center_lat,
center_lng = EXCLUDED.center_lng,
updated_at = NOW()
WHERE regions.imported
RETURNING id
`
type UpsertImportedRegionParams struct {
Slug string `json:"slug"`
Name string `json:"name"`
DisplayOrder *int32 `json:"display_order"`
CenterLat *float64 `json:"center_lat"`
CenterLng *float64 `json:"center_lng"`
}
// A hand-written region owns its slug: the WHERE turns a clash into no row.
func (q *Queries) UpsertImportedRegion(ctx context.Context, arg UpsertImportedRegionParams) (int32, error) {
row := q.db.QueryRow(ctx, upsertImportedRegion,
arg.Slug,
arg.Name,
arg.DisplayOrder,
arg.CenterLat,
arg.CenterLng,
)
var id int32
err := row.Scan(&id)
return id, err
}
const upsertKnownRoute = `-- name: UpsertKnownRoute :exec
INSERT INTO known_routes (path_key, node_ids, hash_prefix, iata, hop_count)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (iata, path_key) DO UPDATE SET
hash_prefix = EXCLUDED.hash_prefix,
last_seen = NOW(),
observation_count = known_routes.observation_count + 1
`
type UpsertKnownRouteParams struct {
PathKey []byte `json:"path_key"`
NodeIds []uuid.UUID `json:"node_ids"`
HashPrefix [][]byte `json:"hash_prefix"`
Iata string `json:"iata"`
HopCount int32 `json:"hop_count"`
}
// ============================================================
// ROUTES
// ============================================================
// Route identity is path_key, an md5 of node_ids computed by the caller.
// On conflict, observation_count and last_seen are bumped and hash_prefix follows the
// latest hearing, so evidence matches the hash width the route uses now.
func (q *Queries) UpsertKnownRoute(ctx context.Context, arg UpsertKnownRouteParams) error {
_, err := q.db.Exec(ctx, upsertKnownRoute,
arg.PathKey,
arg.NodeIds,
arg.HashPrefix,
arg.Iata,
arg.HopCount,
)
return err
}
const upsertNode = `-- name: UpsertNode :one
INSERT INTO nodes (public_key, node_type, name, latitude, longitude, location_source, last_advert_at, last_seen, radio_freq_mhz, radio_sf, radio_bw_khz, device_clock_drift_seconds)
VALUES ($1, $2, $3, $4, $5, CASE WHEN $4::double precision IS NOT NULL THEN 'advert' END, NOW(), NOW(), $6, $7, $8, $9)
ON CONFLICT (public_key) DO UPDATE SET
node_type = EXCLUDED.node_type,
name = COALESCE(EXCLUDED.name, nodes.name),
latitude = CASE WHEN $10::bool THEN NULL ELSE COALESCE(EXCLUDED.latitude, nodes.latitude) END,
longitude = CASE WHEN $10::bool THEN NULL ELSE COALESCE(EXCLUDED.longitude, nodes.longitude) END,
location_source = CASE WHEN $10::bool THEN NULL WHEN EXCLUDED.latitude IS NOT NULL THEN 'advert' ELSE nodes.location_source END,
last_advert_at = NOW(),
last_seen = NOW(),
radio_freq_mhz = EXCLUDED.radio_freq_mhz,
radio_sf = EXCLUDED.radio_sf,
radio_bw_khz = EXCLUDED.radio_bw_khz,
device_clock_drift_seconds = EXCLUDED.device_clock_drift_seconds
RETURNING id, public_key, node_type, name, latitude, longitude, location_source, last_advert_at, supports_multibyte_paths, supports_multibyte_traces, default_scope_id, min_firmware_version, first_seen, last_seen, radio_freq_mhz, radio_sf, radio_bw_khz, metadata, device_clock_drift_seconds
`
type UpsertNodeParams struct {
PublicKey []byte `json:"public_key"`
NodeType int16 `json:"node_type"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"`
ClearLocation bool `json:"clear_location"`
}
// ============================================================
// NODES
// ============================================================
func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (Node, error) {
row := q.db.QueryRow(ctx, upsertNode,
arg.PublicKey,
arg.NodeType,
arg.Name,
arg.Latitude,
arg.Longitude,
arg.RadioFreqMhz,
arg.RadioSf,
arg.RadioBwKhz,
arg.DeviceClockDriftSeconds,
arg.ClearLocation,
)
var i Node
err := row.Scan(
&i.ID,
&i.PublicKey,
&i.NodeType,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.LocationSource,
&i.LastAdvertAt,
&i.SupportsMultibytePaths,
&i.SupportsMultibyteTraces,
&i.DefaultScopeID,
&i.MinFirmwareVersion,
&i.FirstSeen,
&i.LastSeen,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.Metadata,
&i.DeviceClockDriftSeconds,
)
return i, err
}
const upsertNodeIATA = `-- name: UpsertNodeIATA :exec
INSERT INTO node_iatas (node_id, iata, last_heard, observation_count)
VALUES ($1, $2, NOW(), 1)
ON CONFLICT (node_id, iata) DO UPDATE SET
last_heard = NOW(),
observation_count = node_iatas.observation_count + 1
`
type UpsertNodeIATAParams struct {
NodeID uuid.UUID `json:"node_id"`
Iata string `json:"iata"`
}
// ============================================================
// NODE IATAS
// ============================================================
func (q *Queries) UpsertNodeIATA(ctx context.Context, arg UpsertNodeIATAParams) error {
_, err := q.db.Exec(ctx, upsertNodeIATA, arg.NodeID, arg.Iata)
return err
}
const upsertNodeNeighbor = `-- name: UpsertNodeNeighbor :exec
INSERT INTO node_neighbors (node_id, neighbor_id, iata, observation_count, snr, region_scope)
VALUES ($1, $2, $3, 1, $4, $5)
ON CONFLICT (node_id, neighbor_id, iata) DO UPDATE SET
last_seen = NOW(),
observation_count = node_neighbors.observation_count + 1,
snr = COALESCE(EXCLUDED.snr, node_neighbors.snr),
region_scope = COALESCE(EXCLUDED.region_scope, node_neighbors.region_scope)
`
type UpsertNodeNeighborParams struct {
NodeID uuid.UUID `json:"node_id"`
NeighborID uuid.UUID `json:"neighbor_id"`
Iata string `json:"iata"`
Snr *float32 `json:"snr"`
RegionScope *string `json:"region_scope"`
}
// ============================================================
// NEIGHBORS
// ============================================================
// Records or updates a neighbor relationship between two nodes observed in the same IATA.
// node_id is the advertising node, neighbor_id is the first-hop forwarder.
// snr is optional; pass NULL when no signal reading is available (the
// common case). regionScope is optional too; pass NULL whenever the OTA
// scope query for this neighbor didn't succeed (status != "responded"),
// so a failed/timed-out query doesn't erase a previously known scope.
// On conflict, snr and region_scope are only overwritten when a new
// non-null value is supplied.
func (q *Queries) UpsertNodeNeighbor(ctx context.Context, arg UpsertNodeNeighborParams) error {
_, err := q.db.Exec(ctx, upsertNodeNeighbor,
arg.NodeID,
arg.NeighborID,
arg.Iata,
arg.Snr,
arg.RegionScope,
)
return err
}
const upsertNodeShortID = `-- name: UpsertNodeShortID :exec
INSERT INTO node_short_ids (node_id, iata, prefix_4)
VALUES ($1, $2, $3)
ON CONFLICT (node_id, iata) DO NOTHING
`
type UpsertNodeShortIDParams struct {
NodeID uuid.UUID `json:"node_id"`
Iata string `json:"iata"`
Prefix4 []byte `json:"prefix_4"`
}
func (q *Queries) UpsertNodeShortID(ctx context.Context, arg UpsertNodeShortIDParams) error {
_, err := q.db.Exec(ctx, upsertNodeShortID, arg.NodeID, arg.Iata, arg.Prefix4)
return err
}
const upsertObserver = `-- name: UpsertObserver :one
INSERT INTO observers (public_key, observer_type, last_seen, last_iata, last_iata_at)
VALUES ($1, 'unknown', NOW(), NULLIF($2::text, ''),
CASE WHEN $2::text <> '' THEN $3::timestamptz END)
ON CONFLICT (public_key) DO UPDATE SET
last_seen = NOW(),
observation_count = observers.observation_count + 1,
last_iata = CASE WHEN EXCLUDED.last_iata IS NOT NULL
AND (observers.last_iata_at IS NULL OR EXCLUDED.last_iata_at > observers.last_iata_at)
THEN EXCLUDED.last_iata ELSE observers.last_iata END,
last_iata_at = GREATEST(observers.last_iata_at, EXCLUDED.last_iata_at)
RETURNING id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at
`
type UpsertObserverParams struct {
PublicKey []byte `json:"public_key"`
Iata string `json:"iata"`
IataAt pgtype.Timestamptz `json:"iata_at"`
}
// ============================================================
// OBSERVERS
// ============================================================
// Empty iata (status/neighbors) leaves last_iata alone; otherwise the newest iata_at wins.
func (q *Queries) UpsertObserver(ctx context.Context, arg UpsertObserverParams) (Observer, error) {
row := q.db.QueryRow(ctx, upsertObserver, arg.PublicKey, arg.Iata, arg.IataAt)
var i Observer
err := row.Scan(
&i.ID,
&i.PublicKey,
&i.DisplayName,
&i.ObserverType,
&i.SoftwareVersion,
&i.HardwareModel,
&i.FirmwareVersion,
&i.FirmwareBuild,
&i.RadioFreqMhz,
&i.RadioSf,
&i.RadioBwKhz,
&i.RadioCr,
&i.BatteryLevel,
&i.UptimeSeconds,
&i.StatusMetadata,
&i.LastStatusAt,
&i.FirstSeen,
&i.LastSeen,
&i.ObservationCount,
&i.Metadata,
&i.RegionScope,
&i.LastIata,
&i.LastIataAt,
)
return i, err
}
const upsertObserverBroker = `-- name: UpsertObserverBroker :exec
INSERT INTO observer_brokers (observer_id, broker_name, last_seen, last_packet_at)
VALUES ($1, $2, NOW(), CASE WHEN $3::boolean THEN NOW() END)
ON CONFLICT (observer_id, broker_name) DO UPDATE SET
last_seen = NOW(),
last_packet_at = COALESCE(EXCLUDED.last_packet_at, observer_brokers.last_packet_at)
`
type UpsertObserverBrokerParams struct {
ObserverID uuid.UUID `json:"observer_id"`
BrokerName string `json:"broker_name"`
IsPacket bool `json:"is_packet"`
}
// ============================================================
// OBSERVER BROKERS
// ============================================================
func (q *Queries) UpsertObserverBroker(ctx context.Context, arg UpsertObserverBrokerParams) error {
_, err := q.db.Exec(ctx, upsertObserverBroker, arg.ObserverID, arg.BrokerName, arg.IsPacket)
return err
}
const upsertObserverScope = `-- name: UpsertObserverScope :exec
INSERT INTO observer_scopes (observer_id, scope_id, last_seen)
VALUES ($1, $2, NOW())
ON CONFLICT (observer_id, scope_id) DO UPDATE SET
last_seen = NOW()
`
type UpsertObserverScopeParams struct {
ObserverID uuid.UUID `json:"observer_id"`
ScopeID int32 `json:"scope_id"`
}
func (q *Queries) UpsertObserverScope(ctx context.Context, arg UpsertObserverScopeParams) error {
_, err := q.db.Exec(ctx, upsertObserverScope, arg.ObserverID, arg.ScopeID)
return err
}
const upsertPacket = `-- name: UpsertPacket :one
WITH up AS (
INSERT INTO packets (
packet_hash,
payload_type,
payload_version,
route_type,
transport_codes_present,
region_code,
sub_region_code,
origin_pubkey,
raw_payload,
raw_header,
parsed_payload,
channel_hash,
scope_id,
trace_tag,
first_heard_at,
last_heard_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW(), NOW()
)
ON CONFLICT (packet_hash) DO UPDATE SET
last_heard_at = NOW()
RETURNING packet_hash, payload_type, payload_version, route_type, transport_codes_present, region_code, sub_region_code, origin_pubkey, raw_payload, raw_header, parsed_payload, decrypted, channel_hash, first_heard_at, last_heard_at, trace_tag, scope_id, (xmax = 0)
AS inserted
), trace AS (
INSERT INTO trace_tags (trace_tag, first_heard_at, last_heard_at, packet_count, trace_type, scope_id, best_payload, best_path_len)
SELECT u.trace_tag, u.first_heard_at, u.last_heard_at, 1, u.parsed_payload->>'type', u.scope_id, u.parsed_payload,
CASE WHEN jsonb_typeof(u.parsed_payload->'pathHashes') = 'array'
THEN jsonb_array_length(u.parsed_payload->'pathHashes') ELSE 0 END
FROM up u
WHERE u.inserted AND u.trace_tag IS NOT NULL
ON CONFLICT (trace_tag) DO UPDATE SET
packet_count = trace_tags.packet_count + 1,
trace_type = GREATEST(trace_tags.trace_type, EXCLUDED.trace_type),
scope_id = COALESCE(trace_tags.scope_id, EXCLUDED.scope_id),
best_payload = CASE WHEN EXCLUDED.best_path_len > trace_tags.best_path_len
THEN EXCLUDED.best_payload ELSE trace_tags.best_payload END,
best_path_len = GREATEST(trace_tags.best_path_len, EXCLUDED.best_path_len)
)
SELECT packet_hash, payload_type, payload_version, route_type, transport_codes_present, region_code, sub_region_code, origin_pubkey, raw_payload, raw_header, parsed_payload, decrypted, channel_hash, first_heard_at, last_heard_at, inserted
FROM up
`
type UpsertPacketParams struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
PayloadVersion int16 `json:"payload_version"`
RouteType int16 `json:"route_type"`
TransportCodesPresent *bool `json:"transport_codes_present"`
RegionCode *int32 `json:"region_code"`
SubRegionCode *int32 `json:"sub_region_code"`
OriginPubkey []byte `json:"origin_pubkey"`
RawPayload []byte `json:"raw_payload"`
RawHeader []byte `json:"raw_header"`
ParsedPayload []byte `json:"parsed_payload"`
ChannelHash []byte `json:"channel_hash"`
ScopeID *int32 `json:"scope_id"`
TraceTag []byte `json:"trace_tag"`
}
type UpsertPacketRow struct {
PacketHash []byte `json:"packet_hash"`
PayloadType int16 `json:"payload_type"`
PayloadVersion int16 `json:"payload_version"`
RouteType int16 `json:"route_type"`
TransportCodesPresent *bool `json:"transport_codes_present"`
RegionCode *int32 `json:"region_code"`
SubRegionCode *int32 `json:"sub_region_code"`
OriginPubkey []byte `json:"origin_pubkey"`
RawPayload []byte `json:"raw_payload"`
RawHeader []byte `json:"raw_header"`
ParsedPayload []byte `json:"parsed_payload"`
Decrypted *bool `json:"decrypted"`
ChannelHash []byte `json:"channel_hash"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
Inserted bool `json:"inserted"`
}
// ============================================================
// PACKETS
// ============================================================
// A new TRACE packet adds itself to its tag summary in the same statement, so the summary
// can't miss a stored packet. 'TRACE' > 'PING'; the longest path keeps the best payload.
func (q *Queries) UpsertPacket(ctx context.Context, arg UpsertPacketParams) (UpsertPacketRow, error) {
row := q.db.QueryRow(ctx, upsertPacket,
arg.PacketHash,
arg.PayloadType,
arg.PayloadVersion,
arg.RouteType,
arg.TransportCodesPresent,
arg.RegionCode,
arg.SubRegionCode,
arg.OriginPubkey,
arg.RawPayload,
arg.RawHeader,
arg.ParsedPayload,
arg.ChannelHash,
arg.ScopeID,
arg.TraceTag,
)
var i UpsertPacketRow
err := row.Scan(
&i.PacketHash,
&i.PayloadType,
&i.PayloadVersion,
&i.RouteType,
&i.TransportCodesPresent,
&i.RegionCode,
&i.SubRegionCode,
&i.OriginPubkey,
&i.RawPayload,
&i.RawHeader,
&i.ParsedPayload,
&i.Decrypted,
&i.ChannelHash,
&i.FirstHeardAt,
&i.LastHeardAt,
&i.Inserted,
)
return i, err
}
const upsertRegion = `-- name: UpsertRegion :one
INSERT INTO regions (slug, name, description, display_order, center_lat, center_lng, zoom_level, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
ON CONFLICT (slug) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description,
display_order = EXCLUDED.display_order,
center_lat = EXCLUDED.center_lat,
center_lng = EXCLUDED.center_lng,
zoom_level = EXCLUDED.zoom_level,
imported = FALSE, -- config owns the slug from now on
updated_at = NOW()
RETURNING id
`
type UpsertRegionParams struct {
Slug string `json:"slug"`
Name string `json:"name"`
Description *string `json:"description"`
DisplayOrder *int32 `json:"display_order"`
CenterLat *float64 `json:"center_lat"`
CenterLng *float64 `json:"center_lng"`
ZoomLevel *int32 `json:"zoom_level"`
}
func (q *Queries) UpsertRegion(ctx context.Context, arg UpsertRegionParams) (int32, error) {
row := q.db.QueryRow(ctx, upsertRegion,
arg.Slug,
arg.Name,
arg.Description,
arg.DisplayOrder,
arg.CenterLat,
arg.CenterLng,
arg.ZoomLevel,
)
var id int32
err := row.Scan(&id)
return id, err
}
const upsertTransportScope = `-- name: UpsertTransportScope :exec
INSERT INTO transport_scopes (name, display_name, transport_key, key_fingerprint)
VALUES ($1, $2, $3, $4)
ON CONFLICT (name) DO UPDATE SET
display_name = EXCLUDED.display_name,
transport_key = EXCLUDED.transport_key,
key_fingerprint = EXCLUDED.key_fingerprint,
imported_only = FALSE
`
type UpsertTransportScopeParams struct {
Name string `json:"name"`
DisplayName *string `json:"display_name"`
TransportKey []byte `json:"transport_key"`
KeyFingerprint []byte `json:"key_fingerprint"`
}
// ============================================================
// TRANSPORT CODES
// ============================================================
func (q *Queries) UpsertTransportScope(ctx context.Context, arg UpsertTransportScopeParams) error {
_, err := q.db.Exec(ctx, upsertTransportScope,
arg.Name,
arg.DisplayName,
arg.TransportKey,
arg.KeyFingerprint,
)
return err
}