feat(relay): enable SQLite command persistence

Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je
2026-08-10 12:12:05 -04:00
committed by Brother Darryl
parent 1971f71a06
commit 168168d8fa
2 changed files with 42 additions and 14 deletions
+15 -2
View File
@@ -4818,15 +4818,28 @@ impl Db {
}
/// Ensures monthly partitions exist for the next N months.
///
/// SQLite stores events in an unpartitioned local table, so this is an
/// intentional single-node no-op.
pub async fn ensure_future_partitions(&self, months_ahead: u32) -> Result<()> {
partition::ensure_future_partitions(self.pg_pool()?, months_ahead).await
match &self.backend {
DbBackend::SQLite(_) => Ok(()),
DbBackend::Postgres => {
partition::ensure_future_partitions(self.pg_pool()?, months_ahead).await
}
}
}
/// Backfill `d_tag` for existing NIP-33 events (kind 30000–39999) that have `d_tag IS NULL`.
///
/// Idempotent — safe to call on every startup. No-ops when all rows are already populated.
/// Runs a single UPDATE touching only NIP-33 rows with NULL d_tag.
/// Runs a single UPDATE touching only NIP-33 rows with NULL d_tag. SQLite
/// derives d-tags from the persisted event JSON and has no denormalized
/// `d_tag` column, so its profile deliberately returns zero.
pub async fn backfill_d_tags(&self) -> Result<u64> {
if matches!(&self.backend, DbBackend::SQLite(_)) {
return Ok(0);
}
let result = sqlx::query(
"UPDATE events \
SET d_tag = COALESCE( \
@@ -119,11 +119,35 @@ async fn persist_command_event(
channel_id_override: Option<Uuid>,
) -> Result<PersistResult, IngestError> {
let channel_id = channel_id_override.or_else(|| extract_channel_id(event));
let d_tag = buzz_db::event::extract_d_tag(event);
if let Some(ref d_tag) = d_tag {
if d_tag.len() > buzz_db::event::D_TAG_MAX_LEN {
return Err(IngestError::Rejected(format!(
"invalid: d tag too long ({} bytes, max {})",
d_tag.len(),
buzz_db::event::D_TAG_MAX_LEN,
)));
}
}
if state.config.profile.is_single_node() {
return Err(IngestError::Rejected(
"unsupported_feature: this command is unavailable in the single-node profile".into(),
));
let result = if let Some(d_tag) = d_tag.as_deref() {
state
.db
.replace_parameterized_event(tenant.community(), event, d_tag, channel_id)
.await
} else {
state
.db
.insert_event(tenant.community(), event, channel_id)
.await
}
.map_err(|e| IngestError::Internal(format!("error: persist SQLite command: {e}")))?;
return Ok(if result.1 {
PersistResult::Inserted(CommandTransaction::Sqlite)
} else {
PersistResult::Duplicate
});
}
let mut tx = state
@@ -146,16 +170,7 @@ async fn persist_command_event(
let received_at = chrono::Utc::now();
// Extract d_tag for parameterized replaceable kinds (NIP-33).
let d_tag = buzz_db::event::extract_d_tag(event);
if let Some(ref d_tag) = d_tag {
if d_tag.len() > buzz_db::event::D_TAG_MAX_LEN {
return Err(IngestError::Rejected(format!(
"invalid: d tag too long ({} bytes, max {})",
d_tag.len(),
buzz_db::event::D_TAG_MAX_LEN,
)));
}
// Command kinds normally use plain insert semantics, but workflow
// definitions are NIP-33 events. Serialize writers for the same
// coordinate and reject stale writes before executing the domain