mirror of
https://github.com/scsibug/nostr-rs-relay.git
synced 2024-11-09 21:29:06 -05:00
feat: implement multi-valued filter searching
NIP-01 now uses arrays instead of scalars. Fixes https://todo.sr.ht/~gheartsfield/nostr-rs-relay/17
This commit is contained in:
parent
b5da3fa2b0
commit
6673fcfd11
57
src/db.rs
57
src/db.rs
|
@ -302,35 +302,52 @@ fn query_from_sub(sub: &Subscription) -> String {
|
||||||
filter_components.push(authors_clause);
|
filter_components.push(authors_clause);
|
||||||
}
|
}
|
||||||
// Query for Kind
|
// Query for Kind
|
||||||
if f.kind.is_some() {
|
if let Some(ks) = &f.kinds {
|
||||||
// kind is number, no escaping needed
|
// kind is number, no escaping needed
|
||||||
let kind_clause = format!("kind = {}", f.kind.unwrap());
|
let str_kinds: Vec<String> = ks.iter().map(|x| x.to_string()).collect();
|
||||||
|
let kind_clause = format!("kind IN ({})", str_kinds.join(", "));
|
||||||
filter_components.push(kind_clause);
|
filter_components.push(kind_clause);
|
||||||
}
|
}
|
||||||
// Query for event
|
// Query for event
|
||||||
if f.id.is_some() {
|
if f.ids.is_some() {
|
||||||
let id_str = f.id.as_ref().unwrap();
|
let ids_escaped: Vec<String> = f
|
||||||
if is_hex(id_str) {
|
.ids
|
||||||
let id_clause = format!("event_hash = x'{}'", id_str);
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.filter(|&x| is_hex(x))
|
||||||
|
.map(|x| format!("x'{}'", x))
|
||||||
|
.collect();
|
||||||
|
let id_clause = format!("event_hash IN ({})", ids_escaped.join(", "));
|
||||||
filter_components.push(id_clause);
|
filter_components.push(id_clause);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Query for referenced event
|
// Query for referenced event
|
||||||
if f.event.is_some() {
|
if f.events.is_some() {
|
||||||
let ev_str = f.event.as_ref().unwrap();
|
let events_escaped: Vec<String> = f
|
||||||
if is_hex(ev_str) {
|
.events
|
||||||
let ev_clause = format!("referenced_event = x'{}'", ev_str);
|
.as_ref()
|
||||||
filter_components.push(ev_clause);
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
}
|
.filter(|&x| is_hex(x))
|
||||||
// Query for referenced pet name pubkey
|
.map(|x| format!("x'{}'", x))
|
||||||
if f.pubkey.is_some() {
|
.collect();
|
||||||
let pet_str = f.pubkey.as_ref().unwrap();
|
let events_clause = format!("referenced_event IN ({})", events_escaped.join(", "));
|
||||||
if is_hex(pet_str) {
|
filter_components.push(events_clause);
|
||||||
let pet_clause = format!("referenced_pubkey = x'{}'", pet_str);
|
|
||||||
filter_components.push(pet_clause);
|
|
||||||
}
|
}
|
||||||
|
// Query for referenced pubkey
|
||||||
|
if f.pubkeys.is_some() {
|
||||||
|
let pubkeys_escaped: Vec<String> = f
|
||||||
|
.pubkeys
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.filter(|&x| is_hex(x))
|
||||||
|
.map(|x| format!("x'{}'", x))
|
||||||
|
.collect();
|
||||||
|
let pubkeys_clause = format!("referenced_pubkey IN ({})", pubkeys_escaped.join(", "));
|
||||||
|
filter_components.push(pubkeys_clause);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for timestamp
|
// Query for timestamp
|
||||||
if f.since.is_some() {
|
if f.since.is_some() {
|
||||||
let created_clause = format!("created_at > {}", f.since.unwrap());
|
let created_clause = format!("created_at > {}", f.since.unwrap());
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// Subscription identifier and set of request filters
|
/// Subscription identifier and set of request filters
|
||||||
#[derive(Serialize, PartialEq, Debug, Clone)]
|
#[derive(Serialize, PartialEq, Debug, Clone)]
|
||||||
|
@ -17,16 +18,16 @@ pub struct Subscription {
|
||||||
/// absent ([`None`]) if it should be ignored.
|
/// absent ([`None`]) if it should be ignored.
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
pub struct ReqFilter {
|
pub struct ReqFilter {
|
||||||
/// Event hash
|
/// Event hashes
|
||||||
pub id: Option<String>,
|
pub ids: Option<Vec<String>>,
|
||||||
/// Event kind
|
/// Event kinds
|
||||||
pub kind: Option<u64>,
|
pub kinds: Option<Vec<u64>>,
|
||||||
/// Referenced event hash
|
/// Referenced event hash
|
||||||
#[serde(rename = "#e")]
|
#[serde(rename = "#e")]
|
||||||
pub event: Option<String>,
|
pub events: Option<Vec<String>>,
|
||||||
/// Referenced public key for a petname
|
/// Referenced public key for a petname
|
||||||
#[serde(rename = "#p")]
|
#[serde(rename = "#p")]
|
||||||
pub pubkey: Option<String>,
|
pub pubkeys: Option<Vec<String>>,
|
||||||
/// Events published after this time
|
/// Events published after this time
|
||||||
pub since: Option<u64>,
|
pub since: Option<u64>,
|
||||||
/// Events published before this time
|
/// Events published before this time
|
||||||
|
@ -105,8 +106,13 @@ impl Subscription {
|
||||||
|
|
||||||
impl ReqFilter {
|
impl ReqFilter {
|
||||||
/// Check for a match within the authors list.
|
/// Check for a match within the authors list.
|
||||||
// TODO: Ambiguity; what if the array is empty? Should we
|
fn ids_match(&self, event: &Event) -> bool {
|
||||||
// consider that the same as null?
|
self.ids
|
||||||
|
.as_ref()
|
||||||
|
.map(|vs| vs.contains(&event.id.to_owned()))
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn authors_match(&self, event: &Event) -> bool {
|
fn authors_match(&self, event: &Event) -> bool {
|
||||||
self.authors
|
self.authors
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -115,29 +121,47 @@ impl ReqFilter {
|
||||||
}
|
}
|
||||||
/// Check if this filter either matches, or does not care about the event tags.
|
/// Check if this filter either matches, or does not care about the event tags.
|
||||||
fn event_match(&self, event: &Event) -> bool {
|
fn event_match(&self, event: &Event) -> bool {
|
||||||
self.event
|
// This needs to be analyzed for performance; building these
|
||||||
.as_ref()
|
// hash sets for each active subscription isn't great.
|
||||||
.map(|t| event.event_tag_match(t))
|
if let Some(es) = &self.events {
|
||||||
.unwrap_or(true)
|
let event_refs =
|
||||||
|
HashSet::<_>::from_iter(event.get_event_tags().iter().map(|x| x.to_owned()));
|
||||||
|
let filter_refs = HashSet::<_>::from_iter(es.iter().map(|x| &x[..]));
|
||||||
|
let cardinality = event_refs.intersection(&filter_refs).count();
|
||||||
|
cardinality > 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this filter either matches, or does not care about
|
/// Check if this filter either matches, or does not care about
|
||||||
/// the pubkey/petname tags.
|
/// the pubkey/petname tags.
|
||||||
fn pubkey_match(&self, event: &Event) -> bool {
|
fn pubkey_match(&self, event: &Event) -> bool {
|
||||||
self.pubkey
|
// This needs to be analyzed for performance; building these
|
||||||
.as_ref()
|
// hash sets for each active subscription isn't great.
|
||||||
.map(|t| event.pubkey_tag_match(t))
|
if let Some(ps) = &self.pubkeys {
|
||||||
.unwrap_or(true)
|
let pubkey_refs =
|
||||||
|
HashSet::<_>::from_iter(event.get_pubkey_tags().iter().map(|x| x.to_owned()));
|
||||||
|
let filter_refs = HashSet::<_>::from_iter(ps.iter().map(|x| &x[..]));
|
||||||
|
let cardinality = pubkey_refs.intersection(&filter_refs).count();
|
||||||
|
cardinality > 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this filter either matches, or does not care about the kind.
|
/// Check if this filter either matches, or does not care about the kind.
|
||||||
fn kind_match(&self, kind: u64) -> bool {
|
fn kind_match(&self, kind: u64) -> bool {
|
||||||
self.kind.map(|v| v == kind).unwrap_or(true)
|
self.kinds
|
||||||
|
.as_ref()
|
||||||
|
.map(|ks| ks.contains(&kind))
|
||||||
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if all populated fields in this filter match the provided event.
|
/// Determine if all populated fields in this filter match the provided event.
|
||||||
pub fn interested_in_event(&self, event: &Event) -> bool {
|
pub fn interested_in_event(&self, event: &Event) -> bool {
|
||||||
self.id.as_ref().map(|v| v == &event.id).unwrap_or(true)
|
// self.id.as_ref().map(|v| v == &event.id).unwrap_or(true)
|
||||||
|
self.ids_match(event)
|
||||||
&& self.since.map(|t| event.created_at > t).unwrap_or(true)
|
&& self.since.map(|t| event.created_at > t).unwrap_or(true)
|
||||||
&& self.kind_match(event.kind)
|
&& self.kind_match(event.kind)
|
||||||
&& self.authors_match(event)
|
&& self.authors_match(event)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user