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;
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}