entertainarr_adapter_sqlite/
lib.rs

1use std::borrow::Cow;
2
3use anyhow::Context;
4
5mod auth;
6mod language;
7mod media;
8mod podcast;
9mod podcast_episode;
10mod podcast_task;
11mod prelude;
12mod task;
13mod tvshow;
14mod tvshow_episode;
15mod tvshow_episode_file;
16mod tvshow_season;
17
18#[derive(serde::Deserialize)]
19pub struct Config {
20    #[serde(default = "Config::default_url")]
21    pub url: Cow<'static, str>,
22}
23
24impl Default for Config {
25    fn default() -> Self {
26        Self {
27            url: Self::default_url(),
28        }
29    }
30}
31
32impl Config {
33    pub const fn default_url() -> Cow<'static, str> {
34        Cow::Borrowed(":memory:")
35    }
36
37    pub async fn build(self) -> anyhow::Result<Pool> {
38        let options = sqlx::sqlite::SqliteConnectOptions::new();
39        let options = match self.url.as_ref() {
40            ":memory:" => options.in_memory(true),
41            path => options.filename(path).create_if_missing(true),
42        };
43        let pool = sqlx::sqlite::SqlitePoolOptions::new()
44            .min_connections(1)
45            .connect_with(options)
46            .await?;
47
48        tracing::info!("running migrations");
49        sqlx::migrate!()
50            .run(&pool)
51            .await
52            .context("unable to run migrations")?;
53
54        Ok(Pool(pool))
55    }
56}
57
58#[derive(Debug, Clone)]
59pub struct Pool(sqlx::SqlitePool);
60
61impl AsRef<sqlx::SqlitePool> for Pool {
62    fn as_ref(&self) -> &sqlx::SqlitePool {
63        &self.0
64    }
65}
66
67#[cfg(test)]
68impl Pool {
69    pub async fn test(path: &std::path::Path) -> Self {
70        Config {
71            url: Cow::Owned(path.to_string_lossy().to_string()),
72        }
73        .build()
74        .await
75        .unwrap()
76    }
77}
78
79struct Wrapper<T>(T);
80
81impl<T> Wrapper<T> {
82    fn maybe_inner(this: Option<Self>) -> Option<T> {
83        this.map(Wrapper::inner)
84    }
85
86    fn inner(self) -> T {
87        self.0
88    }
89
90    fn list(values: Vec<Wrapper<T>>) -> Vec<T> {
91        values.into_iter().map(Wrapper::inner).collect()
92    }
93}
94
95fn record_one<T>(_: &T) {
96    let span = tracing::Span::current();
97    span.record("db.response.returned_rows", 1);
98}
99
100fn record_optional<T>(item: &Option<T>) {
101    let span = tracing::Span::current();
102    span.record(
103        "db.response.returned_rows",
104        if item.is_some() { 1 } else { 0 },
105    );
106}
107
108#[allow(clippy::ptr_arg, reason = "needed by sqlx")]
109fn record_all<T>(list: &Vec<T>) {
110    let span = tracing::Span::current();
111    span.record("db.response.returned_rows", list.len());
112}
113
114fn record_error(err: &sqlx::Error) {
115    let span = tracing::Span::current();
116    span.record(
117        "error.type",
118        if err.as_database_error().is_some() {
119            "client"
120        } else {
121            "server"
122        },
123    );
124    span.record("error.message", err.to_string());
125    span.record("error.stacktrace", format!("{err:?}"));
126}
127
128#[derive(Debug, Default)]
129struct IndexIter(usize);
130
131impl IndexIter {
132    pub fn next(&mut self) -> usize {
133        let current = self.0;
134        self.0 += 1;
135        current
136    }
137}