mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-22 11:32:27 +00:00
release: v2.0.4 (#217)
* perf(routes): batch cross-IATA and hash route search (#216) One search fired a /routes/cross request per IATA pair (138k on 2026-10-08). Route search now takes an optional iatas list and runs a fixed number of index-backed queries. /info advertises built-in minimum client versions. * chore: bump version to 2.0.4
This commit is contained in:
1 parent
768889243c
commit
9bbf8efee5
37 files changed
+1625
-591
No files matched your search
+245
-88
@@ -403,65 +403,6 @@ func (q *Queries) GetChannelByID(ctx context.Context, id int32) (Channel, error)
|
||||
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
|
||||
@@ -540,20 +481,23 @@ func (q *Queries) GetIATABorder(ctx context.Context, iata string) (json.RawMessa
|
||||
return border, err
|
||||
}
|
||||
|
||||
const getKnownRoutesByNode = `-- name: GetKnownRoutesByNode :many
|
||||
const getKnownRoutesByNodes = `-- name: GetKnownRoutesByNodes :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)
|
||||
FROM known_routes kr
|
||||
WHERE kr.node_ids && $1::uuid[]
|
||||
AND kr.iata = ANY($2::bpchar[])
|
||||
AND ($1::uuid[])[array_position($2::bpchar[], kr.iata)] = ANY(kr.node_ids)
|
||||
ORDER BY hop_count ASC, last_seen DESC
|
||||
LIMIT $3
|
||||
`
|
||||
|
||||
type GetKnownRoutesByNodeParams struct {
|
||||
Iata string `json:"iata"`
|
||||
Column2 uuid.UUID `json:"column_2"`
|
||||
type GetKnownRoutesByNodesParams struct {
|
||||
Nodes []uuid.UUID `json:"nodes"`
|
||||
Iatas []string `json:"iatas"`
|
||||
RowLimit int32 `json:"row_limit"`
|
||||
}
|
||||
|
||||
type GetKnownRoutesByNodeRow struct {
|
||||
type GetKnownRoutesByNodesRow struct {
|
||||
ID int64 `json:"id"`
|
||||
NodeIds []uuid.UUID `json:"node_ids"`
|
||||
HashPrefix [][]byte `json:"hash_prefix"`
|
||||
@@ -564,15 +508,17 @@ type GetKnownRoutesByNodeRow struct {
|
||||
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)
|
||||
// Routes containing @nodes[i] in IATA @iatas[i] (aligned, one node per IATA). The &&
|
||||
// drives the node_ids GIN index; the indexed check keeps LIMIT counting real matches.
|
||||
func (q *Queries) GetKnownRoutesByNodes(ctx context.Context, arg GetKnownRoutesByNodesParams) ([]GetKnownRoutesByNodesRow, error) {
|
||||
rows, err := q.db.Query(ctx, getKnownRoutesByNodes, arg.Nodes, arg.Iatas, arg.RowLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetKnownRoutesByNodeRow{}
|
||||
items := []GetKnownRoutesByNodesRow{}
|
||||
for rows.Next() {
|
||||
var i GetKnownRoutesByNodeRow
|
||||
var i GetKnownRoutesByNodesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.NodeIds,
|
||||
@@ -593,6 +539,46 @@ func (q *Queries) GetKnownRoutesByNode(ctx context.Context, arg GetKnownRoutesBy
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNeighborLinks = `-- name: GetNeighborLinks :many
|
||||
SELECT node_id, neighbor_id, last_seen
|
||||
FROM node_neighbors
|
||||
WHERE (node_id = ANY($1::uuid[]) AND neighbor_id = ANY($2::uuid[]))
|
||||
OR (node_id = ANY($2::uuid[]) AND neighbor_id = ANY($1::uuid[]))
|
||||
`
|
||||
|
||||
type GetNeighborLinksParams struct {
|
||||
ANodes []uuid.UUID `json:"a_nodes"`
|
||||
BNodes []uuid.UUID `json:"b_nodes"`
|
||||
}
|
||||
|
||||
type GetNeighborLinksRow struct {
|
||||
NodeID uuid.UUID `json:"node_id"`
|
||||
NeighborID uuid.UUID `json:"neighbor_id"`
|
||||
LastSeen pgtype.Timestamptz `json:"last_seen"`
|
||||
}
|
||||
|
||||
// Neighbor rows between @a_nodes and @b_nodes in either orientation: ingest writes
|
||||
// adverts as (sender, forwarder) but traces as (receiver, sender). Callers normalise.
|
||||
func (q *Queries) GetNeighborLinks(ctx context.Context, arg GetNeighborLinksParams) ([]GetNeighborLinksRow, error) {
|
||||
rows, err := q.db.Query(ctx, getNeighborLinks, arg.ANodes, arg.BNodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetNeighborLinksRow{}
|
||||
for rows.Next() {
|
||||
var i GetNeighborLinksRow
|
||||
if err := rows.Scan(&i.NodeID, &i.NeighborID, &i.LastSeen); 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,
|
||||
@@ -4856,6 +4842,170 @@ func (q *Queries) ResolvePathHashesP4(ctx context.Context, arg ResolvePathHashes
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const resolveRelayHashPairsP1 = `-- name: ResolveRelayHashPairsP1 :many
|
||||
SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id
|
||||
FROM node_short_ids ns
|
||||
JOIN nodes n ON n.id = ns.node_id
|
||||
WHERE ns.iata = ANY($1::bpchar[])
|
||||
AND n.node_type IN (2, 3)
|
||||
AND ns.prefix_1 = ANY($2::bytea[])
|
||||
`
|
||||
|
||||
type ResolveRelayHashPairsP1Params struct {
|
||||
Iatas []string `json:"iatas"`
|
||||
Hashes [][]byte `json:"hashes"`
|
||||
}
|
||||
|
||||
type ResolveRelayHashPairsP1Row struct {
|
||||
Iata string `json:"iata"`
|
||||
Hash []byte `json:"hash"`
|
||||
NodeID uuid.UUID `json:"node_id"`
|
||||
}
|
||||
|
||||
// Batch form of ResolvePathHashesP1 across IATAs; callers check uniqueness per IATA.
|
||||
func (q *Queries) ResolveRelayHashPairsP1(ctx context.Context, arg ResolveRelayHashPairsP1Params) ([]ResolveRelayHashPairsP1Row, error) {
|
||||
rows, err := q.db.Query(ctx, resolveRelayHashPairsP1, arg.Iatas, arg.Hashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ResolveRelayHashPairsP1Row{}
|
||||
for rows.Next() {
|
||||
var i ResolveRelayHashPairsP1Row
|
||||
if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const resolveRelayHashPairsP2 = `-- name: ResolveRelayHashPairsP2 :many
|
||||
SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id
|
||||
FROM node_short_ids ns
|
||||
JOIN nodes n ON n.id = ns.node_id
|
||||
WHERE ns.iata = ANY($1::bpchar[])
|
||||
AND n.node_type IN (2, 3)
|
||||
AND ns.prefix_2 = ANY($2::bytea[])
|
||||
`
|
||||
|
||||
type ResolveRelayHashPairsP2Params struct {
|
||||
Iatas []string `json:"iatas"`
|
||||
Hashes [][]byte `json:"hashes"`
|
||||
}
|
||||
|
||||
type ResolveRelayHashPairsP2Row struct {
|
||||
Iata string `json:"iata"`
|
||||
Hash []byte `json:"hash"`
|
||||
NodeID uuid.UUID `json:"node_id"`
|
||||
}
|
||||
|
||||
// Batch form of ResolvePathHashesP2 across IATAs; callers check uniqueness per IATA.
|
||||
func (q *Queries) ResolveRelayHashPairsP2(ctx context.Context, arg ResolveRelayHashPairsP2Params) ([]ResolveRelayHashPairsP2Row, error) {
|
||||
rows, err := q.db.Query(ctx, resolveRelayHashPairsP2, arg.Iatas, arg.Hashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ResolveRelayHashPairsP2Row{}
|
||||
for rows.Next() {
|
||||
var i ResolveRelayHashPairsP2Row
|
||||
if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const resolveRelayHashPairsP3 = `-- name: ResolveRelayHashPairsP3 :many
|
||||
SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id
|
||||
FROM node_short_ids ns
|
||||
JOIN nodes n ON n.id = ns.node_id
|
||||
WHERE ns.iata = ANY($1::bpchar[])
|
||||
AND n.node_type IN (2, 3)
|
||||
AND ns.prefix_3 = ANY($2::bytea[])
|
||||
`
|
||||
|
||||
type ResolveRelayHashPairsP3Params struct {
|
||||
Iatas []string `json:"iatas"`
|
||||
Hashes [][]byte `json:"hashes"`
|
||||
}
|
||||
|
||||
type ResolveRelayHashPairsP3Row struct {
|
||||
Iata string `json:"iata"`
|
||||
Hash []byte `json:"hash"`
|
||||
NodeID uuid.UUID `json:"node_id"`
|
||||
}
|
||||
|
||||
// Batch form of ResolvePathHashesP3 across IATAs; callers check uniqueness per IATA.
|
||||
func (q *Queries) ResolveRelayHashPairsP3(ctx context.Context, arg ResolveRelayHashPairsP3Params) ([]ResolveRelayHashPairsP3Row, error) {
|
||||
rows, err := q.db.Query(ctx, resolveRelayHashPairsP3, arg.Iatas, arg.Hashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ResolveRelayHashPairsP3Row{}
|
||||
for rows.Next() {
|
||||
var i ResolveRelayHashPairsP3Row
|
||||
if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const resolveRelayHashPairsP4 = `-- name: ResolveRelayHashPairsP4 :many
|
||||
SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id
|
||||
FROM node_short_ids ns
|
||||
JOIN nodes n ON n.id = ns.node_id
|
||||
WHERE ns.iata = ANY($1::bpchar[])
|
||||
AND n.node_type IN (2, 3)
|
||||
AND ns.prefix_4 = ANY($2::bytea[])
|
||||
`
|
||||
|
||||
type ResolveRelayHashPairsP4Params struct {
|
||||
Iatas []string `json:"iatas"`
|
||||
Hashes [][]byte `json:"hashes"`
|
||||
}
|
||||
|
||||
type ResolveRelayHashPairsP4Row struct {
|
||||
Iata string `json:"iata"`
|
||||
Hash []byte `json:"hash"`
|
||||
NodeID uuid.UUID `json:"node_id"`
|
||||
}
|
||||
|
||||
// Batch form of ResolvePathHashesP4 across IATAs; callers check uniqueness per IATA.
|
||||
func (q *Queries) ResolveRelayHashPairsP4(ctx context.Context, arg ResolveRelayHashPairsP4Params) ([]ResolveRelayHashPairsP4Row, error) {
|
||||
rows, err := q.db.Query(ctx, resolveRelayHashPairsP4, arg.Iatas, arg.Hashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ResolveRelayHashPairsP4Row{}
|
||||
for rows.Next() {
|
||||
var i ResolveRelayHashPairsP4Row
|
||||
if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); 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,
|
||||
@@ -5026,23 +5176,24 @@ func (q *Queries) SaveZoneList(ctx context.Context, arg SaveZoneListParams) erro
|
||||
return err
|
||||
}
|
||||
|
||||
const searchKnownRoutes = `-- name: SearchKnownRoutes :many
|
||||
const searchKnownRoutesByNodes = `-- name: SearchKnownRoutesByNodes :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)
|
||||
FROM known_routes kr
|
||||
WHERE kr.node_ids && $1::uuid[]
|
||||
AND kr.node_ids && $2::uuid[]
|
||||
AND kr.iata = ANY($3::bpchar[])
|
||||
ORDER BY hop_count ASC, last_seen DESC
|
||||
LIMIT $4
|
||||
`
|
||||
|
||||
type SearchKnownRoutesParams struct {
|
||||
Iata string `json:"iata"`
|
||||
Column2 []byte `json:"column_2"`
|
||||
Column3 []byte `json:"column_3"`
|
||||
type SearchKnownRoutesByNodesParams struct {
|
||||
FromNodes []uuid.UUID `json:"from_nodes"`
|
||||
ToNodes []uuid.UUID `json:"to_nodes"`
|
||||
Iatas []string `json:"iatas"`
|
||||
RowLimit int32 `json:"row_limit"`
|
||||
}
|
||||
|
||||
type SearchKnownRoutesRow struct {
|
||||
type SearchKnownRoutesByNodesRow struct {
|
||||
ID int64 `json:"id"`
|
||||
NodeIds []uuid.UUID `json:"node_ids"`
|
||||
HashPrefix [][]byte `json:"hash_prefix"`
|
||||
@@ -5053,17 +5204,23 @@ type SearchKnownRoutesRow struct {
|
||||
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)
|
||||
// Routes holding a source and a destination candidate; callers check order and IATA.
|
||||
// A route's hops resolve in its own IATA, so candidates from other IATAs can't match.
|
||||
// Both && predicates use the node_ids GIN index.
|
||||
func (q *Queries) SearchKnownRoutesByNodes(ctx context.Context, arg SearchKnownRoutesByNodesParams) ([]SearchKnownRoutesByNodesRow, error) {
|
||||
rows, err := q.db.Query(ctx, searchKnownRoutesByNodes,
|
||||
arg.FromNodes,
|
||||
arg.ToNodes,
|
||||
arg.Iatas,
|
||||
arg.RowLimit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SearchKnownRoutesRow{}
|
||||
items := []SearchKnownRoutesByNodesRow{}
|
||||
for rows.Next() {
|
||||
var i SearchKnownRoutesRow
|
||||
var i SearchKnownRoutesByNodesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.NodeIds,
|
||||
|
||||
Reference in new issue
Block a user