entertainarr_adapter_sqlite/
lib.rs1use 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}