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