nanosql/query.rs
1//! Strongly-typed queries.
2//!
3//! Consult the official [SQLite documentation](https://www.sqlite.org/lang.html) on the
4//! supported queries for an in-depth explanation of the precise SQL understood by SQLite.
5
6use core::fmt::{self, Display, Formatter};
7use crate::param::Param;
8use crate::row::ResultSet;
9
10
11/// Describes the input (parameter) and output (relation/row/tuple)
12/// types of a query, as well as its actual SQL source text.
13///
14/// Consult the official [SQLite documentation](https://www.sqlite.org/lang.html) on the
15/// supported queries for an in-depth explanation of the precise SQL understood by SQLite.
16pub trait Query {
17 /// The parameter type of the query. This must be either of the following:
18 ///
19 /// * a scalar (integer, floating-point number, string, blob, or null/unit);
20 /// * an ordered tuple (or tuple struct) of scalars;
21 /// * a struct with named fields of scalar type;
22 /// * a map with string-like keys and scalar values;
23 /// * or a newtype or anything that implements [`Param`] like any of the items above.
24 ///
25 /// The lifetime parameter allows the implementor to use a type containing
26 /// references, so as to avoid allocations when binding strings and blobs.
27 type Input<'p>: Param;
28
29 /// The result type returned by the query. This must be either of the following:
30 ///
31 /// * a scalar (integer, floating-point number, string, blob, or null/unit);
32 /// * an ordered tuple (or tuple struct) of scalars;
33 /// * a struct with named fields of scalar type;
34 /// * a map with string-like keys and scalar values;
35 /// * a sequence of any of the items above;
36 /// * or a newtype or any other type that deserializes as such (via [`ResultSet`]).
37 type Output: ResultSet;
38
39 /// Provides the SQL source text of the query.
40 fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result;
41
42 /// Returns a formatter object that displays the SQL text for this query.
43 fn display_sql(&self) -> SqlDisplay<&Self> {
44 SqlDisplay::new(self)
45 }
46}
47
48impl<Q> Query for &Q
49where
50 Q: ?Sized + Query
51{
52 type Input<'p> = Q::Input<'p>;
53 type Output = Q::Output;
54
55 fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
56 Q::format_sql(&**self, formatter)
57 }
58}
59
60impl<Q> Query for &mut Q
61where
62 Q: ?Sized + Query
63{
64 type Input<'p> = Q::Input<'p>;
65 type Output = Q::Output;
66
67 fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
68 Q::format_sql(&**self, formatter)
69 }
70}
71
72impl<Q> Query for Box<Q>
73where
74 Q: ?Sized + Query
75{
76 type Input<'p> = Q::Input<'p>;
77 type Output = Q::Output;
78
79 fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
80 Q::format_sql(&**self, formatter)
81 }
82}
83
84/// Formats the SQL source of a query (an adapter between `Query` and `Display`)
85#[derive(Clone, Copy, Debug)]
86pub struct SqlDisplay<Q: ?Sized> {
87 /// The query of which the SQL text is to be displayed.
88 pub query: Q,
89}
90
91impl<Q> SqlDisplay<Q> {
92 /// Creates a new `SqlDisplay`, wrapping a specific query instance.
93 pub const fn new(query: Q) -> Self {
94 SqlDisplay { query }
95 }
96
97 /// Returns ownership of the wrapped query back to the caller.
98 pub fn into_inner(self) -> Q {
99 self.query
100 }
101}
102
103impl<Q: Query + ?Sized> Display for SqlDisplay<Q> {
104 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
105 self.query.format_sql(formatter)
106 }
107}
108
109/// Creates a new `struct` and implements [`Query`] for it using a function-like syntax.
110/// The invocation looks like the following:
111///
112/// ```ignore
113/// define_query!{
114/// QueryName<'lt>: InputType<'lt> => OutputType { "SQL (impl Display)" }
115/// }
116/// ```
117///
118/// The query name may be preceded by a visibility specifier (e.g. `pub`) to control the scope,
119/// just like normal Rust UDT declarations. Likewise, it may also be preceded by `#[attributes]`
120/// such as `#[derive(Clone, Copy, Default)]` or documentation comments (which expand to such
121/// an attribute). These will all be forwarded to the definition of the query type itsef.
122///
123/// The macro brings the lifetime `'lt` into scope when binding the input type, so
124/// you can use it for defining the input type as a reference or reference-like type.
125/// The SQL expression may borrow immutably from `self` and may use the `?` operator
126/// to return an error when building the SQL query string.
127///
128/// You can declare multiple queries in the same invocation by repeating the above pattern.
129///
130/// Example:
131///
132/// ```rust
133/// # use nanosql::{Result, Connection, ConnectionExt, Param, ResultRecord, Table};
134/// #[derive(Clone, Copy, Debug, Param)]
135/// #[nanosql(param_prefix = '@')]
136/// struct YoungEmployeesByNameParams<'n> {
137/// name: &'n str,
138/// max_age: usize,
139/// }
140///
141/// #[derive(Clone, Default, Debug, Param, ResultRecord, Table)]
142/// struct Employee {
143/// id: u64,
144/// name: String,
145/// age: usize,
146/// boss_id: u64,
147/// }
148///
149/// nanosql::define_query! {
150/// // A simple query that only uses built-in types.
151/// pub PetNameById<'p>: i64 => Option<String> {
152/// "SELECT name FROM pet WHERE id = ?"
153/// }
154///
155/// // A more involved query that uses the domain types defined above.
156/// pub(crate) YoungEmployeesByName<'p>: YoungEmployeesByNameParams<'p> => Vec<Employee> {
157/// r#"
158/// SELECT id, name, age, boss_id
159/// FROM employee
160/// WHERE name LIKE @name AND age <= @max_age
161/// "#
162/// }
163/// }
164///
165/// fn main() -> Result<()> {
166/// let mut conn = Connection::connect_in_memory()?;
167/// #
168/// # conn.create_table::<Employee>()?;
169/// # conn.insert_batch([
170/// # Employee {
171/// # id: 1,
172/// # name: "Alice".into(),
173/// # age: 18,
174/// # boss_id: 0,
175/// # },
176/// # Employee {
177/// # id: 1,
178/// # name: "Joe".into(),
179/// # age: 19,
180/// # boss_id: 0,
181/// # },
182/// # Employee {
183/// # id: 1,
184/// # name: "Joe".into(),
185/// # age: 20,
186/// # boss_id: 0,
187/// # },
188/// # Employee {
189/// # id: 1,
190/// # name: "Joe".into(),
191/// # age: 22,
192/// # boss_id: 0,
193/// # },
194/// # ])?;
195///
196/// // Compile the query
197/// let mut stmt = conn.compile(YoungEmployeesByName)?;
198///
199/// // Get all employees named Joe under 21
200/// // (details of creating and populating the table have been omitted)
201/// let employees: Vec<Employee> = stmt.invoke(YoungEmployeesByNameParams {
202/// name: "Joe",
203/// max_age: 21,
204/// })?;
205///
206/// // suppose there are 2 of them
207/// assert_eq!(employees.len(), 2);
208///
209/// Ok(())
210/// }
211/// ```
212#[macro_export]
213macro_rules! define_query {
214 ($(
215 $(#[$($attrs:tt)*])*
216 $vis:vis $tyname:ident<$lt:lifetime>: $input_ty:ty => $output_ty:ty { $sql:expr }
217 )*) => {$(
218 $(#[$($attrs)*])*
219 $vis struct $tyname;
220
221 impl $crate::Query for $tyname {
222 type Input<$lt> = $input_ty;
223 type Output = $output_ty;
224
225 fn format_sql(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
226 ::core::fmt::Display::fmt(&$sql, formatter)
227 }
228 }
229 )*}
230}