quick_oxibooks_sql/
lib.rs

1// Re-export the procedural macro
2#[cfg(feature = "macros")]
3pub use quick_oxibooks_sql_macro::qb_sql;
4
5mod query;
6pub use query::Query;
7mod limit;
8pub(crate) use limit::Limit;
9mod order;
10pub use order::{Order, OrderClause};
11mod condition;
12pub use condition::{Operator, TypedWhereClause, WhereClause};
13
14#[cfg(feature = "macros")]
15pub use pastey::paste;
16
17#[cfg(test)]
18mod tests {
19    use super::*;
20    use quickbooks_types::Customer;
21
22    #[test]
23    fn test_empty_query() {
24        #[cfg(feature = "macros")]
25        let qry = qb_sql!(select * from Customer);
26        #[cfg(not(feature = "macros"))]
27        let qry: Query<Customer> = Query::new();
28        assert_eq!(qry.condition.len(), 0);
29        assert_eq!(qry.order.len(), 0);
30        assert!(qry.limit.is_none());
31    }
32
33    #[test]
34    fn test_basic_query() {
35        #[cfg(feature = "macros")]
36        let qry = qb_sql!(
37            select * from Customer
38            where display_name like "John%"
39        );
40        #[cfg(not(feature = "macros"))]
41        let qry: Query<Customer> = unsafe {
42            Query::new().condition(WhereClause {
43                field: "DisplayName",
44                operator: Operator::Like,
45                values: vec!["John%".to_string()],
46            })
47        };
48
49        assert_eq!(qry.condition.len(), 1);
50        assert_eq!(qry.condition[0].field, "DisplayName");
51    }
52
53    #[test]
54    fn test_multiple_conditions() {
55        let balance_min = 1000.0;
56        #[cfg(feature = "macros")]
57        let qry = qb_sql!(
58            select * from Customer
59            where display_name like "John%"
60            and balance >= balance_min
61        );
62        #[cfg(not(feature = "macros"))]
63        let qry: Query<Customer> = unsafe {
64            Query::new()
65                .condition(WhereClause {
66                    field: "DisplayName",
67                    operator: Operator::Like,
68                    values: vec!["John%".into()],
69                })
70                .condition(WhereClause {
71                    field: "Balance",
72                    operator: Operator::GreaterEqual,
73                    values: vec![balance_min.to_string()],
74                })
75        };
76
77        assert_eq!(qry.condition.len(), 2);
78    }
79
80    #[test]
81    fn test_order_by() {
82        #[cfg(feature = "macros")]
83        let qry = qb_sql!(
84            select * from Customer
85            where display_name like "John%"
86            order by display_name asc, balance desc
87        );
88        #[cfg(not(feature = "macros"))]
89        let qry: Query<Customer> = unsafe {
90            Query::new()
91                .condition(WhereClause {
92                    field: "DisplayName",
93                    operator: Operator::Like,
94                    values: vec!["John%".into()],
95                })
96                .order("DisplayName", Order::Asc)
97                .order("Balance", Order::Desc)
98        };
99
100        assert_eq!(qry.order.len(), 2);
101        assert_eq!(qry.order[0].field, "DisplayName");
102        assert_eq!(qry.order[0].order, Order::Asc);
103    }
104
105    #[test]
106    fn test_limit_and_offset() {
107        let offset_val = 5;
108        #[cfg(feature = "macros")]
109        let qry = qb_sql!(
110            select * from Customer
111            where display_name like "John%"
112            limit 10 offset offset_val
113        );
114        #[cfg(not(feature = "macros"))]
115        let qry: Query<Customer> = unsafe {
116            Query::new()
117                .condition(WhereClause {
118                    field: "DisplayName",
119                    operator: Operator::Like,
120                    values: vec!["John%".into()],
121                })
122                .limit(10, Some(offset_val))
123        };
124
125        assert!(qry.limit.is_some());
126        let limit = qry.limit.unwrap();
127        assert_eq!(limit.number, 10);
128        assert_eq!(limit.offset, Some(5));
129    }
130
131    #[test]
132    fn test_qry_string_generation() {
133        #[cfg(feature = "macros")]
134        let qry = qb_sql!(
135            select * from Customer
136            where display_name like "John%"
137            and id in (1, 2, 3)
138            and balance >= 1000.0
139            order by display_name asc, balance desc
140            limit 10 offset 5
141        );
142        #[cfg(not(feature = "macros"))]
143        let qry: Query<Customer> = unsafe {
144            Query::new()
145                .condition(WhereClause {
146                    field: "DisplayName",
147                    operator: Operator::Like,
148                    values: vec!["John%".into()],
149                })
150                .condition(WhereClause {
151                    field: "Id",
152                    operator: Operator::In,
153                    values: vec!["1".into(), "2".into(), "3".into()],
154                })
155                .condition(WhereClause {
156                    field: "Balance",
157                    operator: Operator::GreaterEqual,
158                    values: vec!["1000".into()],
159                })
160                .order("DisplayName", Order::Asc)
161                .order("Balance", Order::Desc)
162                .limit(10, Some(5))
163        };
164
165        let qry_string = qry.query_string();
166        let expected = "select * from Customer where DisplayName LIKE 'John%' and Id IN ('1', '2', '3') and Balance >= '1000' order by DisplayName ASC, Balance DESC LIMIT 10 OFFSET 5";
167        assert_eq!(qry_string, expected);
168    }
169
170    #[test]
171    fn test_in_operator() {
172        #[cfg(feature = "macros")]
173        let qry = qb_sql!(
174            select * from Customer
175            where id in (1, 2, 3, 4, 5)
176        );
177
178        #[cfg(not(feature = "macros"))]
179        let qry: Query<Customer> = unsafe {
180            Query::new().condition(WhereClause {
181                field: "Id",
182                operator: Operator::In,
183                values: vec!["1".into(), "2".into(), "3".into(), "4".into(), "5".into()],
184            })
185        };
186
187        assert_eq!(qry.condition.len(), 1);
188        assert_eq!(qry.condition[0].field, "Id");
189        assert_eq!(qry.condition[0].operator, Operator::In);
190        assert_eq!(qry.condition[0].values.len(), 5);
191
192        let qry_string = qry.query_string();
193        assert_eq!(
194            qry_string,
195            "select * from Customer where Id IN ('1', '2', '3', '4', '5')"
196        );
197    }
198
199    #[test]
200    fn test_in_operator_with_strings() {
201        let title1 = "Mr";
202        let title2 = "Mrs";
203        #[cfg(feature = "macros")]
204        let qry = qb_sql!(
205            select * from Customer
206            where title in (title1, title2, "Dr")
207        );
208
209        #[cfg(not(feature = "macros"))]
210        let qry: Query<Customer> = unsafe {
211            Query::new().condition(WhereClause {
212                field: "Title",
213                operator: Operator::In,
214                values: vec![title1.into(), title2.into(), "Dr".into()],
215            })
216        };
217
218        assert_eq!(qry.condition.len(), 1);
219        assert_eq!(qry.condition[0].values.len(), 3);
220
221        let qry_string = qry.query_string();
222        assert_eq!(
223            qry_string,
224            "select * from Customer where Title IN ('Mr', 'Mrs', 'Dr')"
225        );
226    }
227
228    #[test]
229    fn test_in_iterator() {
230        let ids = vec![1, 2, 3, 4, 5];
231        #[cfg(feature = "macros")]
232        let qry = qb_sql!(
233            select * from Customer
234            where id in (ids)
235        );
236        #[cfg(not(feature = "macros"))]
237        let qry: Query<Customer> = unsafe {
238            Query::new().condition(WhereClause {
239                field: "Id",
240                operator: Operator::In,
241                values: ids.iter().map(|id| id.to_string()).collect(),
242            })
243        };
244
245        assert_eq!(qry.condition.len(), 1);
246        assert_eq!(qry.condition[0].field, "Id");
247        assert_eq!(qry.condition[0].operator, Operator::In);
248        assert_eq!(qry.condition[0].values.len(), 5);
249
250        let qry_string = qry.query_string();
251        assert_eq!(
252            qry_string,
253            "select * from Customer where Id IN ('1', '2', '3', '4', '5')"
254        );
255    }
256
257    #[test]
258    fn test_nested_fields() {
259        #[cfg(feature = "macros")]
260        let qry = qb_sql!(
261            select * from Customer
262            where primary_email_addr.address like "%@example.com"
263        );
264
265        #[cfg(not(feature = "macros"))]
266        let qry: Query<Customer> = unsafe {
267            Query::new().condition(WhereClause {
268                field: "PrimaryEmailAddr.Address",
269                operator: Operator::Like,
270                values: vec!["%@example.com".into()],
271            })
272        };
273
274        assert_eq!(qry.condition.len(), 1);
275        assert_eq!(qry.condition[0].field, "PrimaryEmailAddr.Address");
276
277        let qry_string = qry.query_string();
278        assert_eq!(
279            qry_string,
280            "select * from Customer where PrimaryEmailAddr.Address LIKE '%@example.com'"
281        );
282    }
283}