atagen

vineyard

public
Star0
Watch0
ab6cd8e63410ba088ca5b4d4edb18049022ed8eb
1
ssh://brewery@pls.lobotomise.me:33/atagen/vineyard.git

Blame: src/journal.rs

25d14e49ab68atagen2026-09-24 11:07 UTC1
use std::cmp::Ordering;
25d14e49ab68atagen2026-09-24 11:07 UTC2
use std::collections::{BTreeSet, HashSet};
25d14e49ab68atagen2026-09-24 11:07 UTC3
use std::error::Error;
25d14e49ab68atagen2026-09-24 11:07 UTC4
use std::path::{Path, PathBuf};
25d14e49ab68atagen2026-09-24 11:07 UTC5
use std::sync::Arc;
25d14e49ab68atagen2026-09-24 11:07 UTC6
use std::time::{Duration, SystemTime, UNIX_EPOCH};
25d14e49ab68atagen2026-09-24 11:07 UTC7
25d14e49ab68atagen2026-09-24 11:07 UTC8
use anyhow::{Context as _, Result};
25d14e49ab68atagen2026-09-24 11:07 UTC9
use nostr::event::{Event, EventId, Kind, Signature, Tags};
25d14e49ab68atagen2026-09-24 11:07 UTC10
use nostr::filter::Filter;
25d14e49ab68atagen2026-09-24 11:07 UTC11
use nostr::key::PublicKey;
25d14e49ab68atagen2026-09-24 11:07 UTC12
use nostr::types::Timestamp;
25d14e49ab68atagen2026-09-24 11:07 UTC13
use nostr_database::{DatabaseEventStatus, RejectedReason, SaveEventStatus};
25d14e49ab68atagen2026-09-24 11:07 UTC14
use rusqlite::{
25d14e49ab68atagen2026-09-24 11:07 UTC15
    Connection, OptionalExtension as _, Params, Row, Transaction, params, params_from_iter,
25d14e49ab68atagen2026-09-24 11:07 UTC16
};
25d14e49ab68atagen2026-09-24 11:07 UTC17
use tokio::sync::{Mutex, MutexGuard};
25d14e49ab68atagen2026-09-24 11:07 UTC18
use tokio::task::spawn_blocking;
25d14e49ab68atagen2026-09-24 11:07 UTC19
25d14e49ab68atagen2026-09-24 11:07 UTC20
use crate::config::relay_url;
25d14e49ab68atagen2026-09-24 11:07 UTC21
use crate::query::EventQuery;
25d14e49ab68atagen2026-09-24 11:07 UTC22
25d14e49ab68atagen2026-09-24 11:07 UTC23
const SCHEMA: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC24
CREATE TABLE IF NOT EXISTS events(
25d14e49ab68atagen2026-09-24 11:07 UTC25
    id BLOB PRIMARY KEY NOT NULL CHECK(length(id) = 32),
25d14e49ab68atagen2026-09-24 11:07 UTC26
    pubkey BLOB NOT NULL CHECK(length(pubkey) = 32),
25d14e49ab68atagen2026-09-24 11:07 UTC27
    created_at INTEGER NOT NULL CHECK(created_at > 0),
25d14e49ab68atagen2026-09-24 11:07 UTC28
    kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 65535),
25d14e49ab68atagen2026-09-24 11:07 UTC29
    content TEXT NOT NULL,
25d14e49ab68atagen2026-09-24 11:07 UTC30
    tags JSONB NOT NULL,
25d14e49ab68atagen2026-09-24 11:07 UTC31
    sig BLOB NOT NULL CHECK(length(sig) = 64)
25d14e49ab68atagen2026-09-24 11:07 UTC32
) WITHOUT ROWID;
25d14e49ab68atagen2026-09-24 11:07 UTC33
25d14e49ab68atagen2026-09-24 11:07 UTC34
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at DESC);
25d14e49ab68atagen2026-09-24 11:07 UTC35
CREATE INDEX IF NOT EXISTS idx_events_pubkey_created_at ON events(pubkey, created_at DESC);
25d14e49ab68atagen2026-09-24 11:07 UTC36
CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind_created_at
25d14e49ab68atagen2026-09-24 11:07 UTC37
ON events(pubkey, kind, created_at DESC);
25d14e49ab68atagen2026-09-24 11:07 UTC38
CREATE INDEX IF NOT EXISTS idx_events_kind_created_at ON events(kind, created_at DESC);
25d14e49ab68atagen2026-09-24 11:07 UTC39
25d14e49ab68atagen2026-09-24 11:07 UTC40
DROP INDEX IF EXISTS idx_events_pubkey;
25d14e49ab68atagen2026-09-24 11:07 UTC41
DROP INDEX IF EXISTS idx_events_kind;
25d14e49ab68atagen2026-09-24 11:07 UTC42
25d14e49ab68atagen2026-09-24 11:07 UTC43
CREATE TABLE IF NOT EXISTS event_tags(
25d14e49ab68atagen2026-09-24 11:07 UTC44
    event_id BLOB NOT NULL CHECK(length(event_id) = 32),
25d14e49ab68atagen2026-09-24 11:07 UTC45
    tag_name TEXT NOT NULL CHECK(length(tag_name) = 1),
25d14e49ab68atagen2026-09-24 11:07 UTC46
    tag_value TEXT NOT NULL,
25d14e49ab68atagen2026-09-24 11:07 UTC47
    FOREIGN KEY(event_id) REFERENCES events(id) ON DELETE CASCADE,
25d14e49ab68atagen2026-09-24 11:07 UTC48
    PRIMARY KEY(tag_name, tag_value, event_id)
25d14e49ab68atagen2026-09-24 11:07 UTC49
);
25d14e49ab68atagen2026-09-24 11:07 UTC50
25d14e49ab68atagen2026-09-24 11:07 UTC51
CREATE INDEX IF NOT EXISTS idx_tags_event_id ON event_tags(event_id);
25d14e49ab68atagen2026-09-24 11:07 UTC52
25d14e49ab68atagen2026-09-24 11:07 UTC53
CREATE TABLE IF NOT EXISTS deleted_ids(
25d14e49ab68atagen2026-09-24 11:07 UTC54
    event_id BLOB PRIMARY KEY NOT NULL CHECK(length(event_id) = 32)
25d14e49ab68atagen2026-09-24 11:07 UTC55
) WITHOUT ROWID;
25d14e49ab68atagen2026-09-24 11:07 UTC56
25d14e49ab68atagen2026-09-24 11:07 UTC57
CREATE TABLE IF NOT EXISTS deleted_coordinates(
25d14e49ab68atagen2026-09-24 11:07 UTC58
    pubkey BLOB NOT NULL CHECK(length(pubkey) = 32),
25d14e49ab68atagen2026-09-24 11:07 UTC59
    kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 65535),
25d14e49ab68atagen2026-09-24 11:07 UTC60
    identifier TEXT NOT NULL,
25d14e49ab68atagen2026-09-24 11:07 UTC61
    deleted_at INTEGER NOT NULL CHECK(deleted_at > 0),
25d14e49ab68atagen2026-09-24 11:07 UTC62
    PRIMARY KEY(pubkey, kind, identifier)
25d14e49ab68atagen2026-09-24 11:07 UTC63
) WITHOUT ROWID;
25d14e49ab68atagen2026-09-24 11:07 UTC64
25d14e49ab68atagen2026-09-24 11:07 UTC65
DROP TRIGGER IF EXISTS sanctum_deleted_after_insert;
25d14e49ab68atagen2026-09-24 11:07 UTC66
DROP TABLE IF EXISTS sanctum_deleted_events;
25d14e49ab68atagen2026-09-24 11:07 UTC67
25d14e49ab68atagen2026-09-24 11:07 UTC68
CREATE TABLE IF NOT EXISTS vineyard_delivery(
25d14e49ab68atagen2026-09-24 11:07 UTC69
    sequence INTEGER PRIMARY KEY AUTOINCREMENT,
25d14e49ab68atagen2026-09-24 11:07 UTC70
    event_id BLOB NOT NULL UNIQUE REFERENCES events(id) ON DELETE CASCADE
25d14e49ab68atagen2026-09-24 11:07 UTC71
);
25d14e49ab68atagen2026-09-24 11:07 UTC72
25d14e49ab68atagen2026-09-24 11:07 UTC73
CREATE TABLE IF NOT EXISTS vineyard_peers(
25d14e49ab68atagen2026-09-24 11:07 UTC74
    peer TEXT PRIMARY KEY,
25d14e49ab68atagen2026-09-24 11:07 UTC75
    delivered INTEGER NOT NULL DEFAULT 0
25d14e49ab68atagen2026-09-24 11:07 UTC76
);
25d14e49ab68atagen2026-09-24 11:07 UTC77
25d14e49ab68atagen2026-09-24 11:07 UTC78
CREATE TABLE IF NOT EXISTS vineyard_checkpoints(
25d14e49ab68atagen2026-09-24 11:07 UTC79
    peer TEXT,
25d14e49ab68atagen2026-09-24 11:07 UTC80
    scope TEXT,
25d14e49ab68atagen2026-09-24 11:07 UTC81
    until INTEGER NOT NULL,
25d14e49ab68atagen2026-09-24 11:07 UTC82
    PRIMARY KEY(peer, scope)
25d14e49ab68atagen2026-09-24 11:07 UTC83
);
25d14e49ab68atagen2026-09-24 11:07 UTC84
25d14e49ab68atagen2026-09-24 11:07 UTC85
CREATE TABLE IF NOT EXISTS vineyard_retries(
25d14e49ab68atagen2026-09-24 11:07 UTC86
    peer TEXT,
25d14e49ab68atagen2026-09-24 11:07 UTC87
    sequence INTEGER REFERENCES vineyard_delivery(sequence) ON DELETE CASCADE,
25d14e49ab68atagen2026-09-24 11:07 UTC88
    retry_at INTEGER,
25d14e49ab68atagen2026-09-24 11:07 UTC89
    error TEXT,
25d14e49ab68atagen2026-09-24 11:07 UTC90
    PRIMARY KEY(peer, sequence)
25d14e49ab68atagen2026-09-24 11:07 UTC91
);
25d14e49ab68atagen2026-09-24 11:07 UTC92
25d14e49ab68atagen2026-09-24 11:07 UTC93
CREATE TABLE IF NOT EXISTS vineyard_discovered_peers(
25d14e49ab68atagen2026-09-24 11:07 UTC94
    signer BLOB,
25d14e49ab68atagen2026-09-24 11:07 UTC95
    url TEXT,
25d14e49ab68atagen2026-09-24 11:07 UTC96
    discovered_at INTEGER,
25d14e49ab68atagen2026-09-24 11:07 UTC97
    PRIMARY KEY(signer, url)
25d14e49ab68atagen2026-09-24 11:07 UTC98
) WITHOUT ROWID;
25d14e49ab68atagen2026-09-24 11:07 UTC99
25d14e49ab68atagen2026-09-24 11:07 UTC100
CREATE INDEX IF NOT EXISTS vineyard_retries_due
25d14e49ab68atagen2026-09-24 11:07 UTC101
ON vineyard_retries(peer, retry_at, sequence);
25d14e49ab68atagen2026-09-24 11:07 UTC102
25d14e49ab68atagen2026-09-24 11:07 UTC103
CREATE INDEX IF NOT EXISTS vineyard_discovered_urls
25d14e49ab68atagen2026-09-24 11:07 UTC104
ON vineyard_discovered_peers(url);
25d14e49ab68atagen2026-09-24 11:07 UTC105
25d14e49ab68atagen2026-09-24 11:07 UTC106
CREATE TRIGGER IF NOT EXISTS vineyard_delivery_after_insert
25d14e49ab68atagen2026-09-24 11:07 UTC107
AFTER INSERT ON events
25d14e49ab68atagen2026-09-24 11:07 UTC108
BEGIN
25d14e49ab68atagen2026-09-24 11:07 UTC109
    INSERT OR IGNORE INTO vineyard_delivery(event_id) VALUES (NEW.id);
25d14e49ab68atagen2026-09-24 11:07 UTC110
END;
25d14e49ab68atagen2026-09-24 11:07 UTC111
25d14e49ab68atagen2026-09-24 11:07 UTC112
";
25d14e49ab68atagen2026-09-24 11:07 UTC113
25d14e49ab68atagen2026-09-24 11:07 UTC114
const BACKFILL_DELIVERY: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC115
INSERT INTO vineyard_delivery(event_id)
25d14e49ab68atagen2026-09-24 11:07 UTC116
SELECT event.id
25d14e49ab68atagen2026-09-24 11:07 UTC117
FROM events AS event
25d14e49ab68atagen2026-09-24 11:07 UTC118
LEFT JOIN vineyard_delivery AS delivery ON delivery.event_id = event.id
25d14e49ab68atagen2026-09-24 11:07 UTC119
WHERE delivery.event_id IS NULL
25d14e49ab68atagen2026-09-24 11:07 UTC120
ORDER BY
25d14e49ab68atagen2026-09-24 11:07 UTC121
    CASE WHEN event.kind = 30617 THEN 0 ELSE 1 END,
25d14e49ab68atagen2026-09-24 11:07 UTC122
    event.created_at,
25d14e49ab68atagen2026-09-24 11:07 UTC123
    event.id
25d14e49ab68atagen2026-09-24 11:07 UTC124
";
25d14e49ab68atagen2026-09-24 11:07 UTC125
25d14e49ab68atagen2026-09-24 11:07 UTC126
const DISCOVERED_RELAY_TAGS: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC127
SELECT event.pubkey, json_extract(tag.value, '$[1]'), event.created_at
25d14e49ab68atagen2026-09-24 11:07 UTC128
FROM events AS event, json_each(event.tags) AS tag
25d14e49ab68atagen2026-09-24 11:07 UTC129
WHERE event.kind = 10002
25d14e49ab68atagen2026-09-24 11:07 UTC130
  AND json_extract(tag.value, '$[0]') = 'r'
25d14e49ab68atagen2026-09-24 11:07 UTC131
  AND typeof(json_extract(tag.value, '$[1]')) = 'text'
25d14e49ab68atagen2026-09-24 11:07 UTC132
ORDER BY event.created_at DESC, event.id
25d14e49ab68atagen2026-09-24 11:07 UTC133
";
25d14e49ab68atagen2026-09-24 11:07 UTC134
25d14e49ab68atagen2026-09-24 11:07 UTC135
const DUE_DELIVERIES: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC136
SELECT delivery.sequence, delivery.event_id
25d14e49ab68atagen2026-09-24 11:07 UTC137
FROM vineyard_delivery AS delivery
25d14e49ab68atagen2026-09-24 11:07 UTC138
JOIN vineyard_retries AS retry ON retry.sequence = delivery.sequence
25d14e49ab68atagen2026-09-24 11:07 UTC139
WHERE retry.peer = ?1 AND retry.retry_at <= ?2
25d14e49ab68atagen2026-09-24 11:07 UTC140
ORDER BY delivery.sequence
25d14e49ab68atagen2026-09-24 11:07 UTC141
LIMIT ?3
25d14e49ab68atagen2026-09-24 11:07 UTC142
";
25d14e49ab68atagen2026-09-24 11:07 UTC143
25d14e49ab68atagen2026-09-24 11:07 UTC144
const NEW_DELIVERIES: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC145
SELECT sequence, event_id
25d14e49ab68atagen2026-09-24 11:07 UTC146
FROM vineyard_delivery
25d14e49ab68atagen2026-09-24 11:07 UTC147
WHERE sequence > ?1
25d14e49ab68atagen2026-09-24 11:07 UTC148
ORDER BY sequence
25d14e49ab68atagen2026-09-24 11:07 UTC149
LIMIT ?2
25d14e49ab68atagen2026-09-24 11:07 UTC150
";
25d14e49ab68atagen2026-09-24 11:07 UTC151
25d14e49ab68atagen2026-09-24 11:07 UTC152
const REPOSITORIES: &str = "
25d14e49ab68atagen2026-09-24 11:07 UTC153
SELECT lower(hex(event.pubkey)), tag.tag_value, event.tags
25d14e49ab68atagen2026-09-24 11:07 UTC154
FROM events AS event
25d14e49ab68atagen2026-09-24 11:07 UTC155
JOIN event_tags AS tag ON event.id = tag.event_id
25d14e49ab68atagen2026-09-24 11:07 UTC156
WHERE event.kind = 30617 AND tag.tag_name = 'd'
25d14e49ab68atagen2026-09-24 11:07 UTC157
ORDER BY 1, 2, event.created_at DESC, event.id
25d14e49ab68atagen2026-09-24 11:07 UTC158
";
25d14e49ab68atagen2026-09-24 11:07 UTC159
25d14e49ab68atagen2026-09-24 11:07 UTC160
type Delivery = (u64, EventId);
25d14e49ab68atagen2026-09-24 11:07 UTC161
25d14e49ab68atagen2026-09-24 11:07 UTC162
#[derive(Debug, Clone)]
25d14e49ab68atagen2026-09-24 11:07 UTC163
pub struct Journal {
25d14e49ab68atagen2026-09-24 11:07 UTC164
    connection: Arc<Mutex<Connection>>,
25d14e49ab68atagen2026-09-24 11:07 UTC165
    writes: Arc<Mutex<()>>,
25d14e49ab68atagen2026-09-24 11:07 UTC166
}
25d14e49ab68atagen2026-09-24 11:07 UTC167
25d14e49ab68atagen2026-09-24 11:07 UTC168
struct StoredEvent {
25d14e49ab68atagen2026-09-24 11:07 UTC169
    id: Vec<u8>,
25d14e49ab68atagen2026-09-24 11:07 UTC170
    pubkey: Vec<u8>,
25d14e49ab68atagen2026-09-24 11:07 UTC171
    created_at: i64,
25d14e49ab68atagen2026-09-24 11:07 UTC172
    kind: i64,
25d14e49ab68atagen2026-09-24 11:07 UTC173
    content: String,
25d14e49ab68atagen2026-09-24 11:07 UTC174
    tags: String,
25d14e49ab68atagen2026-09-24 11:07 UTC175
    signature: Vec<u8>,
25d14e49ab68atagen2026-09-24 11:07 UTC176
}
25d14e49ab68atagen2026-09-24 11:07 UTC177
25d14e49ab68atagen2026-09-24 11:07 UTC178
impl Journal {
25d14e49ab68atagen2026-09-24 11:07 UTC179
    pub async fn open(
25d14e49ab68atagen2026-09-24 11:07 UTC180
        path: PathBuf,
25d14e49ab68atagen2026-09-24 11:07 UTC181
        max_database_bytes: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC182
        max_discovered_peers: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC183
        excluded_peers: Vec<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC184
        writes: Arc<Mutex<()>>,
25d14e49ab68atagen2026-09-24 11:07 UTC185
    ) -> Result<Self> {
25d14e49ab68atagen2026-09-24 11:07 UTC186
        let connection = spawn_blocking(move || {
25d14e49ab68atagen2026-09-24 11:07 UTC187
            open_connection(
25d14e49ab68atagen2026-09-24 11:07 UTC188
                &path,
25d14e49ab68atagen2026-09-24 11:07 UTC189
                max_database_bytes,
25d14e49ab68atagen2026-09-24 11:07 UTC190
                max_discovered_peers,
25d14e49ab68atagen2026-09-24 11:07 UTC191
                &excluded_peers,
25d14e49ab68atagen2026-09-24 11:07 UTC192
            )
25d14e49ab68atagen2026-09-24 11:07 UTC193
        })
25d14e49ab68atagen2026-09-24 11:07 UTC194
        .await
25d14e49ab68atagen2026-09-24 11:07 UTC195
        .context("journal task panicked")??;
25d14e49ab68atagen2026-09-24 11:07 UTC196
25d14e49ab68atagen2026-09-24 11:07 UTC197
        Ok(Self {
25d14e49ab68atagen2026-09-24 11:07 UTC198
            connection: Arc::new(Mutex::new(connection)),
25d14e49ab68atagen2026-09-24 11:07 UTC199
            writes,
25d14e49ab68atagen2026-09-24 11:07 UTC200
        })
25d14e49ab68atagen2026-09-24 11:07 UTC201
    }
25d14e49ab68atagen2026-09-24 11:07 UTC202
25d14e49ab68atagen2026-09-24 11:07 UTC203
    pub async fn pending(&self, peer: &str, limit: usize) -> Result<Vec<Delivery>> {
25d14e49ab68atagen2026-09-24 11:07 UTC204
        let peer_url = peer.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC205
        self.run(move |connection| pending_deliveries(connection, &peer_url, limit))
25d14e49ab68atagen2026-09-24 11:07 UTC206
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC207
    }
25d14e49ab68atagen2026-09-24 11:07 UTC208
25d14e49ab68atagen2026-09-24 11:07 UTC209
    pub async fn acknowledge(&self, peer: &str, sequence: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC210
        let peer_url = peer.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC211
        self.write(move |connection| acknowledge_delivery(connection, &peer_url, sequence))
25d14e49ab68atagen2026-09-24 11:07 UTC212
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC213
    }
25d14e49ab68atagen2026-09-24 11:07 UTC214
25d14e49ab68atagen2026-09-24 11:07 UTC215
    pub async fn defer(&self, peer: &str, sequence: u64, error: &str, retry_at: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC216
        let peer_url = peer.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC217
        let message = error.chars().take(512).collect::<String>();
25d14e49ab68atagen2026-09-24 11:07 UTC218
        self.write(move |connection| {
25d14e49ab68atagen2026-09-24 11:07 UTC219
            defer_delivery(connection, &peer_url, sequence, &message, retry_at)
25d14e49ab68atagen2026-09-24 11:07 UTC220
        })
25d14e49ab68atagen2026-09-24 11:07 UTC221
        .await
25d14e49ab68atagen2026-09-24 11:07 UTC222
    }
25d14e49ab68atagen2026-09-24 11:07 UTC223
25d14e49ab68atagen2026-09-24 11:07 UTC224
    pub async fn checkpoint(&self, peer: &str, scope: &str) -> Result<Option<u64>> {
25d14e49ab68atagen2026-09-24 11:07 UTC225
        let peer_url = peer.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC226
        let scope_name = scope.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC227
        self.run(move |connection| read_checkpoint(connection, &peer_url, &scope_name))
25d14e49ab68atagen2026-09-24 11:07 UTC228
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC229
    }
25d14e49ab68atagen2026-09-24 11:07 UTC230
25d14e49ab68atagen2026-09-24 11:07 UTC231
    pub async fn advance(&self, peer: &str, scope: &str, until: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC232
        let peer_url = peer.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC233
        let scope_name = scope.to_owned();
25d14e49ab68atagen2026-09-24 11:07 UTC234
        self.write(move |connection| write_checkpoint(connection, &peer_url, &scope_name, until))
25d14e49ab68atagen2026-09-24 11:07 UTC235
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC236
    }
25d14e49ab68atagen2026-09-24 11:07 UTC237
25d14e49ab68atagen2026-09-24 11:07 UTC238
    pub async fn repositories(&self, limit: usize) -> Result<Vec<(String, String)>> {
25d14e49ab68atagen2026-09-24 11:07 UTC239
        self.run(move |connection| list_repositories(connection, limit))
25d14e49ab68atagen2026-09-24 11:07 UTC240
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC241
    }
25d14e49ab68atagen2026-09-24 11:07 UTC242
25d14e49ab68atagen2026-09-24 11:07 UTC243
    pub async fn healthy(&self) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC244
        self.run(|connection| Ok(connection.query_row("SELECT 1", [], |_| Ok(()))?))
25d14e49ab68atagen2026-09-24 11:07 UTC245
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC246
            .is_ok()
25d14e49ab68atagen2026-09-24 11:07 UTC247
    }
25d14e49ab68atagen2026-09-24 11:07 UTC248
25d14e49ab68atagen2026-09-24 11:07 UTC249
    pub async fn save_locked(
25d14e49ab68atagen2026-09-24 11:07 UTC250
        &self,
25d14e49ab68atagen2026-09-24 11:07 UTC251
        event: Event,
25d14e49ab68atagen2026-09-24 11:07 UTC252
        _write: &MutexGuard<'_, ()>,
25d14e49ab68atagen2026-09-24 11:07 UTC253
    ) -> Result<SaveEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC254
        self.run(move |connection| save_event(connection, &event))
25d14e49ab68atagen2026-09-24 11:07 UTC255
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC256
    }
25d14e49ab68atagen2026-09-24 11:07 UTC257
25d14e49ab68atagen2026-09-24 11:07 UTC258
    pub async fn status(&self, event_id: EventId) -> Result<DatabaseEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC259
        self.run(move |connection| event_status(connection, &event_id))
25d14e49ab68atagen2026-09-24 11:07 UTC260
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC261
    }
25d14e49ab68atagen2026-09-24 11:07 UTC262
25d14e49ab68atagen2026-09-24 11:07 UTC263
    pub async fn event(&self, event_id: EventId) -> Result<Option<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC264
        self.run(move |connection| event_by_id(connection, &event_id))
25d14e49ab68atagen2026-09-24 11:07 UTC265
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC266
    }
25d14e49ab68atagen2026-09-24 11:07 UTC267
25d14e49ab68atagen2026-09-24 11:07 UTC268
    pub async fn count(&self, filter: Filter) -> Result<usize> {
25d14e49ab68atagen2026-09-24 11:07 UTC269
        self.run(move |connection| Ok(query_events(connection, &filter, usize::MAX)?.len()))
25d14e49ab68atagen2026-09-24 11:07 UTC270
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC271
    }
25d14e49ab68atagen2026-09-24 11:07 UTC272
25d14e49ab68atagen2026-09-24 11:07 UTC273
    pub async fn query(&self, filter: Filter, max_bytes: usize) -> Result<BTreeSet<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC274
        self.run(move |connection| query_events(connection, &filter, max_bytes))
25d14e49ab68atagen2026-09-24 11:07 UTC275
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC276
    }
25d14e49ab68atagen2026-09-24 11:07 UTC277
25d14e49ab68atagen2026-09-24 11:07 UTC278
    pub async fn replace_discovered_locked(
25d14e49ab68atagen2026-09-24 11:07 UTC279
        &self,
25d14e49ab68atagen2026-09-24 11:07 UTC280
        signer: PublicKey,
25d14e49ab68atagen2026-09-24 11:07 UTC281
        urls: Vec<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC282
        max: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC283
        _write: &MutexGuard<'_, ()>,
25d14e49ab68atagen2026-09-24 11:07 UTC284
    ) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC285
        let signer_bytes = signer.as_bytes().to_vec();
25d14e49ab68atagen2026-09-24 11:07 UTC286
        self.run(move |connection| replace_discovered(connection, &signer_bytes, urls, max))
25d14e49ab68atagen2026-09-24 11:07 UTC287
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC288
    }
25d14e49ab68atagen2026-09-24 11:07 UTC289
25d14e49ab68atagen2026-09-24 11:07 UTC290
    pub async fn discovered(&self, limit: usize) -> Result<Vec<String>> {
25d14e49ab68atagen2026-09-24 11:07 UTC291
        self.run(move |connection| list_discovered(connection, limit))
25d14e49ab68atagen2026-09-24 11:07 UTC292
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC293
    }
25d14e49ab68atagen2026-09-24 11:07 UTC294
25d14e49ab68atagen2026-09-24 11:07 UTC295
    pub async fn forget_discovered(&self, urls: Vec<String>) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC296
        self.write(move |connection| forget_discovered(connection, urls))
25d14e49ab68atagen2026-09-24 11:07 UTC297
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC298
    }
25d14e49ab68atagen2026-09-24 11:07 UTC299
25d14e49ab68atagen2026-09-24 11:07 UTC300
    async fn write<T, F>(&self, operation: F) -> Result<T>
25d14e49ab68atagen2026-09-24 11:07 UTC301
    where
25d14e49ab68atagen2026-09-24 11:07 UTC302
        T: Send + 'static,
25d14e49ab68atagen2026-09-24 11:07 UTC303
        F: FnOnce(&mut Connection) -> Result<T> + Send + 'static,
25d14e49ab68atagen2026-09-24 11:07 UTC304
    {
25d14e49ab68atagen2026-09-24 11:07 UTC305
        let _write = self.writes.lock().await;
25d14e49ab68atagen2026-09-24 11:07 UTC306
        self.run(operation).await
25d14e49ab68atagen2026-09-24 11:07 UTC307
    }
25d14e49ab68atagen2026-09-24 11:07 UTC308
25d14e49ab68atagen2026-09-24 11:07 UTC309
    async fn run<T, F>(&self, operation: F) -> Result<T>
25d14e49ab68atagen2026-09-24 11:07 UTC310
    where
25d14e49ab68atagen2026-09-24 11:07 UTC311
        T: Send + 'static,
25d14e49ab68atagen2026-09-24 11:07 UTC312
        F: FnOnce(&mut Connection) -> Result<T> + Send + 'static,
25d14e49ab68atagen2026-09-24 11:07 UTC313
    {
25d14e49ab68atagen2026-09-24 11:07 UTC314
        let shared_connection = Arc::clone(&self.connection);
25d14e49ab68atagen2026-09-24 11:07 UTC315
        let mut connection = shared_connection.lock_owned().await;
25d14e49ab68atagen2026-09-24 11:07 UTC316
        spawn_blocking(move || operation(&mut connection))
25d14e49ab68atagen2026-09-24 11:07 UTC317
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC318
            .context("journal task panicked")?
25d14e49ab68atagen2026-09-24 11:07 UTC319
    }
25d14e49ab68atagen2026-09-24 11:07 UTC320
}
25d14e49ab68atagen2026-09-24 11:07 UTC321
25d14e49ab68atagen2026-09-24 11:07 UTC322
fn open_connection(
25d14e49ab68atagen2026-09-24 11:07 UTC323
    path: &Path,
25d14e49ab68atagen2026-09-24 11:07 UTC324
    max_database_bytes: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC325
    max_discovered_peers: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC326
    excluded_peers: &[String],
25d14e49ab68atagen2026-09-24 11:07 UTC327
) -> Result<Connection> {
25d14e49ab68atagen2026-09-24 11:07 UTC328
    let mut connection =
25d14e49ab68atagen2026-09-24 11:07 UTC329
        Connection::open(path).with_context(|| format!("opening journal {}", path.display()))?;
25d14e49ab68atagen2026-09-24 11:07 UTC330
    configure(&connection, max_database_bytes)?;
25d14e49ab68atagen2026-09-24 11:07 UTC331
    migrate(&mut connection, max_discovered_peers, excluded_peers)?;
25d14e49ab68atagen2026-09-24 11:07 UTC332
    Ok(connection)
25d14e49ab68atagen2026-09-24 11:07 UTC333
}
25d14e49ab68atagen2026-09-24 11:07 UTC334
25d14e49ab68atagen2026-09-24 11:07 UTC335
fn pending_deliveries(connection: &Connection, peer: &str, limit: usize) -> Result<Vec<Delivery>> {
25d14e49ab68atagen2026-09-24 11:07 UTC336
    if limit == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC337
        return Ok(Vec::new());
25d14e49ab68atagen2026-09-24 11:07 UTC338
    }
25d14e49ab68atagen2026-09-24 11:07 UTC339
25d14e49ab68atagen2026-09-24 11:07 UTC340
    let retry_limit = limit / 2;
25d14e49ab68atagen2026-09-24 11:07 UTC341
    let mut pending = if retry_limit == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC342
        Vec::new()
25d14e49ab68atagen2026-09-24 11:07 UTC343
    } else {
25d14e49ab68atagen2026-09-24 11:07 UTC344
        read_deliveries(
25d14e49ab68atagen2026-09-24 11:07 UTC345
            connection,
25d14e49ab68atagen2026-09-24 11:07 UTC346
            DUE_DELIVERIES,
25d14e49ab68atagen2026-09-24 11:07 UTC347
            params![peer, unix_seconds(), integer(retry_limit)?],
25d14e49ab68atagen2026-09-24 11:07 UTC348
        )?
25d14e49ab68atagen2026-09-24 11:07 UTC349
    };
25d14e49ab68atagen2026-09-24 11:07 UTC350
25d14e49ab68atagen2026-09-24 11:07 UTC351
    let remaining = limit - pending.len();
25d14e49ab68atagen2026-09-24 11:07 UTC352
    if remaining == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC353
        return Ok(pending);
25d14e49ab68atagen2026-09-24 11:07 UTC354
    }
25d14e49ab68atagen2026-09-24 11:07 UTC355
25d14e49ab68atagen2026-09-24 11:07 UTC356
    let delivered = read_delivered(connection, peer)?;
25d14e49ab68atagen2026-09-24 11:07 UTC357
    pending.extend(read_deliveries(
25d14e49ab68atagen2026-09-24 11:07 UTC358
        connection,
25d14e49ab68atagen2026-09-24 11:07 UTC359
        NEW_DELIVERIES,
25d14e49ab68atagen2026-09-24 11:07 UTC360
        params![delivered, integer(remaining)?],
25d14e49ab68atagen2026-09-24 11:07 UTC361
    )?);
25d14e49ab68atagen2026-09-24 11:07 UTC362
    Ok(pending)
25d14e49ab68atagen2026-09-24 11:07 UTC363
}
25d14e49ab68atagen2026-09-24 11:07 UTC364
25d14e49ab68atagen2026-09-24 11:07 UTC365
fn read_deliveries<P>(connection: &Connection, sql: &str, parameters: P) -> Result<Vec<Delivery>>
25d14e49ab68atagen2026-09-24 11:07 UTC366
where
25d14e49ab68atagen2026-09-24 11:07 UTC367
    P: Params,
25d14e49ab68atagen2026-09-24 11:07 UTC368
{
25d14e49ab68atagen2026-09-24 11:07 UTC369
    let mut statement = connection.prepare(sql)?;
25d14e49ab68atagen2026-09-24 11:07 UTC370
    let mut rows = statement.query(parameters)?;
25d14e49ab68atagen2026-09-24 11:07 UTC371
    let mut deliveries = Vec::new();
25d14e49ab68atagen2026-09-24 11:07 UTC372
25d14e49ab68atagen2026-09-24 11:07 UTC373
    while let Some(row) = rows.next()? {
25d14e49ab68atagen2026-09-24 11:07 UTC374
        let sequence = row.get::<_, i64>(0)?;
25d14e49ab68atagen2026-09-24 11:07 UTC375
        let event_id = row.get::<_, Vec<u8>>(1)?;
25d14e49ab68atagen2026-09-24 11:07 UTC376
        deliveries.push((
25d14e49ab68atagen2026-09-24 11:07 UTC377
            u64::try_from(sequence).context("negative delivery sequence")?,
25d14e49ab68atagen2026-09-24 11:07 UTC378
            EventId::from_slice(&event_id)
25d14e49ab68atagen2026-09-24 11:07 UTC379
                .map_err(|error| anyhow::anyhow!("invalid event id {error}"))?,
25d14e49ab68atagen2026-09-24 11:07 UTC380
        ));
25d14e49ab68atagen2026-09-24 11:07 UTC381
    }
25d14e49ab68atagen2026-09-24 11:07 UTC382
25d14e49ab68atagen2026-09-24 11:07 UTC383
    Ok(deliveries)
25d14e49ab68atagen2026-09-24 11:07 UTC384
}
25d14e49ab68atagen2026-09-24 11:07 UTC385
25d14e49ab68atagen2026-09-24 11:07 UTC386
fn read_delivered(connection: &Connection, peer: &str) -> Result<i64> {
25d14e49ab68atagen2026-09-24 11:07 UTC387
    Ok(connection
25d14e49ab68atagen2026-09-24 11:07 UTC388
        .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC389
            "SELECT delivered FROM vineyard_peers WHERE peer = ?1",
25d14e49ab68atagen2026-09-24 11:07 UTC390
            [peer],
25d14e49ab68atagen2026-09-24 11:07 UTC391
            |row| row.get(0),
25d14e49ab68atagen2026-09-24 11:07 UTC392
        )
25d14e49ab68atagen2026-09-24 11:07 UTC393
        .optional()?
25d14e49ab68atagen2026-09-24 11:07 UTC394
        .unwrap_or(0))
25d14e49ab68atagen2026-09-24 11:07 UTC395
}
25d14e49ab68atagen2026-09-24 11:07 UTC396
25d14e49ab68atagen2026-09-24 11:07 UTC397
fn acknowledge_delivery(connection: &mut Connection, peer: &str, sequence: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC398
    let stored_sequence = integer(sequence)?;
25d14e49ab68atagen2026-09-24 11:07 UTC399
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC400
    transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC401
        "DELETE FROM vineyard_retries WHERE peer = ?1 AND sequence = ?2",
25d14e49ab68atagen2026-09-24 11:07 UTC402
        params![peer, stored_sequence],
25d14e49ab68atagen2026-09-24 11:07 UTC403
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC404
    advance_delivery(&transaction, peer, stored_sequence)?;
25d14e49ab68atagen2026-09-24 11:07 UTC405
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC406
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC407
}
25d14e49ab68atagen2026-09-24 11:07 UTC408
25d14e49ab68atagen2026-09-24 11:07 UTC409
fn defer_delivery(
25d14e49ab68atagen2026-09-24 11:07 UTC410
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC411
    peer: &str,
25d14e49ab68atagen2026-09-24 11:07 UTC412
    sequence: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC413
    error: &str,
25d14e49ab68atagen2026-09-24 11:07 UTC414
    retry_at: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC415
) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC416
    let stored_sequence = integer(sequence)?;
25d14e49ab68atagen2026-09-24 11:07 UTC417
    let stored_retry_at = integer(retry_at)?;
25d14e49ab68atagen2026-09-24 11:07 UTC418
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC419
    advance_delivery(&transaction, peer, stored_sequence)?;
25d14e49ab68atagen2026-09-24 11:07 UTC420
    transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC421
        "
25d14e49ab68atagen2026-09-24 11:07 UTC422
            INSERT INTO vineyard_retries(peer, sequence, retry_at, error)
25d14e49ab68atagen2026-09-24 11:07 UTC423
            VALUES (?1, ?2, ?3, ?4)
25d14e49ab68atagen2026-09-24 11:07 UTC424
            ON CONFLICT(peer, sequence) DO UPDATE SET
25d14e49ab68atagen2026-09-24 11:07 UTC425
                retry_at = excluded.retry_at,
25d14e49ab68atagen2026-09-24 11:07 UTC426
                error = excluded.error
25d14e49ab68atagen2026-09-24 11:07 UTC427
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC428
        params![peer, stored_sequence, stored_retry_at, error],
25d14e49ab68atagen2026-09-24 11:07 UTC429
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC430
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC431
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC432
}
25d14e49ab68atagen2026-09-24 11:07 UTC433
25d14e49ab68atagen2026-09-24 11:07 UTC434
fn read_checkpoint(connection: &Connection, peer: &str, scope: &str) -> Result<Option<u64>> {
25d14e49ab68atagen2026-09-24 11:07 UTC435
    let checkpoint = connection
25d14e49ab68atagen2026-09-24 11:07 UTC436
        .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC437
            "SELECT until FROM vineyard_checkpoints WHERE peer = ?1 AND scope = ?2",
25d14e49ab68atagen2026-09-24 11:07 UTC438
            params![peer, scope],
25d14e49ab68atagen2026-09-24 11:07 UTC439
            |row| row.get::<_, i64>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC440
        )
25d14e49ab68atagen2026-09-24 11:07 UTC441
        .optional()?;
25d14e49ab68atagen2026-09-24 11:07 UTC442
25d14e49ab68atagen2026-09-24 11:07 UTC443
    checkpoint
25d14e49ab68atagen2026-09-24 11:07 UTC444
        .map(|value| u64::try_from(value).context("negative checkpoint"))
25d14e49ab68atagen2026-09-24 11:07 UTC445
        .transpose()
25d14e49ab68atagen2026-09-24 11:07 UTC446
}
25d14e49ab68atagen2026-09-24 11:07 UTC447
25d14e49ab68atagen2026-09-24 11:07 UTC448
fn write_checkpoint(connection: &Connection, peer: &str, scope: &str, until: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC449
    connection.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC450
        "
25d14e49ab68atagen2026-09-24 11:07 UTC451
            INSERT INTO vineyard_checkpoints(peer, scope, until)
25d14e49ab68atagen2026-09-24 11:07 UTC452
            VALUES (?1, ?2, ?3)
25d14e49ab68atagen2026-09-24 11:07 UTC453
            ON CONFLICT(peer, scope) DO UPDATE SET
25d14e49ab68atagen2026-09-24 11:07 UTC454
                until = max(vineyard_checkpoints.until, excluded.until)
25d14e49ab68atagen2026-09-24 11:07 UTC455
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC456
        params![peer, scope, integer(until)?],
25d14e49ab68atagen2026-09-24 11:07 UTC457
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC458
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC459
}
25d14e49ab68atagen2026-09-24 11:07 UTC460
25d14e49ab68atagen2026-09-24 11:07 UTC461
fn list_repositories(connection: &Connection, limit: usize) -> Result<Vec<(String, String)>> {
25d14e49ab68atagen2026-09-24 11:07 UTC462
    let mut statement = connection.prepare(REPOSITORIES)?;
25d14e49ab68atagen2026-09-24 11:07 UTC463
    let mut rows = statement.query([])?;
25d14e49ab68atagen2026-09-24 11:07 UTC464
    let mut repositories = Vec::new();
25d14e49ab68atagen2026-09-24 11:07 UTC465
    let mut seen = BTreeSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC466
    while let Some(row) = rows.next()? {
25d14e49ab68atagen2026-09-24 11:07 UTC467
        let tags = serde_json::from_str::<Tags>(&row.get::<_, String>(2)?)?;
25d14e49ab68atagen2026-09-24 11:07 UTC468
        if tags
25d14e49ab68atagen2026-09-24 11:07 UTC469
            .expiration()
25d14e49ab68atagen2026-09-24 11:07 UTC470
            .is_some_and(|expiration| expiration < Timestamp::now())
25d14e49ab68atagen2026-09-24 11:07 UTC471
        {
25d14e49ab68atagen2026-09-24 11:07 UTC472
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC473
        }
25d14e49ab68atagen2026-09-24 11:07 UTC474
        let repository = (row.get(0)?, row.get(1)?);
25d14e49ab68atagen2026-09-24 11:07 UTC475
        if !seen.insert(repository.clone()) {
25d14e49ab68atagen2026-09-24 11:07 UTC476
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC477
        }
25d14e49ab68atagen2026-09-24 11:07 UTC478
        repositories.push(repository);
25d14e49ab68atagen2026-09-24 11:07 UTC479
        if repositories.len() == limit {
25d14e49ab68atagen2026-09-24 11:07 UTC480
            break;
25d14e49ab68atagen2026-09-24 11:07 UTC481
        }
25d14e49ab68atagen2026-09-24 11:07 UTC482
    }
25d14e49ab68atagen2026-09-24 11:07 UTC483
    Ok(repositories)
25d14e49ab68atagen2026-09-24 11:07 UTC484
}
25d14e49ab68atagen2026-09-24 11:07 UTC485
25d14e49ab68atagen2026-09-24 11:07 UTC486
fn event_status(connection: &Connection, event_id: &EventId) -> Result<DatabaseEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC487
    let deleted = connection.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC488
        "SELECT EXISTS(SELECT 1 FROM deleted_ids WHERE event_id = ?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC489
        [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC490
        |row| row.get::<_, bool>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC491
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC492
    if deleted {
25d14e49ab68atagen2026-09-24 11:07 UTC493
        return Ok(DatabaseEventStatus::Deleted);
25d14e49ab68atagen2026-09-24 11:07 UTC494
    }
25d14e49ab68atagen2026-09-24 11:07 UTC495
    let saved = connection.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC496
        "SELECT EXISTS(SELECT 1 FROM events WHERE id = ?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC497
        [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC498
        |row| row.get::<_, bool>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC499
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC500
    Ok(if saved {
25d14e49ab68atagen2026-09-24 11:07 UTC501
        DatabaseEventStatus::Saved
25d14e49ab68atagen2026-09-24 11:07 UTC502
    } else {
25d14e49ab68atagen2026-09-24 11:07 UTC503
        DatabaseEventStatus::NotExistent
25d14e49ab68atagen2026-09-24 11:07 UTC504
    })
25d14e49ab68atagen2026-09-24 11:07 UTC505
}
25d14e49ab68atagen2026-09-24 11:07 UTC506
25d14e49ab68atagen2026-09-24 11:07 UTC507
fn event_by_id(connection: &Connection, event_id: &EventId) -> Result<Option<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC508
    connection
25d14e49ab68atagen2026-09-24 11:07 UTC509
        .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC510
            "
25d14e49ab68atagen2026-09-24 11:07 UTC511
            SELECT id, pubkey, created_at, kind, content, tags, sig
25d14e49ab68atagen2026-09-24 11:07 UTC512
            FROM events
25d14e49ab68atagen2026-09-24 11:07 UTC513
            WHERE id = ?1
25d14e49ab68atagen2026-09-24 11:07 UTC514
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC515
            [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC516
            StoredEvent::read,
25d14e49ab68atagen2026-09-24 11:07 UTC517
        )
25d14e49ab68atagen2026-09-24 11:07 UTC518
        .optional()?
25d14e49ab68atagen2026-09-24 11:07 UTC519
        .map(StoredEvent::into_event)
25d14e49ab68atagen2026-09-24 11:07 UTC520
        .transpose()
25d14e49ab68atagen2026-09-24 11:07 UTC521
}
25d14e49ab68atagen2026-09-24 11:07 UTC522
25d14e49ab68atagen2026-09-24 11:07 UTC523
fn save_event(connection: &mut Connection, event: &Event) -> Result<SaveEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC524
    if event.kind.is_ephemeral() {
25d14e49ab68atagen2026-09-24 11:07 UTC525
        return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral));
25d14e49ab68atagen2026-09-24 11:07 UTC526
    }
25d14e49ab68atagen2026-09-24 11:07 UTC527
25d14e49ab68atagen2026-09-24 11:07 UTC528
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC529
    if event_exists(&transaction, &event.id)? {
25d14e49ab68atagen2026-09-24 11:07 UTC530
        return Ok(SaveEventStatus::Rejected(RejectedReason::Duplicate));
25d14e49ab68atagen2026-09-24 11:07 UTC531
    }
25d14e49ab68atagen2026-09-24 11:07 UTC532
    if event_deleted(&transaction, &event.id)? || coordinate_deleted(&transaction, event)? {
25d14e49ab68atagen2026-09-24 11:07 UTC533
        return Ok(SaveEventStatus::Rejected(RejectedReason::Deleted));
25d14e49ab68atagen2026-09-24 11:07 UTC534
    }
25d14e49ab68atagen2026-09-24 11:07 UTC535
    if replacement_rejected(&transaction, event)? {
25d14e49ab68atagen2026-09-24 11:07 UTC536
        return Ok(SaveEventStatus::Rejected(RejectedReason::Replaced));
25d14e49ab68atagen2026-09-24 11:07 UTC537
    }
25d14e49ab68atagen2026-09-24 11:07 UTC538
    if event.kind == Kind::EventDeletion && !apply_deletion(&transaction, event)? {
25d14e49ab68atagen2026-09-24 11:07 UTC539
        return Ok(SaveEventStatus::Rejected(RejectedReason::InvalidDelete));
25d14e49ab68atagen2026-09-24 11:07 UTC540
    }
25d14e49ab68atagen2026-09-24 11:07 UTC541
25d14e49ab68atagen2026-09-24 11:07 UTC542
    transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC543
        "
25d14e49ab68atagen2026-09-24 11:07 UTC544
        INSERT INTO events(id, pubkey, created_at, kind, content, tags, sig)
25d14e49ab68atagen2026-09-24 11:07 UTC545
        VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
25d14e49ab68atagen2026-09-24 11:07 UTC546
        ",
25d14e49ab68atagen2026-09-24 11:07 UTC547
        params![
25d14e49ab68atagen2026-09-24 11:07 UTC548
            event.id.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC549
            event.pubkey.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC550
            integer(event.created_at.as_secs())?,
25d14e49ab68atagen2026-09-24 11:07 UTC551
            i64::from(event.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC552
            &event.content,
25d14e49ab68atagen2026-09-24 11:07 UTC553
            serde_json::to_string(&event.tags)?,
25d14e49ab68atagen2026-09-24 11:07 UTC554
            event.sig.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC555
        ],
25d14e49ab68atagen2026-09-24 11:07 UTC556
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC557
    for tag in event.tags.iter() {
25d14e49ab68atagen2026-09-24 11:07 UTC558
        let (Some(name), Some(value)) = (tag.single_letter_tag(), tag.content()) else {
25d14e49ab68atagen2026-09-24 11:07 UTC559
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC560
        };
25d14e49ab68atagen2026-09-24 11:07 UTC561
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC562
            "
25d14e49ab68atagen2026-09-24 11:07 UTC563
            INSERT OR IGNORE INTO event_tags(event_id, tag_name, tag_value)
25d14e49ab68atagen2026-09-24 11:07 UTC564
            VALUES (?1, ?2, ?3)
25d14e49ab68atagen2026-09-24 11:07 UTC565
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC566
            params![event.id.as_bytes().as_slice(), name.to_string(), value],
25d14e49ab68atagen2026-09-24 11:07 UTC567
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC568
    }
25d14e49ab68atagen2026-09-24 11:07 UTC569
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC570
    Ok(SaveEventStatus::Success)
25d14e49ab68atagen2026-09-24 11:07 UTC571
}
25d14e49ab68atagen2026-09-24 11:07 UTC572
25d14e49ab68atagen2026-09-24 11:07 UTC573
fn event_exists(transaction: &Transaction<'_>, event_id: &EventId) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC574
    Ok(transaction.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC575
        "SELECT EXISTS(SELECT 1 FROM events WHERE id = ?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC576
        [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC577
        |row| row.get(0),
25d14e49ab68atagen2026-09-24 11:07 UTC578
    )?)
25d14e49ab68atagen2026-09-24 11:07 UTC579
}
25d14e49ab68atagen2026-09-24 11:07 UTC580
25d14e49ab68atagen2026-09-24 11:07 UTC581
fn event_deleted(transaction: &Transaction<'_>, event_id: &EventId) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC582
    Ok(transaction.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC583
        "SELECT EXISTS(SELECT 1 FROM deleted_ids WHERE event_id = ?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC584
        [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC585
        |row| row.get(0),
25d14e49ab68atagen2026-09-24 11:07 UTC586
    )?)
25d14e49ab68atagen2026-09-24 11:07 UTC587
}
25d14e49ab68atagen2026-09-24 11:07 UTC588
25d14e49ab68atagen2026-09-24 11:07 UTC589
fn coordinate_deleted(transaction: &Transaction<'_>, event: &Event) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC590
    let Some(coordinate) = event.coordinate() else {
25d14e49ab68atagen2026-09-24 11:07 UTC591
        return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC592
    };
25d14e49ab68atagen2026-09-24 11:07 UTC593
    let deleted_at = transaction
25d14e49ab68atagen2026-09-24 11:07 UTC594
        .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC595
            "
25d14e49ab68atagen2026-09-24 11:07 UTC596
            SELECT deleted_at
25d14e49ab68atagen2026-09-24 11:07 UTC597
            FROM deleted_coordinates
25d14e49ab68atagen2026-09-24 11:07 UTC598
            WHERE pubkey = ?1 AND kind = ?2 AND identifier = ?3
25d14e49ab68atagen2026-09-24 11:07 UTC599
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC600
            params![
25d14e49ab68atagen2026-09-24 11:07 UTC601
                coordinate.public_key.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC602
                i64::from(coordinate.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC603
                coordinate.identifier,
25d14e49ab68atagen2026-09-24 11:07 UTC604
            ],
25d14e49ab68atagen2026-09-24 11:07 UTC605
            |row| row.get::<_, i64>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC606
        )
25d14e49ab68atagen2026-09-24 11:07 UTC607
        .optional()?;
25d14e49ab68atagen2026-09-24 11:07 UTC608
    Ok(deleted_at.is_some_and(|stored_deleted_at| {
25d14e49ab68atagen2026-09-24 11:07 UTC609
        u64::try_from(stored_deleted_at).map_or(true, |value| event.created_at.as_secs() <= value)
25d14e49ab68atagen2026-09-24 11:07 UTC610
    }))
25d14e49ab68atagen2026-09-24 11:07 UTC611
}
25d14e49ab68atagen2026-09-24 11:07 UTC612
25d14e49ab68atagen2026-09-24 11:07 UTC613
fn replacement_rejected(transaction: &Transaction<'_>, event: &Event) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC614
    let existing = if event.kind.is_replaceable() {
25d14e49ab68atagen2026-09-24 11:07 UTC615
        transaction
25d14e49ab68atagen2026-09-24 11:07 UTC616
            .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC617
                "
25d14e49ab68atagen2026-09-24 11:07 UTC618
                SELECT id, created_at
25d14e49ab68atagen2026-09-24 11:07 UTC619
                FROM events
25d14e49ab68atagen2026-09-24 11:07 UTC620
                WHERE pubkey = ?1 AND kind = ?2
25d14e49ab68atagen2026-09-24 11:07 UTC621
                LIMIT 1
25d14e49ab68atagen2026-09-24 11:07 UTC622
                ",
25d14e49ab68atagen2026-09-24 11:07 UTC623
                params![
25d14e49ab68atagen2026-09-24 11:07 UTC624
                    event.pubkey.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC625
                    i64::from(event.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC626
                ],
25d14e49ab68atagen2026-09-24 11:07 UTC627
                |row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, i64>(1)?)),
25d14e49ab68atagen2026-09-24 11:07 UTC628
            )
25d14e49ab68atagen2026-09-24 11:07 UTC629
            .optional()?
25d14e49ab68atagen2026-09-24 11:07 UTC630
    } else if event.kind.is_addressable() {
25d14e49ab68atagen2026-09-24 11:07 UTC631
        let Some(identifier) = event.tags.identifier() else {
25d14e49ab68atagen2026-09-24 11:07 UTC632
            return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC633
        };
25d14e49ab68atagen2026-09-24 11:07 UTC634
        transaction
25d14e49ab68atagen2026-09-24 11:07 UTC635
            .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC636
                "
25d14e49ab68atagen2026-09-24 11:07 UTC637
                SELECT event.id, event.created_at
25d14e49ab68atagen2026-09-24 11:07 UTC638
                FROM events AS event
25d14e49ab68atagen2026-09-24 11:07 UTC639
                JOIN event_tags AS tag ON tag.event_id = event.id
25d14e49ab68atagen2026-09-24 11:07 UTC640
                WHERE event.pubkey = ?1
25d14e49ab68atagen2026-09-24 11:07 UTC641
                  AND event.kind = ?2
25d14e49ab68atagen2026-09-24 11:07 UTC642
                  AND tag.tag_name = 'd'
25d14e49ab68atagen2026-09-24 11:07 UTC643
                  AND tag.tag_value = ?3
25d14e49ab68atagen2026-09-24 11:07 UTC644
                LIMIT 1
25d14e49ab68atagen2026-09-24 11:07 UTC645
                ",
25d14e49ab68atagen2026-09-24 11:07 UTC646
                params![
25d14e49ab68atagen2026-09-24 11:07 UTC647
                    event.pubkey.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC648
                    i64::from(event.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC649
                    identifier,
25d14e49ab68atagen2026-09-24 11:07 UTC650
                ],
25d14e49ab68atagen2026-09-24 11:07 UTC651
                |row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, i64>(1)?)),
25d14e49ab68atagen2026-09-24 11:07 UTC652
            )
25d14e49ab68atagen2026-09-24 11:07 UTC653
            .optional()?
25d14e49ab68atagen2026-09-24 11:07 UTC654
    } else {
25d14e49ab68atagen2026-09-24 11:07 UTC655
        return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC656
    };
25d14e49ab68atagen2026-09-24 11:07 UTC657
25d14e49ab68atagen2026-09-24 11:07 UTC658
    let Some((stored_id, stored_at)) = existing else {
25d14e49ab68atagen2026-09-24 11:07 UTC659
        return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC660
    };
25d14e49ab68atagen2026-09-24 11:07 UTC661
    let incoming_at = integer(event.created_at.as_secs())?;
25d14e49ab68atagen2026-09-24 11:07 UTC662
    match stored_at.cmp(&incoming_at) {
25d14e49ab68atagen2026-09-24 11:07 UTC663
        Ordering::Greater => Ok(true),
25d14e49ab68atagen2026-09-24 11:07 UTC664
        Ordering::Equal if stored_id.as_slice() < event.id.as_bytes().as_slice() => Ok(true),
25d14e49ab68atagen2026-09-24 11:07 UTC665
        Ordering::Equal | Ordering::Less => {
25d14e49ab68atagen2026-09-24 11:07 UTC666
            transaction.execute("DELETE FROM events WHERE id = ?1", [stored_id])?;
25d14e49ab68atagen2026-09-24 11:07 UTC667
            Ok(false)
25d14e49ab68atagen2026-09-24 11:07 UTC668
        }
25d14e49ab68atagen2026-09-24 11:07 UTC669
    }
25d14e49ab68atagen2026-09-24 11:07 UTC670
}
25d14e49ab68atagen2026-09-24 11:07 UTC671
25d14e49ab68atagen2026-09-24 11:07 UTC672
fn apply_deletion(transaction: &Transaction<'_>, event: &Event) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC673
    for event_id in event.tags.event_ids() {
25d14e49ab68atagen2026-09-24 11:07 UTC674
        let owner = transaction
25d14e49ab68atagen2026-09-24 11:07 UTC675
            .query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC676
                "SELECT pubkey FROM events WHERE id = ?1",
25d14e49ab68atagen2026-09-24 11:07 UTC677
                [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC678
                |row| row.get::<_, Vec<u8>>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC679
            )
25d14e49ab68atagen2026-09-24 11:07 UTC680
            .optional()?;
25d14e49ab68atagen2026-09-24 11:07 UTC681
        if owner.is_some_and(|saved_owner| saved_owner.as_slice() != event.pubkey.as_bytes()) {
25d14e49ab68atagen2026-09-24 11:07 UTC682
            return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC683
        }
25d14e49ab68atagen2026-09-24 11:07 UTC684
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC685
            "INSERT OR IGNORE INTO deleted_ids(event_id) VALUES (?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC686
            [event_id.as_bytes().as_slice()],
25d14e49ab68atagen2026-09-24 11:07 UTC687
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC688
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC689
            "DELETE FROM events WHERE id = ?1 AND pubkey = ?2",
25d14e49ab68atagen2026-09-24 11:07 UTC690
            params![
25d14e49ab68atagen2026-09-24 11:07 UTC691
                event_id.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC692
                event.pubkey.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC693
            ],
25d14e49ab68atagen2026-09-24 11:07 UTC694
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC695
    }
25d14e49ab68atagen2026-09-24 11:07 UTC696
25d14e49ab68atagen2026-09-24 11:07 UTC697
    for coordinate in event.tags.coordinates() {
25d14e49ab68atagen2026-09-24 11:07 UTC698
        if coordinate.public_key != event.pubkey {
25d14e49ab68atagen2026-09-24 11:07 UTC699
            return Ok(false);
25d14e49ab68atagen2026-09-24 11:07 UTC700
        }
25d14e49ab68atagen2026-09-24 11:07 UTC701
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC702
            "
25d14e49ab68atagen2026-09-24 11:07 UTC703
            INSERT INTO deleted_coordinates(pubkey, kind, identifier, deleted_at)
25d14e49ab68atagen2026-09-24 11:07 UTC704
            VALUES (?1, ?2, ?3, ?4)
25d14e49ab68atagen2026-09-24 11:07 UTC705
            ON CONFLICT(pubkey, kind, identifier) DO UPDATE SET
25d14e49ab68atagen2026-09-24 11:07 UTC706
                deleted_at = max(deleted_coordinates.deleted_at, excluded.deleted_at)
25d14e49ab68atagen2026-09-24 11:07 UTC707
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC708
            params![
25d14e49ab68atagen2026-09-24 11:07 UTC709
                coordinate.public_key.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC710
                i64::from(coordinate.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC711
                &coordinate.identifier,
25d14e49ab68atagen2026-09-24 11:07 UTC712
                integer(event.created_at.as_secs())?,
25d14e49ab68atagen2026-09-24 11:07 UTC713
            ],
25d14e49ab68atagen2026-09-24 11:07 UTC714
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC715
        if coordinate.kind.is_replaceable() {
25d14e49ab68atagen2026-09-24 11:07 UTC716
            transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC717
                "
25d14e49ab68atagen2026-09-24 11:07 UTC718
                DELETE FROM events
25d14e49ab68atagen2026-09-24 11:07 UTC719
                WHERE pubkey = ?1 AND kind = ?2 AND created_at <= ?3
25d14e49ab68atagen2026-09-24 11:07 UTC720
                ",
25d14e49ab68atagen2026-09-24 11:07 UTC721
                params![
25d14e49ab68atagen2026-09-24 11:07 UTC722
                    coordinate.public_key.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC723
                    i64::from(coordinate.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC724
                    integer(event.created_at.as_secs())?,
25d14e49ab68atagen2026-09-24 11:07 UTC725
                ],
25d14e49ab68atagen2026-09-24 11:07 UTC726
            )?;
25d14e49ab68atagen2026-09-24 11:07 UTC727
        } else {
25d14e49ab68atagen2026-09-24 11:07 UTC728
            transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC729
                "
25d14e49ab68atagen2026-09-24 11:07 UTC730
                DELETE FROM events
25d14e49ab68atagen2026-09-24 11:07 UTC731
                WHERE id IN (
25d14e49ab68atagen2026-09-24 11:07 UTC732
                    SELECT stored.id
25d14e49ab68atagen2026-09-24 11:07 UTC733
                    FROM events AS stored
25d14e49ab68atagen2026-09-24 11:07 UTC734
                    JOIN event_tags AS tag ON tag.event_id = stored.id
25d14e49ab68atagen2026-09-24 11:07 UTC735
                    WHERE stored.pubkey = ?1
25d14e49ab68atagen2026-09-24 11:07 UTC736
                      AND stored.kind = ?2
25d14e49ab68atagen2026-09-24 11:07 UTC737
                      AND stored.created_at <= ?3
25d14e49ab68atagen2026-09-24 11:07 UTC738
                      AND tag.tag_name = 'd'
25d14e49ab68atagen2026-09-24 11:07 UTC739
                      AND tag.tag_value = ?4
25d14e49ab68atagen2026-09-24 11:07 UTC740
                )
25d14e49ab68atagen2026-09-24 11:07 UTC741
                ",
25d14e49ab68atagen2026-09-24 11:07 UTC742
                params![
25d14e49ab68atagen2026-09-24 11:07 UTC743
                    coordinate.public_key.as_bytes().as_slice(),
25d14e49ab68atagen2026-09-24 11:07 UTC744
                    i64::from(coordinate.kind.as_u16()),
25d14e49ab68atagen2026-09-24 11:07 UTC745
                    integer(event.created_at.as_secs())?,
25d14e49ab68atagen2026-09-24 11:07 UTC746
                    &coordinate.identifier,
25d14e49ab68atagen2026-09-24 11:07 UTC747
                ],
25d14e49ab68atagen2026-09-24 11:07 UTC748
            )?;
25d14e49ab68atagen2026-09-24 11:07 UTC749
        }
25d14e49ab68atagen2026-09-24 11:07 UTC750
    }
25d14e49ab68atagen2026-09-24 11:07 UTC751
    Ok(true)
25d14e49ab68atagen2026-09-24 11:07 UTC752
}
25d14e49ab68atagen2026-09-24 11:07 UTC753
25d14e49ab68atagen2026-09-24 11:07 UTC754
fn query_events(
25d14e49ab68atagen2026-09-24 11:07 UTC755
    connection: &Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC756
    filter: &Filter,
25d14e49ab68atagen2026-09-24 11:07 UTC757
    max_bytes: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC758
) -> Result<BTreeSet<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC759
    let query = EventQuery::new(filter)?;
25d14e49ab68atagen2026-09-24 11:07 UTC760
    if query.limit == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC761
        return Ok(BTreeSet::new());
25d14e49ab68atagen2026-09-24 11:07 UTC762
    }
25d14e49ab68atagen2026-09-24 11:07 UTC763
    let mut statement = connection.prepare(&query.sql)?;
25d14e49ab68atagen2026-09-24 11:07 UTC764
    let mut rows = statement.query(params_from_iter(query.parameters))?;
25d14e49ab68atagen2026-09-24 11:07 UTC765
    let mut events = BTreeSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC766
    let mut bytes = 0_usize;
25d14e49ab68atagen2026-09-24 11:07 UTC767
25d14e49ab68atagen2026-09-24 11:07 UTC768
    while let Some(row) = rows.next()? {
25d14e49ab68atagen2026-09-24 11:07 UTC769
        let event = StoredEvent::read(row)?.into_event()?;
25d14e49ab68atagen2026-09-24 11:07 UTC770
        if event.is_expired() {
25d14e49ab68atagen2026-09-24 11:07 UTC771
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC772
        }
25d14e49ab68atagen2026-09-24 11:07 UTC773
        bytes = bytes
25d14e49ab68atagen2026-09-24 11:07 UTC774
            .checked_add(event.as_json().len())
25d14e49ab68atagen2026-09-24 11:07 UTC775
            .context("history byte count overflow")?;
25d14e49ab68atagen2026-09-24 11:07 UTC776
        if bytes > max_bytes {
25d14e49ab68atagen2026-09-24 11:07 UTC777
            anyhow::bail!("history byte limit exceeded");
25d14e49ab68atagen2026-09-24 11:07 UTC778
        }
25d14e49ab68atagen2026-09-24 11:07 UTC779
        events.insert(event);
25d14e49ab68atagen2026-09-24 11:07 UTC780
        if events.len() == query.limit {
25d14e49ab68atagen2026-09-24 11:07 UTC781
            break;
25d14e49ab68atagen2026-09-24 11:07 UTC782
        }
25d14e49ab68atagen2026-09-24 11:07 UTC783
    }
25d14e49ab68atagen2026-09-24 11:07 UTC784
25d14e49ab68atagen2026-09-24 11:07 UTC785
    Ok(events)
25d14e49ab68atagen2026-09-24 11:07 UTC786
}
25d14e49ab68atagen2026-09-24 11:07 UTC787
25d14e49ab68atagen2026-09-24 11:07 UTC788
fn replace_discovered(
25d14e49ab68atagen2026-09-24 11:07 UTC789
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC790
    signer: &[u8],
25d14e49ab68atagen2026-09-24 11:07 UTC791
    urls: Vec<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC792
    max: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC793
) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC794
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC795
    transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC796
        "DELETE FROM vineyard_discovered_peers WHERE signer = ?1",
25d14e49ab68atagen2026-09-24 11:07 UTC797
        [signer],
25d14e49ab68atagen2026-09-24 11:07 UTC798
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC799
    let stored_count = transaction.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC800
        "SELECT count(DISTINCT url) FROM vineyard_discovered_peers",
25d14e49ab68atagen2026-09-24 11:07 UTC801
        [],
25d14e49ab68atagen2026-09-24 11:07 UTC802
        |row| row.get::<_, i64>(0),
25d14e49ab68atagen2026-09-24 11:07 UTC803
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC804
    let count = usize::try_from(stored_count).context("negative discovered peer count")?;
25d14e49ab68atagen2026-09-24 11:07 UTC805
25d14e49ab68atagen2026-09-24 11:07 UTC806
    for url in urls.into_iter().take(max.saturating_sub(count)) {
25d14e49ab68atagen2026-09-24 11:07 UTC807
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC808
            "
25d14e49ab68atagen2026-09-24 11:07 UTC809
                INSERT OR IGNORE INTO vineyard_discovered_peers(signer, url, discovered_at)
25d14e49ab68atagen2026-09-24 11:07 UTC810
                VALUES (?1, ?2, unixepoch())
25d14e49ab68atagen2026-09-24 11:07 UTC811
                ",
25d14e49ab68atagen2026-09-24 11:07 UTC812
            params![signer, url],
25d14e49ab68atagen2026-09-24 11:07 UTC813
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC814
    }
25d14e49ab68atagen2026-09-24 11:07 UTC815
25d14e49ab68atagen2026-09-24 11:07 UTC816
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC817
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC818
}
25d14e49ab68atagen2026-09-24 11:07 UTC819
25d14e49ab68atagen2026-09-24 11:07 UTC820
fn list_discovered(connection: &Connection, limit: usize) -> Result<Vec<String>> {
25d14e49ab68atagen2026-09-24 11:07 UTC821
    let mut statement = connection
25d14e49ab68atagen2026-09-24 11:07 UTC822
        .prepare("SELECT DISTINCT url FROM vineyard_discovered_peers ORDER BY url LIMIT ?1")?;
25d14e49ab68atagen2026-09-24 11:07 UTC823
    let mut rows = statement.query([integer(limit)?])?;
25d14e49ab68atagen2026-09-24 11:07 UTC824
    let mut urls = Vec::new();
25d14e49ab68atagen2026-09-24 11:07 UTC825
    while let Some(row) = rows.next()? {
25d14e49ab68atagen2026-09-24 11:07 UTC826
        urls.push(row.get(0)?);
25d14e49ab68atagen2026-09-24 11:07 UTC827
    }
25d14e49ab68atagen2026-09-24 11:07 UTC828
    Ok(urls)
25d14e49ab68atagen2026-09-24 11:07 UTC829
}
25d14e49ab68atagen2026-09-24 11:07 UTC830
25d14e49ab68atagen2026-09-24 11:07 UTC831
fn forget_discovered(connection: &mut Connection, urls: Vec<String>) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC832
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC833
    for url in urls {
25d14e49ab68atagen2026-09-24 11:07 UTC834
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC835
            "DELETE FROM vineyard_discovered_peers WHERE url = ?1",
25d14e49ab68atagen2026-09-24 11:07 UTC836
            [url],
25d14e49ab68atagen2026-09-24 11:07 UTC837
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC838
    }
25d14e49ab68atagen2026-09-24 11:07 UTC839
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC840
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC841
}
25d14e49ab68atagen2026-09-24 11:07 UTC842
25d14e49ab68atagen2026-09-24 11:07 UTC843
impl StoredEvent {
25d14e49ab68atagen2026-09-24 11:07 UTC844
    fn read(row: &Row<'_>) -> rusqlite::Result<Self> {
25d14e49ab68atagen2026-09-24 11:07 UTC845
        Ok(Self {
25d14e49ab68atagen2026-09-24 11:07 UTC846
            id: row.get(0)?,
25d14e49ab68atagen2026-09-24 11:07 UTC847
            pubkey: row.get(1)?,
25d14e49ab68atagen2026-09-24 11:07 UTC848
            created_at: row.get(2)?,
25d14e49ab68atagen2026-09-24 11:07 UTC849
            kind: row.get(3)?,
25d14e49ab68atagen2026-09-24 11:07 UTC850
            content: row.get(4)?,
25d14e49ab68atagen2026-09-24 11:07 UTC851
            tags: row.get(5)?,
25d14e49ab68atagen2026-09-24 11:07 UTC852
            signature: row.get(6)?,
25d14e49ab68atagen2026-09-24 11:07 UTC853
        })
25d14e49ab68atagen2026-09-24 11:07 UTC854
    }
25d14e49ab68atagen2026-09-24 11:07 UTC855
25d14e49ab68atagen2026-09-24 11:07 UTC856
    fn into_event(self) -> Result<Event> {
25d14e49ab68atagen2026-09-24 11:07 UTC857
        let id = EventId::from_slice(&self.id).context("invalid stored event id")?;
25d14e49ab68atagen2026-09-24 11:07 UTC858
        let pubkey = PublicKey::from_slice(&self.pubkey).context("invalid stored public key")?;
25d14e49ab68atagen2026-09-24 11:07 UTC859
        let created_at: Timestamp = self
25d14e49ab68atagen2026-09-24 11:07 UTC860
            .created_at
25d14e49ab68atagen2026-09-24 11:07 UTC861
            .try_into()
25d14e49ab68atagen2026-09-24 11:07 UTC862
            .context("invalid event timestamp")?;
25d14e49ab68atagen2026-09-24 11:07 UTC863
        let kind = Kind::from_u16(self.kind.try_into().context("invalid stored event kind")?);
25d14e49ab68atagen2026-09-24 11:07 UTC864
        let tags: Tags = serde_json::from_str(&self.tags).context("invalid stored event tags")?;
25d14e49ab68atagen2026-09-24 11:07 UTC865
        let signature =
25d14e49ab68atagen2026-09-24 11:07 UTC866
            Signature::from_slice(&self.signature).context("invalid stored event signature")?;
25d14e49ab68atagen2026-09-24 11:07 UTC867
        Ok(Event::new(
25d14e49ab68atagen2026-09-24 11:07 UTC868
            id,
25d14e49ab68atagen2026-09-24 11:07 UTC869
            pubkey,
25d14e49ab68atagen2026-09-24 11:07 UTC870
            created_at,
25d14e49ab68atagen2026-09-24 11:07 UTC871
            kind,
25d14e49ab68atagen2026-09-24 11:07 UTC872
            tags,
25d14e49ab68atagen2026-09-24 11:07 UTC873
            self.content,
25d14e49ab68atagen2026-09-24 11:07 UTC874
            signature,
25d14e49ab68atagen2026-09-24 11:07 UTC875
        ))
25d14e49ab68atagen2026-09-24 11:07 UTC876
    }
25d14e49ab68atagen2026-09-24 11:07 UTC877
}
25d14e49ab68atagen2026-09-24 11:07 UTC878
25d14e49ab68atagen2026-09-24 11:07 UTC879
fn configure(connection: &Connection, max_database_bytes: u64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC880
    connection.busy_timeout(Duration::from_secs(5))?;
25d14e49ab68atagen2026-09-24 11:07 UTC881
    connection.pragma_update(None, "journal_mode", "WAL")?;
25d14e49ab68atagen2026-09-24 11:07 UTC882
    let journal_mode: String =
25d14e49ab68atagen2026-09-24 11:07 UTC883
        connection.pragma_query_value(None, "journal_mode", |row| row.get(0))?;
25d14e49ab68atagen2026-09-24 11:07 UTC884
    if !journal_mode.eq_ignore_ascii_case("wal") {
25d14e49ab68atagen2026-09-24 11:07 UTC885
        anyhow::bail!("journal mode is not wal");
25d14e49ab68atagen2026-09-24 11:07 UTC886
    }
25d14e49ab68atagen2026-09-24 11:07 UTC887
25d14e49ab68atagen2026-09-24 11:07 UTC888
    connection.pragma_update(None, "synchronous", "FULL")?;
25d14e49ab68atagen2026-09-24 11:07 UTC889
    connection.pragma_update(None, "foreign_keys", "ON")?;
25d14e49ab68atagen2026-09-24 11:07 UTC890
25d14e49ab68atagen2026-09-24 11:07 UTC891
    let stored_page_size =
25d14e49ab68atagen2026-09-24 11:07 UTC892
        connection.query_row("PRAGMA page_size", [], |row| row.get::<_, i64>(0))?;
25d14e49ab68atagen2026-09-24 11:07 UTC893
    let page_size = u64::try_from(stored_page_size).context("invalid database page size")?;
25d14e49ab68atagen2026-09-24 11:07 UTC894
    let page_budget = max_database_bytes / page_size;
25d14e49ab68atagen2026-09-24 11:07 UTC895
    if page_budget == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC896
        anyhow::bail!("database budget is smaller than one page");
25d14e49ab68atagen2026-09-24 11:07 UTC897
    }
25d14e49ab68atagen2026-09-24 11:07 UTC898
    let max_pages = integer(page_budget)?;
25d14e49ab68atagen2026-09-24 11:07 UTC899
    let page_count = connection.query_row("PRAGMA page_count", [], |row| row.get::<_, i64>(0))?;
25d14e49ab68atagen2026-09-24 11:07 UTC900
    if page_count > max_pages {
25d14e49ab68atagen2026-09-24 11:07 UTC901
        anyhow::bail!("database exceeds size budget");
25d14e49ab68atagen2026-09-24 11:07 UTC902
    }
25d14e49ab68atagen2026-09-24 11:07 UTC903
    connection.pragma_update(None, "max_page_count", max_pages)?;
25d14e49ab68atagen2026-09-24 11:07 UTC904
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC905
}
25d14e49ab68atagen2026-09-24 11:07 UTC906
25d14e49ab68atagen2026-09-24 11:07 UTC907
fn migrate(
25d14e49ab68atagen2026-09-24 11:07 UTC908
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC909
    max_discovered_peers: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC910
    excluded_peers: &[String],
25d14e49ab68atagen2026-09-24 11:07 UTC911
) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC912
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC913
    migrate_legacy_tables(&transaction)?;
25d14e49ab68atagen2026-09-24 11:07 UTC914
    transaction.execute_batch(SCHEMA)?;
25d14e49ab68atagen2026-09-24 11:07 UTC915
    transaction.execute(BACKFILL_DELIVERY, [])?;
25d14e49ab68atagen2026-09-24 11:07 UTC916
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC917
    backfill_discovered(connection, max_discovered_peers, excluded_peers)?;
25d14e49ab68atagen2026-09-24 11:07 UTC918
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC919
}
25d14e49ab68atagen2026-09-24 11:07 UTC920
25d14e49ab68atagen2026-09-24 11:07 UTC921
fn migrate_legacy_tables(transaction: &Transaction<'_>) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC922
    for (old, new) in [
25d14e49ab68atagen2026-09-24 11:07 UTC923
        ("sanctum_delivery", "vineyard_delivery"),
25d14e49ab68atagen2026-09-24 11:07 UTC924
        ("sanctum_peers", "vineyard_peers"),
25d14e49ab68atagen2026-09-24 11:07 UTC925
        ("sanctum_checkpoints", "vineyard_checkpoints"),
25d14e49ab68atagen2026-09-24 11:07 UTC926
        ("sanctum_retries", "vineyard_retries"),
25d14e49ab68atagen2026-09-24 11:07 UTC927
        ("sanctum_discovered_peers", "vineyard_discovered_peers"),
25d14e49ab68atagen2026-09-24 11:07 UTC928
    ] {
25d14e49ab68atagen2026-09-24 11:07 UTC929
        let old_exists = table_exists(transaction, old)?;
25d14e49ab68atagen2026-09-24 11:07 UTC930
        let new_exists = table_exists(transaction, new)?;
25d14e49ab68atagen2026-09-24 11:07 UTC931
        if old_exists && new_exists {
25d14e49ab68atagen2026-09-24 11:07 UTC932
            anyhow::bail!("database contains both {old} and {new}");
25d14e49ab68atagen2026-09-24 11:07 UTC933
        }
25d14e49ab68atagen2026-09-24 11:07 UTC934
        if old_exists {
25d14e49ab68atagen2026-09-24 11:07 UTC935
            transaction.execute_batch(&format!("ALTER TABLE {old} RENAME TO {new}"))?;
25d14e49ab68atagen2026-09-24 11:07 UTC936
        }
25d14e49ab68atagen2026-09-24 11:07 UTC937
    }
25d14e49ab68atagen2026-09-24 11:07 UTC938
    transaction.execute_batch(
25d14e49ab68atagen2026-09-24 11:07 UTC939
        "
25d14e49ab68atagen2026-09-24 11:07 UTC940
        DROP TRIGGER IF EXISTS sanctum_delivery_after_insert;
25d14e49ab68atagen2026-09-24 11:07 UTC941
        DROP INDEX IF EXISTS sanctum_retries_due;
25d14e49ab68atagen2026-09-24 11:07 UTC942
        DROP INDEX IF EXISTS sanctum_discovered_urls;
25d14e49ab68atagen2026-09-24 11:07 UTC943
        ",
25d14e49ab68atagen2026-09-24 11:07 UTC944
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC945
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC946
}
25d14e49ab68atagen2026-09-24 11:07 UTC947
25d14e49ab68atagen2026-09-24 11:07 UTC948
fn table_exists(transaction: &Transaction<'_>, name: &str) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC949
    Ok(transaction.query_row(
25d14e49ab68atagen2026-09-24 11:07 UTC950
        "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1)",
25d14e49ab68atagen2026-09-24 11:07 UTC951
        [name],
25d14e49ab68atagen2026-09-24 11:07 UTC952
        |row| row.get(0),
25d14e49ab68atagen2026-09-24 11:07 UTC953
    )?)
25d14e49ab68atagen2026-09-24 11:07 UTC954
}
25d14e49ab68atagen2026-09-24 11:07 UTC955
25d14e49ab68atagen2026-09-24 11:07 UTC956
fn backfill_discovered(
25d14e49ab68atagen2026-09-24 11:07 UTC957
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC958
    max_discovered_peers: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC959
    excluded_peers: &[String],
25d14e49ab68atagen2026-09-24 11:07 UTC960
) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC961
    let excluded = excluded_peers.iter().cloned().collect::<HashSet<_>>();
25d14e49ab68atagen2026-09-24 11:07 UTC962
    let relay_tags = {
25d14e49ab68atagen2026-09-24 11:07 UTC963
        let mut statement = connection.prepare(DISCOVERED_RELAY_TAGS)?;
25d14e49ab68atagen2026-09-24 11:07 UTC964
        let mut rows = statement.query([])?;
25d14e49ab68atagen2026-09-24 11:07 UTC965
        let mut relay_tags = Vec::new();
25d14e49ab68atagen2026-09-24 11:07 UTC966
        while let Some(row) = rows.next()? {
25d14e49ab68atagen2026-09-24 11:07 UTC967
            relay_tags.push((
25d14e49ab68atagen2026-09-24 11:07 UTC968
                row.get::<_, Vec<u8>>(0)?,
25d14e49ab68atagen2026-09-24 11:07 UTC969
                row.get::<_, String>(1)?,
25d14e49ab68atagen2026-09-24 11:07 UTC970
                row.get::<_, i64>(2)?,
25d14e49ab68atagen2026-09-24 11:07 UTC971
            ));
25d14e49ab68atagen2026-09-24 11:07 UTC972
        }
25d14e49ab68atagen2026-09-24 11:07 UTC973
        relay_tags
25d14e49ab68atagen2026-09-24 11:07 UTC974
    };
25d14e49ab68atagen2026-09-24 11:07 UTC975
25d14e49ab68atagen2026-09-24 11:07 UTC976
    let transaction = connection.transaction()?;
25d14e49ab68atagen2026-09-24 11:07 UTC977
    transaction.execute("DELETE FROM vineyard_discovered_peers", [])?;
25d14e49ab68atagen2026-09-24 11:07 UTC978
    let mut urls = HashSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC979
    for (signer, raw_url, discovered_at) in relay_tags {
25d14e49ab68atagen2026-09-24 11:07 UTC980
        let Some(url) = relay_url(&raw_url).map(|url| url.to_string()) else {
25d14e49ab68atagen2026-09-24 11:07 UTC981
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC982
        };
25d14e49ab68atagen2026-09-24 11:07 UTC983
        if excluded.contains(&url) || (!urls.contains(&url) && urls.len() >= max_discovered_peers) {
25d14e49ab68atagen2026-09-24 11:07 UTC984
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC985
        }
25d14e49ab68atagen2026-09-24 11:07 UTC986
        urls.insert(url.clone());
25d14e49ab68atagen2026-09-24 11:07 UTC987
        transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC988
            "
25d14e49ab68atagen2026-09-24 11:07 UTC989
            INSERT OR IGNORE INTO vineyard_discovered_peers(signer, url, discovered_at)
25d14e49ab68atagen2026-09-24 11:07 UTC990
            VALUES (?1, ?2, ?3)
25d14e49ab68atagen2026-09-24 11:07 UTC991
            ",
25d14e49ab68atagen2026-09-24 11:07 UTC992
            params![signer, url, discovered_at],
25d14e49ab68atagen2026-09-24 11:07 UTC993
        )?;
25d14e49ab68atagen2026-09-24 11:07 UTC994
    }
25d14e49ab68atagen2026-09-24 11:07 UTC995
    transaction.commit()?;
25d14e49ab68atagen2026-09-24 11:07 UTC996
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC997
}
25d14e49ab68atagen2026-09-24 11:07 UTC998
25d14e49ab68atagen2026-09-24 11:07 UTC999
fn advance_delivery(transaction: &Transaction<'_>, peer: &str, sequence: i64) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC1000
    transaction.execute(
25d14e49ab68atagen2026-09-24 11:07 UTC1001
        "
25d14e49ab68atagen2026-09-24 11:07 UTC1002
        INSERT INTO vineyard_peers(peer, delivered)
25d14e49ab68atagen2026-09-24 11:07 UTC1003
        VALUES (?1, ?2)
25d14e49ab68atagen2026-09-24 11:07 UTC1004
        ON CONFLICT(peer) DO UPDATE SET
25d14e49ab68atagen2026-09-24 11:07 UTC1005
            delivered = max(vineyard_peers.delivered, excluded.delivered)
25d14e49ab68atagen2026-09-24 11:07 UTC1006
        ",
25d14e49ab68atagen2026-09-24 11:07 UTC1007
        params![peer, sequence],
25d14e49ab68atagen2026-09-24 11:07 UTC1008
    )?;
25d14e49ab68atagen2026-09-24 11:07 UTC1009
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC1010
}
25d14e49ab68atagen2026-09-24 11:07 UTC1011
25d14e49ab68atagen2026-09-24 11:07 UTC1012
fn integer<T>(value: T) -> Result<i64>
25d14e49ab68atagen2026-09-24 11:07 UTC1013
where
25d14e49ab68atagen2026-09-24 11:07 UTC1014
    T: TryInto<i64>,
25d14e49ab68atagen2026-09-24 11:07 UTC1015
    T::Error: Error + Send + Sync + 'static,
25d14e49ab68atagen2026-09-24 11:07 UTC1016
{
25d14e49ab68atagen2026-09-24 11:07 UTC1017
    value
25d14e49ab68atagen2026-09-24 11:07 UTC1018
        .try_into()
25d14e49ab68atagen2026-09-24 11:07 UTC1019
        .context("value exceeds sqlite integer range")
25d14e49ab68atagen2026-09-24 11:07 UTC1020
}
25d14e49ab68atagen2026-09-24 11:07 UTC1021
25d14e49ab68atagen2026-09-24 11:07 UTC1022
fn unix_seconds() -> i64 {
25d14e49ab68atagen2026-09-24 11:07 UTC1023
    SystemTime::now()
25d14e49ab68atagen2026-09-24 11:07 UTC1024
        .duration_since(UNIX_EPOCH)
25d14e49ab68atagen2026-09-24 11:07 UTC1025
        .map_or(0, |duration| {
25d14e49ab68atagen2026-09-24 11:07 UTC1026
            i64::try_from(duration.as_secs()).unwrap_or(i64::MAX)
25d14e49ab68atagen2026-09-24 11:07 UTC1027
        })
25d14e49ab68atagen2026-09-24 11:07 UTC1028
}