quick_oxibooks_sql/
condition.rs1use std::fmt::{Display, Write};
2
3#[macro_export]
13#[cfg(feature = "macros")]
14macro_rules! qb_where {
15 ($item:ty, $first:ident$(.$nested:ident)*, $op:expr) => {
16 {
17 const _: () = {
18 fn _type_check(v: $item) {
19 let _ = v.$first$(.unwrap().$nested)*;
20 }
21 };
22 unsafe {
23 $crate::paste! {
24 TypedWhereClause::<$item>::new(
25 stringify!([<$first:camel>]$(.[<$nested:camel>])*),
26 $op,
27 )
28 }
29 }
30 }
31 };
32}
33
34#[derive(Debug, PartialEq, Clone)]
36pub struct TypedWhereClause<QB> {
37 pub field: &'static str,
38 pub operator: Operator,
39 pub values: Vec<String>,
40 _phantom: std::marker::PhantomData<QB>,
41}
42
43impl<QB> TypedWhereClause<QB> {
44 #[must_use]
51 pub unsafe fn new(field: &'static str, operator: Operator) -> Self {
52 Self {
53 field,
54 operator,
55 values: Vec::new(),
56 _phantom: std::marker::PhantomData,
57 }
58 }
59
60 #[must_use]
62 pub fn add_value<T: Display>(mut self, value: T) -> Self {
63 self.values.push(value.to_string());
64 self
65 }
66
67 #[must_use]
69 pub fn add_values<I, T>(mut self, values: I) -> Self
70 where
71 I: Iterator<Item = T>,
72 T: Display,
73 {
74 self.values.extend(values.map(|v| v.to_string()));
75 self
76 }
77}
78
79impl<T> From<TypedWhereClause<T>> for WhereClause {
80 fn from(val: TypedWhereClause<T>) -> Self {
81 WhereClause {
82 field: val.field,
83 operator: val.operator,
84 values: val.values,
85 }
86 }
87}
88
89#[cfg(test)]
90mod typed_where_tests {
91 use super::*;
92 use quickbooks_types::Customer;
93
94 #[test]
95 fn test_typed_where_clause_creation() {
96 #[cfg(feature = "macros")]
97 let clause = qb_where!(Customer, display_name, Operator::Like);
98 #[cfg(not(feature = "macros"))]
99 let clause: TypedWhereClause<Customer> =
100 unsafe { TypedWhereClause::new("DisplayName", Operator::Like) };
101 assert_eq!(clause.field, "DisplayName");
102 assert_eq!(clause.operator, Operator::Like);
103 }
104
105 #[test]
106 fn test_nested_typed_where_clause_creation() {
107 #[cfg(feature = "macros")]
108 let clause = qb_where!(Customer, primary_email_addr.address, Operator::Equal);
109 #[cfg(not(feature = "macros"))]
110 let clause: TypedWhereClause<Customer> =
111 unsafe { TypedWhereClause::new("PrimaryEmailAddr.Address", Operator::Equal) };
112 assert_eq!(clause.field, "PrimaryEmailAddr.Address");
113 assert_eq!(clause.operator, Operator::Equal);
114 }
115}
116
117#[derive(Debug, PartialEq, Clone)]
119pub struct WhereClause {
120 pub field: &'static str,
121 pub operator: Operator,
122 pub values: Vec<String>,
123}
124
125impl WhereClause {
126 #[must_use]
128 pub fn new(field: &'static str, operator: Operator) -> Self {
129 Self {
130 field,
131 operator,
132 values: Vec::new(),
133 }
134 }
135
136 #[must_use]
138 pub fn add_value<T: Display>(mut self, value: T) -> Self {
139 self.values.push(value.to_string());
140 self
141 }
142
143 #[must_use]
145 pub fn add_values<I, T>(mut self, values: I) -> Self
146 where
147 I: Iterator<Item = T>,
148 T: Display,
149 {
150 self.values.extend(values.map(|v| v.to_string()));
151 self
152 }
153}
154
155impl WhereClause {
156 pub fn extend_query(&self, query: &mut String) {
157 let op_str = match self.operator {
158 Operator::In => "IN",
159 Operator::Like => "LIKE",
160 Operator::Equal => "=",
161 Operator::Less => "<",
162 Operator::Greater => ">",
163 Operator::LessEqual => "<=",
164 Operator::GreaterEqual => ">=",
165 };
166
167 if self.operator == Operator::In {
168 write!(query, " {} IN (", self.field).unwrap();
169 for (i, value) in self.values.iter().enumerate() {
170 if i > 0 {
171 query.push_str(", ");
172 }
173 write!(query, "'{value}'").unwrap();
174 }
175 query.push(')');
176 } else {
177 write!(query, " {} {} '{}'", self.field, op_str, self.values[0]).unwrap();
178 }
179 }
180}
181
182#[derive(Debug, PartialEq, Clone)]
184pub enum Operator {
185 In,
186 Like,
187 Equal,
188 Less,
189 Greater,
190 LessEqual,
191 GreaterEqual,
192}