mecomp_storage/db/
mod.rs

1#[cfg(feature = "db")]
2pub mod crud;
3#[cfg(feature = "db")]
4pub mod health;
5#[cfg(feature = "db")]
6pub mod queries;
7pub mod schemas;
8
9#[cfg(feature = "db")]
10use surrealdb::{engine::local::Db, Surreal};
11
12#[cfg(feature = "db")]
13#[cfg(not(tarpaulin_include))]
14static DB_DIR: once_cell::sync::OnceCell<std::path::PathBuf> = once_cell::sync::OnceCell::new();
15#[cfg(feature = "db")]
16#[cfg(not(tarpaulin_include))]
17static TEMP_DB_DIR: once_cell::sync::Lazy<tempfile::TempDir> = once_cell::sync::Lazy::new(|| {
18    tempfile::tempdir().expect("Failed to create temporary directory")
19});
20
21/// NOTE: if you change this, you must go through the schemas and update the index analyzer names
22pub const FULL_TEXT_SEARCH_ANALYZER_NAME: &str = "custom_analyzer";
23
24/// Set the path to the database.
25///
26/// # Errors
27///
28/// This function will return an error if the path cannot be set.
29#[cfg(feature = "db")]
30pub fn set_database_path(path: std::path::PathBuf) -> Result<(), crate::errors::Error> {
31    DB_DIR
32        .set(path)
33        .map_err(crate::errors::Error::DbPathSetError)?;
34    log::info!("Primed database path");
35    Ok(())
36}
37
38/// Initialize the database with the necessary tables.
39///
40/// # Errors
41///
42/// This function will return an error if the database cannot be initialized.
43#[cfg(feature = "db")]
44pub async fn init_database() -> surrealdb::Result<Surreal<Db>> {
45    let db = Surreal::new(DB_DIR
46        .get().cloned()
47        .unwrap_or_else(|| {
48            log::warn!("DB_DIR not set, defaulting to a temporary directory `{}`, this is likely a bug because `init_database` should be called before `db`", TEMP_DB_DIR.path().display());
49            TEMP_DB_DIR.path()
50            .to_path_buf()
51        })).await?;
52
53    db.use_ns("mecomp").use_db("music").await?;
54
55    register_custom_analyzer(&db).await?;
56    surrealqlx::register_tables!(
57        &db,
58        schemas::album::Album,
59        schemas::artist::Artist,
60        schemas::song::Song,
61        schemas::collection::Collection,
62        schemas::playlist::Playlist,
63        schemas::dynamic::DynamicPlaylist
64    )?;
65    #[cfg(feature = "analysis")]
66    surrealqlx::register_tables!(&db, schemas::analysis::Analysis)?;
67
68    Ok(db)
69}
70
71#[cfg(feature = "db")]
72pub(crate) async fn register_custom_analyzer<C>(db: &Surreal<C>) -> surrealdb::Result<()>
73where
74    C: surrealdb::Connection,
75{
76    use queries::define_analyzer;
77    use surrealdb::sql::Tokenizer;
78
79    db.query(define_analyzer(
80        FULL_TEXT_SEARCH_ANALYZER_NAME,
81        Some(Tokenizer::Class),
82        &[
83            "ascii",
84            "lowercase",
85            "edgengram(1, 10)",
86            "snowball(english)",
87        ],
88    ))
89    .await?;
90
91    Ok(())
92}
93
94#[cfg(test)]
95mod test {
96    use super::schemas::{
97        album::Album, artist::Artist, collection::Collection, dynamic::DynamicPlaylist,
98        playlist::Playlist, song::Song,
99    };
100    use super::*;
101
102    use surrealdb::engine::local::Mem;
103    use surrealqlx::traits::Table;
104
105    #[tokio::test]
106    async fn test_register_tables() -> anyhow::Result<()> {
107        // use an in-memory db for testing
108        let db = Surreal::new::<Mem>(()).await?;
109        db.use_ns("test").use_db("test").await?;
110
111        // register the custom analyzer
112        register_custom_analyzer(&db).await?;
113
114        // first we init all the table to ensure that the queries made by the macro work without error
115        <Album as Table>::init_table(&db).await?;
116        <Artist as Table>::init_table(&db).await?;
117        <Song as Table>::init_table(&db).await?;
118        <Collection as Table>::init_table(&db).await?;
119        <Playlist as Table>::init_table(&db).await?;
120        <DynamicPlaylist as Table>::init_table(&db).await?;
121        // then we try initializing one of the tables again to ensure that initialization won't mess with existing tables/data
122        <Album as Table>::init_table(&db).await?;
123
124        Ok(())
125    }
126}