Skip to main content

rqlite_rs/query/
mod.rs

1use serde::Serialize;
2use serde_json;
3
4pub mod arguments;
5use crate::error::QueryBuilderError;
6pub(crate) use arguments::RqliteArgument;
7
8/// A query to be executed on the rqlite cluster.
9#[derive(Debug)]
10pub struct RqliteQuery {
11    pub query: String,
12    pub args: Vec<RqliteArgument>,
13    pub op: Operation,
14}
15
16impl TryInto<RqliteQuery> for String {
17    type Error = QueryBuilderError;
18
19    /// Attempts to convert a string into a [`RqliteQuery`].
20    /// Returns a `Result` with the query if the string is valid.
21    /// Fails if the query does not start with a valid operation.
22    /// See [`Operation`] for a list of valid operations.
23    fn try_into(self) -> Result<RqliteQuery, Self::Error> {
24        let op = Operation::from_query_string(self.as_str())?;
25
26        Ok(RqliteQuery {
27            query: self,
28            args: vec![],
29            op,
30        })
31    }
32}
33
34impl TryInto<RqliteQuery> for &str {
35    type Error = QueryBuilderError;
36
37    /// Attempts to convert a string into a [`RqliteQuery`].
38    /// Returns a `Result` with the query if the string is valid.
39    /// Fails if the query does not start with a valid operation.
40    /// See [`Operation`] for a list of valid operations.
41    fn try_into(self) -> Result<RqliteQuery, Self::Error> {
42        let op = Operation::from_query_string(self)?;
43
44        Ok(RqliteQuery {
45            query: self.to_string(),
46            args: vec![],
47            op,
48        })
49    }
50}
51
52impl TryInto<RqliteQuery> for Result<RqliteQuery, QueryBuilderError> {
53    type Error = QueryBuilderError;
54
55    /// Attempts to convert a `Result` into a [`RqliteQuery`].
56    /// Returns a `Result` with the query if the result is valid.
57    /// Fails if the result is an error.
58    /// This allows avoiding the use of `?` in the `query!` macro.
59    fn try_into(self) -> Result<RqliteQuery, Self::Error> {
60        self
61    }
62}
63
64#[derive(Serialize, Debug)]
65struct QueryComponent(RqliteArgument);
66
67// This is a helper struct for serializing multiple queries with arguments.
68#[derive(Serialize, Debug)]
69pub(crate) struct QueryArgs(Vec<Vec<QueryComponent>>);
70
71// This is an internal helper, for creating a `QueryArgs` from a vector of `RqliteQuery`.
72// This is used for batch queries.
73impl From<RqliteQuery> for QueryArgs {
74    fn from(query: RqliteQuery) -> Self {
75        let mut args = Vec::new();
76
77        let mut components = Vec::new();
78
79        components.push(QueryComponent(RqliteArgument::String(query.query)));
80
81        for arg in query.args {
82            components.push(QueryComponent(arg));
83        }
84
85        args.push(components);
86
87        Self(args)
88    }
89}
90
91// This is an internal helper, for creating a `QueryArgs` from a vector of `RqliteQuery`.
92// This is used for batch queries.
93impl From<Vec<RqliteQuery>> for QueryArgs {
94    fn from(queries: Vec<RqliteQuery>) -> Self {
95        let mut args = Vec::new();
96
97        for query in queries {
98            let mut components = Vec::new();
99
100            components.push(QueryComponent(RqliteArgument::String(query.query)));
101
102            for arg in query.args {
103                components.push(QueryComponent(arg));
104            }
105
106            args.push(components);
107        }
108
109        Self(args)
110    }
111}
112
113impl RqliteQuery {
114    pub(crate) fn into_json(self) -> Result<String, serde_json::Error> {
115        let args = QueryArgs::from(self);
116
117        serde_json::to_string(&args)
118    }
119
120    pub(crate) fn endpoint(&self) -> String {
121        let resource = match self.op {
122            Operation::Create
123            | Operation::Update
124            | Operation::Delete
125            | Operation::Insert
126            | Operation::Drop => "execute",
127            Operation::Select | Operation::Pragma => "query",
128        };
129
130        format!("db/{resource}")
131    }
132}
133
134/// The type of operation for a query.
135#[derive(Debug, PartialEq, Eq)]
136pub enum Operation {
137    Create,
138    Select,
139    Update,
140    Delete,
141    Insert,
142    Pragma,
143    Drop,
144}
145
146impl Operation {
147    /// Convert a SQL query string into an [`Operation`].
148    ///
149    /// # Errors
150    /// Returns [`QueryBuilderError::InvalidOperation`] if the query string does not start with a valid operation keyword.
151    pub fn from_query_string(query: &str) -> Result<Self, QueryBuilderError> {
152        match query.to_lowercase() {
153            q if q.starts_with("create") => Ok(Self::Create),
154            q if q.starts_with("select") => Ok(Self::Select),
155            q if q.starts_with("update") => Ok(Self::Update),
156            q if q.starts_with("delete") => Ok(Self::Delete),
157            q if q.starts_with("insert") => Ok(Self::Insert),
158            q if q.starts_with("pragma") => Ok(Self::Pragma),
159            q if q.starts_with("drop") => Ok(Self::Drop),
160            _ => Err(QueryBuilderError::InvalidOperation(query.to_string())),
161        }
162    }
163}
164
165/// A macro for creating a query.
166/// Returns a `Result` with an [`RqliteQuery`] if the query is valid.
167/// The macro accepts a query string and optional arguments.
168///
169/// # Examples
170///
171/// ```
172/// use rqlite_rs::query;
173///
174/// let query = query!("SELECT * FROM foo");
175/// assert!(query.is_ok());
176///
177/// let query = query!("SELECT * FROM foo WHERE id = ? AND name = ?", 1i64, "bar");
178/// assert!(query.is_ok());
179/// ```
180#[macro_export]
181macro_rules! query {
182    // This is the base case, it only accepts a query string.
183    // In this macro named blocks are used to allow using early returns.
184    ( $query:expr ) => {{
185        'blk: {
186            let Ok(op) = $crate::query::Operation::from_query_string($query) else { break 'blk Err($crate::error::QueryBuilderError::InvalidQuery($query.to_string())) };
187
188            Ok($crate::query::RqliteQuery {
189                query: $query.to_string(),
190                args: vec![],
191                op,
192            })
193        }
194    }};
195    ( $query:expr, $( $args:expr ),* ) => {{
196        'blk: {
197            let Ok(query) = ($crate::query!($query)) else {
198                break 'blk Err($crate::error::QueryBuilderError::InvalidQuery($query.to_string()));
199            };
200
201            let param_count = $query.matches("?").count();
202
203            let mut args = vec![];
204
205            $(
206                let arg = $crate::arg!($args);
207                args.push(arg);
208            )*
209
210            let argc = args.len();
211
212            if argc != param_count {
213                break 'blk Err($crate::error::QueryBuilderError::InvalidArgumentCount(param_count, argc));
214            }
215
216            Ok($crate::query::RqliteQuery {
217                query: query.query,
218                args,
219                op: query.op,
220            })
221        }
222    }};
223}
224
225#[cfg(test)]
226mod tests {
227    use crate::query::QueryArgs;
228
229    // This is a unit test for the query macro.
230    #[test]
231    fn unit_query_macro_correct_query_types() {
232        let query = query!("CREATE TABLE foo (id INTEGER PRIMARY KEY)");
233        assert!(query.is_ok());
234        assert_eq!(query.unwrap().op, crate::query::Operation::Create);
235
236        let query = query!("SELECT * FROM foo");
237        assert!(query.is_ok());
238        assert_eq!(query.unwrap().op, crate::query::Operation::Select);
239
240        let query = query!("UPDATE foo SET name = 'bar' WHERE id = 1");
241        assert!(query.is_ok());
242        assert_eq!(query.unwrap().op, crate::query::Operation::Update);
243
244        let query = query!("DELETE FROM foo WHERE id = 1");
245        assert!(query.is_ok());
246        assert_eq!(query.unwrap().op, crate::query::Operation::Delete);
247
248        let query = query!("INSERT INTO foo (name) VALUES ('bar')");
249        assert!(query.is_ok());
250        assert_eq!(query.unwrap().op, crate::query::Operation::Insert);
251
252        let query = query!("PRAGMA table_info(foo)");
253        assert!(query.is_ok());
254        assert_eq!(query.unwrap().op, crate::query::Operation::Pragma);
255
256        let query = query!("DROP TABLE foo");
257        assert!(query.is_ok());
258        assert_eq!(query.unwrap().op, crate::query::Operation::Drop);
259    }
260
261    #[test]
262    fn unit_query_macro_incorrect_query_types() {
263        let query = query!("TEST * FROM foo");
264        assert!(query.is_err());
265    }
266
267    #[test]
268    fn unit_query_macro_query_args() {
269        let query = query!("SELECT * FROM foo WHERE id = ?", 1i64);
270        assert!(query.is_ok());
271        assert_eq!(query.unwrap().args.len(), 1);
272
273        let query = query!("SELECT * FROM foo WHERE id = ?", 1i64, "foo");
274        assert!(query.is_err());
275        assert!(matches!(
276            query.unwrap_err(),
277            crate::error::QueryBuilderError::InvalidArgumentCount(1, 2)
278        ));
279    }
280
281    #[test]
282    fn unit_query_macro_try_into_from_string_slice() {
283        let query: Result<crate::query::RqliteQuery, crate::error::QueryBuilderError> =
284            "SELECT * FROM foo".try_into();
285        assert!(query.is_ok());
286        assert_eq!(query.unwrap().op, crate::query::Operation::Select);
287    }
288
289    #[test]
290    fn unit_query_macro_try_into_from_string() {
291        let query: Result<crate::query::RqliteQuery, crate::error::QueryBuilderError> =
292            "SELECT * FROM foo".to_string().try_into();
293        assert!(query.is_ok());
294        assert_eq!(query.unwrap().op, crate::query::Operation::Select);
295    }
296
297    #[test]
298    fn unit_query_macro_try_into_from_result_err() {
299        let query: Result<crate::query::RqliteQuery, crate::error::QueryBuilderError> = Err(
300            crate::error::QueryBuilderError::InvalidQuery("TEST * FROM foo".to_string()),
301        )
302        .try_into();
303        assert!(query.is_err());
304    }
305
306    #[test]
307    fn unit_query_macro_query_args_from_query() {
308        let query = query!("SELECT * FROM foo WHERE id = ?", 1i64);
309        assert!(query.is_ok());
310
311        let query_args = QueryArgs::from(query.unwrap());
312        assert_eq!(query_args.0.len(), 1);
313        assert_eq!(query_args.0[0].len(), 2);
314    }
315
316    #[test]
317    fn unit_query_macro_query_args_from_query_vec() {
318        let query_1 = query!("SELECT * FROM foo WHERE id = ?", 1i64);
319        let query_2 = query!("SELECT * FROM foo WHERE id = ?", 2i64);
320        assert!(query_1.is_ok());
321        assert!(query_2.is_ok());
322
323        let query_args = QueryArgs::from(vec![query_1.unwrap(), query_2.unwrap()]);
324        assert_eq!(query_args.0.len(), 2);
325        assert_eq!(query_args.0[0].len(), 2);
326        assert_eq!(query_args.0[1].len(), 2);
327    }
328
329    #[test]
330    fn unit_query_macro_query_to_json() {
331        let query = query!("SELECT * FROM foo WHERE id = ?", 1i64);
332        assert!(query.is_ok());
333
334        let json = query.unwrap().into_json();
335        assert_eq!(json.unwrap(), r#"[["SELECT * FROM foo WHERE id = ?",1]]"#);
336    }
337
338    #[test]
339    fn unit_query_macro_query_endpoint() {
340        let query = query!("SELECT * FROM foo WHERE id = ?", 1i64);
341        assert!(query.is_ok());
342
343        let endpoint = query.unwrap().endpoint();
344        assert_eq!(endpoint, "db/query");
345    }
346}