atagen

vineyard

public
Star0
Watch0
25d14e49ab6861630869e2129d40652900bc5833
1
ssh://brewery@pls.lobotomise.me:33/atagen/vineyard.git
src/query.rsRust131 lines3.6 KB
1
use anyhow::{Context as _, Result};
2
use nostr::filter::Filter;
3
use rusqlite::types::Value;
4
5
const DEFAULT_LIMIT: usize = 10_000;
6
7
const SELECT_EVENTS: &str = "
8
SELECT e.id, e.pubkey, e.created_at, e.kind, e.content, e.tags, e.sig
9
FROM events AS e
10
WHERE 1 = 1
11
";
12
13
const TAG_VALUES: &str = "
14
 AND EXISTS (
15
    SELECT 1
16
    FROM event_tags AS tag
17
    WHERE tag.event_id = e.id
18
      AND tag.tag_name = ?
19
      AND tag.tag_value IN (
20
";
21
22
pub struct EventQuery {
23
    pub(crate) sql: String,
24
    pub(crate) parameters: Vec<Value>,
25
    pub(crate) limit: usize,
26
}
27
28
impl EventQuery {
29
    pub(crate) fn new(filter: &Filter) -> Result<Self> {
30
        if filter.search.is_some() {
31
            anyhow::bail!("search is not supported");
32
        }
33
34
        let mut query = Self {
35
            sql: SELECT_EVENTS.to_owned(),
36
            parameters: Vec::new(),
37
            limit: filter.limit.unwrap_or(DEFAULT_LIMIT),
38
        };
39
40
        if let Some(ids) = filter.ids.as_ref() {
41
            query.add_values(
42
                "e.id",
43
                ids.iter()
44
                    .map(|id| Value::Blob(id.as_bytes().to_vec()))
45
                    .collect(),
46
            );
47
        }
48
        if let Some(authors) = filter.authors.as_ref() {
49
            query.add_values(
50
                "e.pubkey",
51
                authors
52
                    .iter()
53
                    .map(|author| Value::Blob(author.as_bytes().to_vec()))
54
                    .collect(),
55
            );
56
        }
57
        if let Some(kinds) = filter.kinds.as_ref() {
58
            query.add_values(
59
                "e.kind",
60
                kinds
61
                    .iter()
62
                    .map(|kind| Value::Integer(i64::from(kind.as_u16())))
63
                    .collect(),
64
            );
65
        }
66
        if let Some(since) = filter.since {
67
            query.add_condition(
68
                "e.created_at >= ?",
69
                Value::Integer(timestamp(since.as_secs())?),
70
            );
71
        }
72
        if let Some(until) = filter.until {
73
            query.add_condition(
74
                "e.created_at <= ?",
75
                Value::Integer(timestamp(until.as_secs())?),
76
            );
77
        }
78
        for (tag, values) in &filter.generic_tags {
79
            if values.is_empty() {
80
                query.exclude_all();
81
                break;
82
            }
83
            query.sql.push_str(TAG_VALUES);
84
            query.parameters.push(Value::Text(tag.to_string()));
85
            query.add_placeholders(values.len());
86
            query.sql.push_str("))");
87
            query
88
                .parameters
89
                .extend(values.iter().cloned().map(Value::Text));
90
        }
91
92
        query.sql.push_str(" ORDER BY e.created_at DESC, e.id");
93
        Ok(query)
94
    }
95
96
    fn add_values(&mut self, column: &str, values: Vec<Value>) {
97
        if values.is_empty() {
98
            self.exclude_all();
99
            return;
100
        }
101
        self.sql.push_str(" AND ");
102
        self.sql.push_str(column);
103
        self.sql.push_str(" IN (");
104
        self.add_placeholders(values.len());
105
        self.sql.push(')');
106
        self.parameters.extend(values);
107
    }
108
109
    fn add_condition(&mut self, condition: &str, value: Value) {
110
        self.sql.push_str(" AND ");
111
        self.sql.push_str(condition);
112
        self.parameters.push(value);
113
    }
114
115
    fn add_placeholders(&mut self, count: usize) {
116
        for index in 0..count {
117
            if index > 0 {
118
                self.sql.push_str(", ");
119
            }
120
            self.sql.push('?');
121
        }
122
    }
123
124
    fn exclude_all(&mut self) {
125
        self.sql.push_str(" AND 0");
126
    }
127
}
128
129
fn timestamp(value: u64) -> Result<i64> {
130
    value.try_into().context("timestamp exceeds sqlite range")
131
}