1pub mod build;
2
3use rusqlite::types::Value;
4use std::error::Error;
5use std::fmt;
6
7#[derive(Debug)]
8pub struct ParseValueError(pub String);
9
10impl fmt::Display for ParseValueError {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 f.write_str(&self.0)
13 }
14}
15
16impl Error for ParseValueError {}
17
18#[macro_export]
19macro_rules! sql_enum {
20 (
21 $(#[$meta:meta])*
22 case_insensitive: $case_insensitive:literal;
23 $vis:vis enum $name:ident {
24 $(
25 $variant:ident => $display:literal $(| $input:literal)*
26 ),+ $(,)?
27 }
28 ) => {
29 $(#[$meta])*
30 $vis enum $name {
31 $($variant),+
32 }
33
34 impl ::std::fmt::Display for $name {
35 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
36 f.write_str(match self {
37 $(Self::$variant => $display),+
38 })
39 }
40 }
41
42 impl ::std::str::FromStr for $name {
43 type Err = String;
44
45 fn from_str(value: &str) -> Result<Self, Self::Err> {
46 let normalized = if $case_insensitive {
47 value.to_ascii_lowercase()
48 } else {
49 value.to_string()
50 };
51 match normalized.as_str() {
52 $($display $(| $input)* => Ok(Self::$variant),)+
53 _ => Err(format!("unknown {}: {value}", stringify!($name))),
54 }
55 }
56 }
57
58 impl ::rusqlite::types::ToSql for $name {
59 fn to_sql(&self) -> ::rusqlite::Result<::rusqlite::types::ToSqlOutput<'_>> {
60 Ok(::rusqlite::types::ToSqlOutput::Owned(
61 ::rusqlite::types::Value::Text(self.to_string()),
62 ))
63 }
64 }
65
66 impl ::rusqlite::types::FromSql for $name {
67 fn column_result(
68 value: ::rusqlite::types::ValueRef<'_>,
69 ) -> Result<Self, ::rusqlite::types::FromSqlError> {
70 Self::from_str(value.as_str()?).map_err(|error| {
71 ::rusqlite::types::FromSqlError::Other(Box::new(
72 $crate::ParseValueError(error),
73 ))
74 })
75 }
76 }
77 };
78}
79
80#[derive(Debug, Clone, Default, PartialEq)]
81pub struct WhereBuilder {
82 clauses: Vec<String>,
83 bindings: Vec<(String, Value)>,
84}
85
86impl WhereBuilder {
87 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn push(&mut self, clause: impl Into<String>, name: impl Into<String>, value: Value) {
92 self.clauses.push(clause.into());
93 self.push_binding(name, value);
94 }
95
96 pub fn push_binding(&mut self, name: impl Into<String>, value: Value) {
97 self.bindings.push((name.into(), value));
98 }
99
100 pub fn sql(&self) -> String {
101 self.clauses.join(" AND ")
102 }
103
104 pub fn bindings(&self) -> &[(String, Value)] {
105 &self.bindings
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn where_builder_keeps_typed_bindings() {
115 let mut builder = WhereBuilder::new();
116 builder.push("name = :name", ":name", Value::Text("Ada".into()));
117 builder.push_binding(":limit", Value::Integer(10));
118 assert_eq!(builder.sql(), "name = :name");
119 assert_eq!(builder.bindings()[1].1, Value::Integer(10));
120 }
121}