dittolive_ditto/dql/query_v2.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 [`QueryV2`] directly, but instead take a generic
8/// parameter that implements [`IntoQuery`], a trait implemented by types that can be turned into a
9/// [`QueryV2`].
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 [`QueryV2`].
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 QueryV2<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 [`QueryV2`].
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/// Note that, due to historical naming reasons, this trait is not used to create a [`Query`].
56///
57/// [`Query`]: super::query::Query
58/// [`Serialize`]: serde::Serialize
59pub trait IntoQuery {
60 /// The type of the arguments provided with this query
61 type Args;
62
63 /// Convert this object into a [`QueryV2`]
64 fn into_query(self) -> Result<QueryV2<Self::Args>, DittoError>;
65}
66
67/// A query implements [`IntoQuery`] :)
68impl<A> IntoQuery for QueryV2<A> {
69 type Args = A;
70 fn into_query(self) -> Result<QueryV2<Self::Args>, DittoError> {
71 Ok(self)
72 }
73}
74
75impl<Q: ?Sized> IntoQuery for &Q
76where
77 Q: ToOwned,
78 Q::Owned: IntoQuery,
79{
80 type Args = <Q::Owned as IntoQuery>::Args;
81 fn into_query(self) -> Result<QueryV2<Self::Args>, DittoError> {
82 let owned = self.to_owned();
83 owned.into_query()
84 }
85}
86
87impl<Q, A> IntoQuery for (Q, A)
88where
89 Q: IntoQuery<Args = ()>,
90 A: serde::Serialize,
91{
92 type Args = A;
93 fn into_query(self) -> Result<QueryV2<Self::Args>, DittoError> {
94 let (string, args) = self;
95 let string = string.into_query()?.string;
96 let args_cbor = serde_cbor::to_vec(&args).unwrap();
97
98 Ok(QueryV2 {
99 string,
100 args,
101 args_cbor: Some(args_cbor),
102 })
103 }
104}
105
106impl IntoQuery for String {
107 type Args = ();
108 fn into_query(self) -> Result<QueryV2<Self::Args>, DittoError> {
109 Ok(QueryV2 {
110 string: char_p::new(self).into(),
111 args: (),
112 args_cbor: None,
113 })
114 }
115}