Skip to main content

modo/db/
conn.rs

1use libsql::params::IntoParams;
2
3use crate::error::{Error, Result};
4
5use super::from_row::FromRow;
6
7/// Low-level query trait implemented for `libsql::Connection` and `libsql::Transaction`.
8///
9/// Provides `query_raw`, `execute_raw`, and the [`select`](Self::select) builder
10/// entry point. Higher-level helpers are on [`ConnQueryExt`], which is blanket-implemented
11/// for all `ConnExt` types.
12pub trait ConnExt: Sync {
13    /// Execute a query and return raw rows.
14    fn query_raw(
15        &self,
16        sql: &str,
17        params: impl IntoParams + Send,
18    ) -> impl std::future::Future<Output = std::result::Result<libsql::Rows, libsql::Error>> + Send;
19
20    /// Execute a statement and return the number of affected rows.
21    fn execute_raw(
22        &self,
23        sql: &str,
24        params: impl IntoParams + Send,
25    ) -> impl std::future::Future<Output = std::result::Result<u64, libsql::Error>> + Send;
26
27    /// Start a composable [`SelectBuilder`](super::SelectBuilder) from a base SQL query.
28    fn select<'a>(&'a self, sql: &str) -> super::select::SelectBuilder<'a, Self>
29    where
30        Self: Sized,
31    {
32        super::select::SelectBuilder::new(self, sql)
33    }
34}
35
36impl ConnExt for libsql::Connection {
37    fn query_raw(
38        &self,
39        sql: &str,
40        params: impl IntoParams + Send,
41    ) -> impl std::future::Future<Output = std::result::Result<libsql::Rows, libsql::Error>> + Send
42    {
43        self.query(sql, params)
44    }
45
46    fn execute_raw(
47        &self,
48        sql: &str,
49        params: impl IntoParams + Send,
50    ) -> impl std::future::Future<Output = std::result::Result<u64, libsql::Error>> + Send {
51        self.execute(sql, params)
52    }
53}
54
55impl ConnExt for libsql::Transaction {
56    fn query_raw(
57        &self,
58        sql: &str,
59        params: impl IntoParams + Send,
60    ) -> impl std::future::Future<Output = std::result::Result<libsql::Rows, libsql::Error>> + Send
61    {
62        self.query(sql, params)
63    }
64
65    fn execute_raw(
66        &self,
67        sql: &str,
68        params: impl IntoParams + Send,
69    ) -> impl std::future::Future<Output = std::result::Result<u64, libsql::Error>> + Send {
70        self.execute(sql, params)
71    }
72}
73
74/// High-level query helpers built on [`ConnExt`].
75///
76/// Blanket-implemented for all `ConnExt` types. Import this trait to use
77/// `query_one`, `query_optional`, `query_all`, and their `_map` variants.
78pub trait ConnQueryExt: ConnExt {
79    /// Fetch the first row as `T` via [`FromRow`].
80    ///
81    /// # Errors
82    ///
83    /// Returns [`Error::not_found`](crate::Error::not_found) if the query returns no rows.
84    fn query_one<T: FromRow + Send>(
85        &self,
86        sql: &str,
87        params: impl IntoParams + Send,
88    ) -> impl std::future::Future<Output = Result<T>> + Send {
89        async move {
90            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
91            let row = rows
92                .next()
93                .await
94                .map_err(Error::from)?
95                .ok_or_else(|| Error::not_found("record not found"))?;
96            T::from_row(&row)
97        }
98    }
99
100    /// Fetch the first row as `T` via [`FromRow`], returning `None` if empty.
101    ///
102    /// # Errors
103    ///
104    /// Returns an error if the query fails or row conversion fails.
105    fn query_optional<T: FromRow + Send>(
106        &self,
107        sql: &str,
108        params: impl IntoParams + Send,
109    ) -> impl std::future::Future<Output = Result<Option<T>>> + Send {
110        async move {
111            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
112            match rows.next().await.map_err(Error::from)? {
113                Some(row) => Ok(Some(T::from_row(&row)?)),
114                None => Ok(None),
115            }
116        }
117    }
118
119    /// Fetch all rows as `Vec<T>` via [`FromRow`].
120    ///
121    /// # Errors
122    ///
123    /// Returns an error if the query fails or any row conversion fails.
124    fn query_all<T: FromRow + Send>(
125        &self,
126        sql: &str,
127        params: impl IntoParams + Send,
128    ) -> impl std::future::Future<Output = Result<Vec<T>>> + Send {
129        async move {
130            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
131            let mut result = Vec::new();
132            while let Some(row) = rows.next().await.map_err(Error::from)? {
133                result.push(T::from_row(&row)?);
134            }
135            Ok(result)
136        }
137    }
138
139    /// Fetch the first row and map it with a closure.
140    ///
141    /// # Errors
142    ///
143    /// Returns [`Error::not_found`](crate::Error::not_found) if the query returns no rows.
144    fn query_one_map<T: Send, F>(
145        &self,
146        sql: &str,
147        params: impl IntoParams + Send,
148        f: F,
149    ) -> impl std::future::Future<Output = Result<T>> + Send
150    where
151        F: Fn(&libsql::Row) -> Result<T> + Send,
152    {
153        async move {
154            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
155            let row = rows
156                .next()
157                .await
158                .map_err(Error::from)?
159                .ok_or_else(|| Error::not_found("record not found"))?;
160            f(&row)
161        }
162    }
163
164    /// Fetch the first row and map it with a closure, returning `None` if empty.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error if the query fails or the mapping closure returns an error.
169    fn query_optional_map<T: Send, F>(
170        &self,
171        sql: &str,
172        params: impl IntoParams + Send,
173        f: F,
174    ) -> impl std::future::Future<Output = Result<Option<T>>> + Send
175    where
176        F: Fn(&libsql::Row) -> Result<T> + Send,
177    {
178        async move {
179            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
180            match rows.next().await.map_err(Error::from)? {
181                Some(row) => Ok(Some(f(&row)?)),
182                None => Ok(None),
183            }
184        }
185    }
186
187    /// Fetch all rows and map each with a closure.
188    ///
189    /// # Errors
190    ///
191    /// Returns an error if the query fails or any mapping closure call returns an error.
192    fn query_all_map<T: Send, F>(
193        &self,
194        sql: &str,
195        params: impl IntoParams + Send,
196        f: F,
197    ) -> impl std::future::Future<Output = Result<Vec<T>>> + Send
198    where
199        F: Fn(&libsql::Row) -> Result<T> + Send,
200    {
201        async move {
202            let mut rows = self.query_raw(sql, params).await.map_err(Error::from)?;
203            let mut result = Vec::new();
204            while let Some(row) = rows.next().await.map_err(Error::from)? {
205                result.push(f(&row)?);
206            }
207            Ok(result)
208        }
209    }
210}
211
212// Blanket implementation: anything that implements ConnExt gets ConnQueryExt for free
213impl<T: ConnExt> ConnQueryExt for T {}