Skip to main content

strata_kernel/
lib.rs

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}