Skip to main content

p2panda_store/address_book/
sqlite.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::collections::HashSet;
4use std::fmt::Display;
5use std::str::FromStr;
6use std::time::Duration;
7
8use p2panda_core::cbor::{decode_cbor, encode_cbor};
9use p2panda_core::{Topic, VerifyingKey};
10use serde::{Deserialize, Serialize};
11use sqlx::{query, query_as, query_scalar};
12
13use crate::address_book::{AddressBookStore, NodeInfo};
14use crate::sqlite::{SqliteError, SqliteStore};
15
16impl<N> AddressBookStore<VerifyingKey, N> for SqliteStore
17where
18    N: NodeInfo<VerifyingKey> + Serialize + for<'de> Deserialize<'de>,
19{
20    type Error = SqliteError;
21
22    async fn insert_node_info(&self, info: N) -> Result<bool, Self::Error> {
23        let is_upsert = {
24            let row = self
25                .tx(async |tx| {
26                    query_as::<_, (i32,)>("SELECT COUNT(*) FROM node_infos_v1 WHERE node_id = ?")
27                        .bind(info.id().to_hex())
28                        .fetch_one(&mut **tx)
29                        .await
30                        .map_err(SqliteError::Sqlite)
31                })
32                .await?;
33
34            row.0 == 1
35        };
36
37        self.tx(async |tx| {
38            query(
39                "
40                INSERT
41                INTO
42                    node_infos_v1 (
43                        node_id,
44                        node_info,
45                        bootstrap,
46                        stale
47                    )
48                VALUES
49                    (?, ?, ?, ?)
50                ON CONFLICT(node_id)
51                DO UPDATE
52                    SET
53                        node_info = EXCLUDED.node_info,
54                        bootstrap = EXCLUDED.bootstrap,
55                        stale = EXCLUDED.stale
56                ",
57            )
58            .bind(info.id().to_hex())
59            .bind(
60                encode_cbor(&info)
61                    .map_err(|err| SqliteError::Encode("node_info".to_string(), err))?,
62            )
63            .bind(info.is_bootstrap())
64            .bind(info.is_stale())
65            .execute(&mut **tx)
66            .await
67            .map_err(SqliteError::Sqlite)
68        })
69        .await?;
70
71        Ok(!is_upsert)
72    }
73
74    async fn remove_node_info(&self, id: &VerifyingKey) -> Result<bool, Self::Error> {
75        // Remove node's info.
76        let result = self
77            .tx(async |tx| {
78                query(
79                    "
80                    DELETE FROM
81                        node_infos_v1
82                    WHERE
83                        node_id = ?
84                    ",
85                )
86                .bind(id.to_hex())
87                .execute(&mut **tx)
88                .await
89                .map_err(SqliteError::Sqlite)
90            })
91            .await?;
92
93        // Remove associated topics for this node.
94        self.tx(async |tx| {
95            query(
96                "
97                DELETE FROM
98                    topics2node_infos_v1
99                WHERE
100                    node_id = ?
101                ",
102            )
103            .bind(id.to_hex())
104            .execute(&mut **tx)
105            .await
106            .map_err(SqliteError::Sqlite)
107        })
108        .await?;
109
110        Ok(result.rows_affected() > 0)
111    }
112
113    async fn remove_older_than(&self, duration: Duration) -> Result<usize, Self::Error> {
114        let result = self
115            .tx(async |tx| {
116                query_as::<_, (String,)>(
117                    "
118                    DELETE FROM
119                        node_infos_v1
120                    WHERE
121                        updated_at < UNIXEPOCH() - ?
122                    RETURNING
123                        node_id
124                    ",
125                )
126                .bind(duration.as_secs() as i64)
127                .fetch_all(&mut **tx)
128                .await
129                .map_err(SqliteError::Sqlite)
130            })
131            .await?;
132
133        let node_ids: Vec<&String> = result.iter().map(|item| &item.0).collect();
134
135        // Remove associated topics for removed nodes.
136        self.tx(async |tx| {
137            query(&format!(
138                "
139                DELETE FROM
140                    topics2node_infos_v1
141                WHERE
142                    node_id IN ({})
143                ",
144                in_op_str(&node_ids)
145            ))
146            .execute(&mut **tx)
147            .await
148            .map_err(SqliteError::Sqlite)
149        })
150        .await?;
151
152        Ok(node_ids.len())
153    }
154
155    async fn node_info(&self, id: &VerifyingKey) -> Result<Option<N>, Self::Error> {
156        let result = self
157            .execute(async |pool| {
158                query_as::<_, (Vec<u8>,)>(
159                    "
160                    SELECT
161                        node_info
162                    FROM
163                        node_infos_v1
164                    WHERE
165                        node_id = ?
166                    ",
167                )
168                .bind(id.to_hex())
169                .fetch_optional(pool)
170                .await
171                .map_err(SqliteError::Sqlite)
172            })
173            .await?;
174
175        decode_node_info(result)
176    }
177
178    async fn node_topics(&self, id: &VerifyingKey) -> Result<HashSet<Topic>, Self::Error> {
179        let result = self
180            .execute(async |pool| {
181                query_as::<_, (String,)>(
182                    "
183                    SELECT
184                        topic_id
185                    FROM
186                        topics2node_infos_v1
187                    WHERE
188                        node_id = ?
189                    ",
190                )
191                .bind(id.to_hex())
192                .fetch_all(pool)
193                .await
194                .map_err(SqliteError::Sqlite)
195            })
196            .await?;
197
198        result
199            .iter()
200            .map(|item| {
201                Topic::from_str(&item.0)
202                    .map_err(|err| SqliteError::Decode("topic_id".to_string(), err.into()))
203            })
204            .collect()
205    }
206
207    async fn all_node_infos(&self) -> Result<Vec<N>, Self::Error> {
208        let result = self
209            .execute(async |pool| {
210                query_as::<_, (Vec<u8>,)>(
211                    "
212                    SELECT
213                        node_info
214                    FROM
215                        node_infos_v1
216                    WHERE
217                        stale = FALSE
218                    ",
219                )
220                .fetch_all(pool)
221                .await
222                .map_err(SqliteError::Sqlite)
223            })
224            .await?;
225
226        decode_node_infos(result)
227    }
228
229    async fn all_nodes_len(&self) -> Result<usize, Self::Error> {
230        let count: i64 = self
231            .execute(async |pool| {
232                query_scalar(
233                    "
234                    SELECT
235                        COUNT(node_id)
236                    FROM
237                        node_infos_v1
238                    WHERE
239                        stale = FALSE
240                    ",
241                )
242                .fetch_one(pool)
243                .await
244                .map_err(SqliteError::Sqlite)
245            })
246            .await?;
247
248        Ok(count as usize)
249    }
250
251    async fn all_bootstrap_nodes_len(&self) -> Result<usize, Self::Error> {
252        let count: i64 = self
253            .execute(async |pool| {
254                query_scalar(
255                    "
256                    SELECT
257                        COUNT(node_id)
258                    FROM
259                        node_infos_v1
260                    WHERE
261                        bootstrap = TRUE
262                        AND stale = FALSE
263                    ",
264                )
265                .fetch_one(pool)
266                .await
267                .map_err(SqliteError::Sqlite)
268            })
269            .await?;
270
271        Ok(count as usize)
272    }
273
274    async fn selected_node_infos(&self, ids: &[VerifyingKey]) -> Result<Vec<N>, Self::Error> {
275        let result = self
276            .execute(async |pool| {
277                query_as::<_, (Vec<u8>,)>(&format!(
278                    "
279                    SELECT
280                        node_info
281                    FROM
282                        node_infos_v1
283                    WHERE
284                        node_id IN ({})
285                    ",
286                    in_op_str(ids)
287                ))
288                .fetch_all(pool)
289                .await
290                .map_err(SqliteError::Sqlite)
291            })
292            .await?;
293
294        decode_node_infos(result)
295    }
296
297    async fn set_topics(
298        &self,
299        id: VerifyingKey,
300        topics: HashSet<Topic>,
301    ) -> Result<(), Self::Error> {
302        // Remove all previous topics set for this node id and replace it with new values. Both
303        // updates will be executed inside the same atomic transaction.
304        self.tx(async |tx| {
305            query(
306                "
307                DELETE FROM
308                    topics2node_infos_v1
309                WHERE
310                    node_id = ?
311                ",
312            )
313            .bind(id.to_hex())
314            .execute(&mut **tx)
315            .await
316            .map_err(SqliteError::Sqlite)
317        })
318        .await?;
319
320        for topic in topics {
321            self.tx(async |tx| {
322                query(
323                    "
324                    INSERT OR IGNORE
325                    INTO
326                        topics2node_infos_v1 (
327                            node_id,
328                            topic_id
329                        )
330                    VALUES
331                        (?, ?)
332                    ",
333                )
334                .bind(id.to_hex())
335                .bind(topic.to_string())
336                .execute(&mut **tx)
337                .await
338                .map_err(SqliteError::Sqlite)
339            })
340            .await?;
341        }
342
343        Ok(())
344    }
345
346    async fn node_infos_by_topics(&self, topics: &[Topic]) -> Result<Vec<N>, Self::Error> {
347        let result = self
348            .execute(async |pool| {
349                query_as::<_, (Vec<u8>,)>(&format!(
350                    "
351                    SELECT
352                        node_infos_v1.node_info
353                    FROM
354                        node_infos_v1
355                    LEFT JOIN topics2node_infos_v1
356                        ON node_infos_v1.node_id = topics2node_infos_v1.node_id
357                    WHERE
358                        topics2node_infos_v1.topic_id IN ({})
359                        AND node_infos_v1.stale = FALSE
360                    GROUP BY
361                        node_infos_v1.node_id
362                    ",
363                    in_op_str(topics)
364                ))
365                .fetch_all(pool)
366                .await
367                .map_err(SqliteError::Sqlite)
368            })
369            .await?;
370
371        decode_node_infos(result)
372    }
373
374    async fn random_node(&self) -> Result<Option<N>, Self::Error> {
375        let result = self
376            .execute(async |pool| {
377                query_as::<_, (Vec<u8>,)>(
378                    "
379                    SELECT
380                        node_info
381                    FROM
382                        node_infos_v1
383                    WHERE
384                        stale = FALSE
385                    ORDER BY RANDOM()
386                    LIMIT 1
387                    ",
388                )
389                .fetch_optional(pool)
390                .await
391                .map_err(SqliteError::Sqlite)
392            })
393            .await?;
394
395        decode_node_info(result)
396    }
397
398    async fn random_bootstrap_node(&self) -> Result<Option<N>, Self::Error> {
399        let result = self
400            .execute(async |pool| {
401                query_as::<_, (Vec<u8>,)>(
402                    "
403                    SELECT
404                        node_info
405                    FROM
406                        node_infos_v1
407                    WHERE
408                        bootstrap = TRUE
409                        AND stale = FALSE
410                    ORDER BY RANDOM()
411                    LIMIT 1
412                    ",
413                )
414                .fetch_optional(pool)
415                .await
416                .map_err(SqliteError::Sqlite)
417            })
418            .await?;
419
420        decode_node_info(result)
421    }
422}
423
424#[cfg(any(test, feature = "test_utils"))]
425#[doc(hidden)]
426impl SqliteStore {
427    pub async fn set_last_changed(
428        &self,
429        id: &VerifyingKey,
430        timestamp: u64,
431    ) -> Result<(), SqliteError> {
432        self.tx(async |tx| {
433            query(
434                "
435                UPDATE
436                    node_infos_v1
437                SET
438                    updated_at = ?
439                WHERE
440                    node_id = ?
441                ",
442            )
443            .bind(timestamp as i64)
444            .bind(id.to_hex())
445            .execute(&mut **tx)
446            .await
447            .map_err(SqliteError::Sqlite)
448        })
449        .await?;
450
451        Ok(())
452    }
453}
454
455/// Takes a list of items implementing `Display` to turn it into an SQL "IN" operator where each
456/// item is represented as a string.
457///
458/// ```text
459/// SELECT * FROM users
460/// WHERE
461///     id IN ('1a', '2b', '3c');
462/// ```
463fn in_op_str<T: Display>(list: &[T]) -> String {
464    list.iter()
465        .map(|item| format!("'{item}'"))
466        .collect::<Vec<String>>()
467        .join(",")
468}
469
470/// Deserialize multiple rows containing encoded node info.
471fn decode_node_infos<N>(result: Vec<(Vec<u8>,)>) -> Result<Vec<N>, SqliteError>
472where
473    N: NodeInfo<VerifyingKey> + Serialize + for<'a> Deserialize<'a>,
474{
475    result
476        .iter()
477        .map(|item| {
478            decode_cbor(&item.0[..])
479                .map_err(|err| SqliteError::Decode("node_info".to_string(), err.into()))
480        })
481        .collect()
482}
483
484/// Deserialize single row maybe containing encoded node info.
485fn decode_node_info<N>(result: Option<(Vec<u8>,)>) -> Result<Option<N>, SqliteError>
486where
487    N: NodeInfo<VerifyingKey> + Serialize + for<'a> Deserialize<'a>,
488{
489    match result {
490        Some((bytes,)) => {
491            Ok(Some(decode_cbor(&bytes[..]).map_err(|err| {
492                SqliteError::Decode("node_info".to_string(), err.into())
493            })?))
494        }
495        None => Ok(None),
496    }
497}