seshat/database/
connection.rs

1// Copyright 2019 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/// A Seshat database connection.
16/// The connection can be used to read data out of the database using a
17/// separate thread.
18use std::ops::{Deref, DerefMut};
19use std::path::PathBuf;
20
21use fs_extra::dir;
22use r2d2::PooledConnection;
23use r2d2_sqlite::SqliteConnectionManager;
24
25use crate::{
26    config::LoadConfig,
27    error::Result,
28    events::{CrawlerCheckpoint, Profile, SerializedEvent},
29    Database,
30};
31
32/// Statistical information about the database.
33#[derive(Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct DatabaseStats {
36    /// The number number of bytes the database is using on disk.
37    pub size: u64,
38    /// The number of events that the database knows about.
39    pub event_count: u64,
40    /// The number of rooms that the database knows about.
41    pub room_count: u64,
42}
43
44/// A Seshat database connection that can be used for reading.
45pub struct Connection {
46    pub(crate) inner: PooledConnection<SqliteConnectionManager>,
47    pub(crate) path: PathBuf,
48}
49
50impl Connection {
51    /// Load all the previously stored crawler checkpoints from the database.
52    /// # Arguments
53    pub fn load_checkpoints(&self) -> Result<Vec<CrawlerCheckpoint>> {
54        let mut stmt = self.prepare(
55            "SELECT room_id, token, full_crawl, direction
56                                    FROM crawlercheckpoints",
57        )?;
58
59        let rows = stmt.query_map([], |row| {
60            Ok(CrawlerCheckpoint {
61                room_id: row.get(0)?,
62                token: row.get(1)?,
63                full_crawl: row.get(2)?,
64                direction: row.get(3)?,
65            })
66        })?;
67
68        let mut checkpoints = Vec::new();
69
70        for row in rows {
71            let checkpoint: CrawlerCheckpoint = row?;
72            checkpoints.push(checkpoint);
73        }
74        Ok(checkpoints)
75    }
76
77    /// Is the database empty.
78    /// Returns true if the database is empty, false otherwise.
79    pub fn is_empty(&self) -> Result<bool> {
80        let event_count: i64 = Database::get_event_count(&self.inner)?;
81        let checkpoint_count: i64 =
82            self.query_row("SELECT COUNT(*) FROM crawlercheckpoints", [], |row| {
83                row.get(0)
84            })?;
85
86        Ok(event_count == 0 && checkpoint_count == 0)
87    }
88
89    /// Is a room already indexed.
90    ///
91    /// Returns true if the database contains events from a room, false
92    /// otherwise.
93    pub fn is_room_indexed(&self, room_id: &str) -> Result<bool> {
94        let event_count: i64 = Database::get_event_count_for_room(&self.inner, room_id)?;
95        let checkpoint_count: i64 = self.query_row(
96            "SELECT COUNT(*) FROM crawlercheckpoints WHERE room_id=?1",
97            [room_id],
98            |row| row.get(0),
99        )?;
100
101        Ok(event_count != 0 || checkpoint_count != 0)
102    }
103
104    /// Get statistical information of the database.
105    pub fn get_stats(&self) -> Result<DatabaseStats> {
106        let event_count = Database::get_event_count(&self.inner)? as u64;
107        let room_count = Database::get_room_count(&self.inner)? as u64;
108        let size = dir::get_size(&self.path)?;
109        Ok(DatabaseStats {
110            size,
111            event_count,
112            room_count,
113        })
114    }
115
116    /// Load events that contain an mxc URL to a file.
117    /// # Arguments
118    ///
119    /// * `load_config` - Configuration deciding which events and how many of
120    ///   them should be loaded.
121    ///
122    /// # Examples
123    ///
124    /// ```noexecute
125    /// let config = LoadConfig::new("!testroom:localhost").limit(10);
126    /// let result = connection.load_file_events(&config);
127    /// ```
128    ///
129    /// Returns a list of tuples containing the serialized events and the
130    ///   profile of the sender at the time when the event was sent.
131    pub fn load_file_events(
132        &self,
133        load_config: &LoadConfig,
134    ) -> Result<Vec<(SerializedEvent, Profile)>> {
135        Ok(Database::load_file_events(
136            self,
137            &load_config.room_id,
138            load_config.limit,
139            load_config.from_event.as_deref(),
140            &load_config.direction,
141        )?)
142    }
143
144    /// Get the user version stored in the database.
145    ///
146    /// This version isn't used anywhere internally and can be set by the user
147    /// to signal changes between the JSON that gets stored inside of Seshat.
148    pub fn get_user_version(&self) -> Result<i64> {
149        Database::get_user_version(self)
150    }
151
152    /// Set the user version to the given version.
153    ///
154    /// # Arguments
155    ///
156    /// * `version` - The new version that will be stored in the database.
157    pub fn set_user_version(&self, version: i64) -> Result<()> {
158        Database::set_user_version(self, version)
159    }
160}
161
162impl Deref for Connection {
163    type Target = PooledConnection<SqliteConnectionManager>;
164
165    fn deref(&self) -> &Self::Target {
166        &self.inner
167    }
168}
169
170impl DerefMut for Connection {
171    fn deref_mut(&mut self) -> &mut Self::Target {
172        &mut self.inner
173    }
174}