tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit b54fdc9d481f4b37c0f783c45973aca01734ca52
parent 22c6d1cf03d3892095fe771e0e3a6bf4cda03738
Author: triesap <tyson@radroots.org>
Date:   Fri,  5 Jun 2026 23:06:13 -0700

store-surreal: query raw events

Diffstat:
Mcrates/tangle_store_surreal/src/lib.rs | 141++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 140 insertions(+), 1 deletion(-)

diff --git a/crates/tangle_store_surreal/src/lib.rs b/crates/tangle_store_surreal/src/lib.rs @@ -8,7 +8,7 @@ use tangle_nips::{ DeletionTarget, ListingProjection, ListingProjectionEvaluation, NIP99_DRAFT_LISTING_KIND, NIP99_PUBLIC_LISTING_KIND, evaluate_listing_projection, parse_deletion_request, }; -use tangle_protocol::{AddressCoordinate, Event, EventId, UnixTimestamp, event_to_value}; +use tangle_protocol::{AddressCoordinate, Event, EventId, Filter, UnixTimestamp, event_to_value}; use tangle_store::{StoreEventOutcome, StoredEvent}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -871,6 +871,80 @@ CREATE type::record('nostr_event', $event_id) CONTENT { response.take(0).map_err(SurrealStoreError::from) } + pub async fn query_raw_events( + &self, + filter: &Filter, + ) -> Result<Vec<serde_json::Value>, SurrealStoreError> { + let mut statement = + "SELECT * FROM nostr_event WHERE deleted = false AND hidden = false".to_owned(); + if !filter.ids().is_empty() { + statement.push_str(" AND event_id IN $ids"); + } + if !filter.authors().is_empty() { + statement.push_str(" AND pubkey IN $authors"); + } + if !filter.kinds().is_empty() { + statement.push_str(" AND kind IN $kinds"); + } + if filter.since().is_some() { + statement.push_str(" AND created_at >= $since"); + } + if filter.until().is_some() { + statement.push_str(" AND created_at <= $until"); + } + statement.push_str(" ORDER BY created_at DESC, event_id ASC"); + if filter.limit().is_some() { + statement.push_str(" LIMIT $limit"); + } + statement.push(';'); + let mut query = self.db.query(statement); + if !filter.ids().is_empty() { + query = query.bind(( + "ids", + filter + .ids() + .iter() + .map(|id| id.as_str().to_owned()) + .collect::<Vec<_>>(), + )); + } + if !filter.authors().is_empty() { + query = query.bind(( + "authors", + filter + .authors() + .iter() + .map(|pubkey| pubkey.as_str().to_owned()) + .collect::<Vec<_>>(), + )); + } + if !filter.kinds().is_empty() { + query = query.bind(( + "kinds", + filter + .kinds() + .iter() + .map(|kind| kind.as_u32()) + .collect::<Vec<_>>(), + )); + } + if let Some(since) = filter.since() { + query = query.bind(("since", since.as_u64())); + } + if let Some(until) = filter.until() { + query = query.bind(("until", until.as_u64())); + } + if let Some(limit) = filter.limit() { + query = query.bind(("limit", limit)); + } + let mut response = query + .await + .map_err(SurrealStoreError::from)? + .check() + .map_err(SurrealStoreError::from)?; + response.take(0).map_err(SurrealStoreError::from) + } + pub async fn index_event_tags(&self, event: &Event) -> Result<(), SurrealStoreError> { self.db .query("DELETE event_tag_index WHERE event_id = $event_id;") @@ -1949,6 +2023,7 @@ mod tests { }; use tangle_protocol::{ Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, + filter_from_value, }; use tangle_store::{StoreEventOutcome, StoredEvent}; use tangle_test_support::{build_fixture_event, valid_public_listing_spec}; @@ -2643,6 +2718,70 @@ mod tests { } #[tokio::test] + async fn query_raw_events_applies_core_filter_constraints() { + let store = memory_store().await; + store + .apply_plan(&base_migration_plan()) + .await + .expect("apply plan"); + let pubkey_a = "1".repeat(PublicKeyHex::HEX_LENGTH); + let pubkey_b = "2".repeat(PublicKeyHex::HEX_LENGTH); + let first = synthetic_event("1", "b", &pubkey_a, 100, 1, Vec::new(), "first"); + let second = synthetic_event("2", "c", &pubkey_a, 101, 1, Vec::new(), "second"); + let third = synthetic_event("3", "d", &pubkey_a, 102, 2, Vec::new(), "third"); + let fourth = synthetic_event("4", "e", &pubkey_b, 103, 1, Vec::new(), "fourth"); + for event in [&first, &second, &third, &fourth] { + assert_eq!( + store + .store_raw_event(&StoredEvent::new(event.clone(), UnixTimestamp::new(200))) + .await + .expect("insert"), + StoreEventOutcome::Inserted + ); + } + + let filtered = filter_from_value(&serde_json::json!({ + "authors": [pubkey_a], + "kinds": [1], + "since": 100, + "until": 101, + "limit": 1 + })) + .expect("filter"); + let rows = store + .query_raw_events(&filtered) + .await + .expect("filtered rows"); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0]["event_id"], second.id().as_str()); + + let id_filter = filter_from_value(&serde_json::json!({ + "ids": [first.id().as_str()] + })) + .expect("id filter"); + assert_eq!( + store.query_raw_events(&id_filter).await.expect("id rows")[0]["event_id"], + first.id().as_str() + ); + + store + .database() + .query("UPDATE nostr_event SET deleted = true WHERE event_id = $event_id;") + .bind(("event_id", first.id().as_str())) + .await + .expect("delete marker") + .check() + .expect("delete check"); + assert!( + store + .query_raw_events(&id_filter) + .await + .expect("deleted rows") + .is_empty() + ); + } + + #[tokio::test] async fn index_event_tags_persists_single_letter_tag_rows() { let store = memory_store().await; store