dittolive_ditto/dql/
query.rs

1use safer_ffi::char_p;
2
3use crate::{error::DittoError, utils::zstr::ZString};
4
5/// A DQL query string with its arguments
6///
7/// Most APIs in the Ditto SDK don't take a [`Query`] directly, but instead take a generic
8/// parameter that implements [`IntoQuery`], a trait implemented by types that can be turned into a
9/// [`Query`].
10///
11/// Common examples are:
12/// - `String` (and string-like types)
13/// - `(String, Args)` where `Args` is anything that implements `Serialize`
14///
15/// ```rust
16/// # use dittolive_ditto::prelude::*;
17/// # use serde_json::json;
18/// let select_query = "SELECT * FROM cars".into_query();
19///
20/// let insert_query = (
21///     "INSERT INTO cars DOCUMENTS (:doc)",
22///     json!({"doc": {"foo": "bar"}}),
23/// ).into_query();
24/// ```
25///
26/// ## The `Args` type
27///
28/// This type is generic over its arguments, with the requirement that the arguments must:
29/// - implement `Serialize`
30/// - serialize to a map-like type (e.g. [`serde_json::json!({"some":
31/// "object"})`][serde_json::json] or [`HashMap`][std::collections::HashMap], not a `String`)
32///
33/// If the arguments are mutated using interior mutability, the result is not specified and may
34/// cause logic errors. Note that this is **not** [undefined behaviour][ub], since any errors will
35/// be confined to this instance of [`Query`].
36///
37/// When no arguments are provided, the `Args` type defaults to `()`
38///
39/// [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
40#[derive(Debug, Clone, PartialEq)]
41pub struct Query<Args> {
42    pub(crate) string: ZString,
43    pub(crate) args: Args,
44    pub(crate) args_cbor: Option<Vec<u8>>,
45}
46
47/// Types which can be used to construct a [`Query`].
48///
49/// The main implementors are:
50/// - String types (e.g. `String`, `&str`, etc.)
51/// - Tuples `(S, A)`, where `S` is a string type, and `A` implements [`Serialize`]
52///
53/// This conversion may be fallible.
54///
55/// [`Serialize`]: serde::Serialize
56pub trait IntoQuery {
57    /// The type of the arguments provided with this query
58    type Args;
59
60    /// Convert this object into a [`Query`]
61    fn into_query(self) -> Result<Query<Self::Args>, DittoError>;
62}
63
64/// A query implements [`IntoQuery`] :)
65impl<A> IntoQuery for Query<A> {
66    type Args = A;
67    fn into_query(self) -> Result<Query<Self::Args>, DittoError> {
68        Ok(self)
69    }
70}
71
72impl<Q: ?Sized> IntoQuery for &Q
73where
74    Q: ToOwned,
75    Q::Owned: IntoQuery,
76{
77    type Args = <Q::Owned as IntoQuery>::Args;
78    fn into_query(self) -> Result<Query<Self::Args>, DittoError> {
79        let owned = self.to_owned();
80        owned.into_query()
81    }
82}
83
84impl<Q, A> IntoQuery for (Q, A)
85where
86    Q: IntoQuery<Args = ()>,
87    A: serde::Serialize,
88{
89    type Args = A;
90    fn into_query(self) -> Result<Query<Self::Args>, DittoError> {
91        let (string, args) = self;
92        let string = string.into_query()?.string;
93        let args_cbor = serde_cbor::to_vec(&args).unwrap();
94
95        Ok(Query {
96            string,
97            args,
98            args_cbor: Some(args_cbor),
99        })
100    }
101}
102
103impl IntoQuery for String {
104    type Args = ();
105    fn into_query(self) -> Result<Query<Self::Args>, DittoError> {
106        Ok(Query {
107            string: char_p::new(self).into(),
108            args: (),
109            args_cbor: None,
110        })
111    }
112}