1extern crate self as quick_oxibooks_sql;
2
3#[cfg(feature = "macros")]
5pub use quick_oxibooks_sql_macro::qb_sql;
6
7mod query;
8pub use query::Query;
9mod limit;
10pub(crate) use limit::Limit;
11mod order;
12pub use order::{Order, OrderClause};
13mod condition;
14pub use condition::{Operator, TypedWhereClause, WhereClause};
15
16#[cfg(feature = "macros")]
17pub use pastey::paste;
18
19pub mod traits {
20 pub trait QbWrapVec {
24 type Item;
25 fn _qb_wrap(self) -> Vec<Self::Item>;
26 }
27 impl<T> QbWrapVec for Vec<T> {
28 type Item = T;
29 fn _qb_wrap(self) -> Vec<T> {
30 self
31 }
32 }
33
34 pub trait QbWrapOpt {
35 type Item;
36 fn _qb_wrap(self) -> Vec<Self::Item>;
37 }
38 impl<T> QbWrapOpt for Option<T> {
39 type Item = T;
40 fn _qb_wrap(self) -> Vec<T> {
41 self.into_iter().collect()
42 }
43 }
44
45 pub trait QbWrapScalar {
46 type Item;
47 fn _qb_wrap(&self) -> Vec<Self::Item>;
48 }
49 impl<T> QbWrapScalar for &T {
50 type Item = T;
51 fn _qb_wrap(&self) -> Vec<T> {
52 Vec::new()
53 }
54 }
55
56 pub trait QbAccessNested {
62 type Inner;
63 fn _qb_access<R, F>(self, f: F) -> Vec<R>
64 where
65 F: FnMut(Self::Inner) -> Vec<R>;
66 }
67 impl<T> QbAccessNested for Vec<Vec<T>> {
68 type Inner = T;
69 fn _qb_access<R, F>(self, f: F) -> Vec<R>
70 where
71 F: FnMut(T) -> Vec<R>,
72 {
73 self.into_iter().flatten().flat_map(f).collect()
74 }
75 }
76
77 pub trait QbAccessGeneric {
82 type Inner;
83 fn _qb_access<R, F>(&self, f: F) -> Vec<R>
84 where
85 F: FnMut(&Self::Inner) -> Vec<R>;
86 }
87 impl<T> QbAccessGeneric for Vec<T> {
88 type Inner = T;
89 fn _qb_access<R, F>(&self, f: F) -> Vec<R>
90 where
91 F: FnMut(&T) -> Vec<R>,
92 {
93 self.iter().flat_map(f).collect()
94 }
95 }
96}
97pub use traits::*;
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 #[cfg(feature = "macros")]
103 use quickbooks_types::Attachable;
104 use quickbooks_types::Customer;
105
106 #[test]
107 fn test_empty_query() {
108 #[cfg(feature = "macros")]
109 let qry = qb_sql!(select * from Customer);
110 #[cfg(not(feature = "macros"))]
111 let qry: Query<Customer> = Query::new();
112 assert_eq!(qry.condition.len(), 0);
113 assert_eq!(qry.order.len(), 0);
114 assert!(qry.limit.is_none());
115 }
116
117 #[test]
118 fn test_basic_query() {
119 #[cfg(feature = "macros")]
120 let qry = qb_sql!(
121 select * from Customer
122 where display_name like "John%"
123 );
124 #[cfg(not(feature = "macros"))]
125 let qry: Query<Customer> = unsafe {
126 Query::new().condition(WhereClause {
127 field: "DisplayName",
128 operator: Operator::Like,
129 values: vec!["John%".to_string()],
130 })
131 };
132
133 assert_eq!(qry.condition.len(), 1);
134 assert_eq!(qry.condition[0].field, "DisplayName");
135 }
136
137 #[test]
138 fn test_multiple_conditions() {
139 let balance_min = 1000.0;
140 #[cfg(feature = "macros")]
141 let qry = qb_sql!(
142 select * from Customer
143 where display_name like "John%"
144 and balance >= balance_min
145 );
146 #[cfg(not(feature = "macros"))]
147 let qry: Query<Customer> = unsafe {
148 Query::new()
149 .condition(WhereClause {
150 field: "DisplayName",
151 operator: Operator::Like,
152 values: vec!["John%".into()],
153 })
154 .condition(WhereClause {
155 field: "Balance",
156 operator: Operator::GreaterEqual,
157 values: vec![balance_min.to_string()],
158 })
159 };
160
161 assert_eq!(qry.condition.len(), 2);
162 }
163
164 #[test]
165 fn test_order_by() {
166 #[cfg(feature = "macros")]
167 let qry = qb_sql!(
168 select * from Customer
169 where display_name like "John%"
170 order by display_name asc, balance desc
171 );
172 #[cfg(not(feature = "macros"))]
173 let qry: Query<Customer> = unsafe {
174 Query::new()
175 .condition(WhereClause {
176 field: "DisplayName",
177 operator: Operator::Like,
178 values: vec!["John%".into()],
179 })
180 .order("DisplayName", Order::Asc)
181 .order("Balance", Order::Desc)
182 };
183
184 assert_eq!(qry.order.len(), 2);
185 assert_eq!(qry.order[0].field, "DisplayName");
186 assert_eq!(qry.order[0].order, Order::Asc);
187 }
188
189 #[test]
190 fn test_limit_and_offset() {
191 let offset_val = 5;
192 #[cfg(feature = "macros")]
193 let qry = qb_sql!(
194 select * from Customer
195 where display_name like "John%"
196 limit 10 offset offset_val
197 );
198 #[cfg(not(feature = "macros"))]
199 let qry: Query<Customer> = unsafe {
200 Query::new()
201 .condition(WhereClause {
202 field: "DisplayName",
203 operator: Operator::Like,
204 values: vec!["John%".into()],
205 })
206 .limit(10, Some(offset_val))
207 };
208
209 assert!(qry.limit.is_some());
210 let limit = qry.limit.unwrap();
211 assert_eq!(limit.number, 10);
212 assert_eq!(limit.offset, Some(5));
213 }
214
215 #[test]
216 fn test_qry_string_generation() {
217 #[cfg(feature = "macros")]
218 let qry = qb_sql!(
219 select * from Customer
220 where display_name like "John%"
221 and id in (1, 2, 3)
222 and balance >= 1000.0
223 order by display_name asc, balance desc
224 limit 10 offset 5
225 );
226 #[cfg(not(feature = "macros"))]
227 let qry: Query<Customer> = unsafe {
228 Query::new()
229 .condition(WhereClause {
230 field: "DisplayName",
231 operator: Operator::Like,
232 values: vec!["John%".into()],
233 })
234 .condition(WhereClause {
235 field: "Id",
236 operator: Operator::In,
237 values: vec!["1".into(), "2".into(), "3".into()],
238 })
239 .condition(WhereClause {
240 field: "Balance",
241 operator: Operator::GreaterEqual,
242 values: vec!["1000".into()],
243 })
244 .order("DisplayName", Order::Asc)
245 .order("Balance", Order::Desc)
246 .limit(10, Some(5))
247 };
248
249 let qry_string = qry.query_string();
250 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";
251 assert_eq!(qry_string, expected);
252 }
253
254 #[test]
255 fn test_in_operator() {
256 #[cfg(feature = "macros")]
257 let qry = qb_sql!(
258 select * from Customer
259 where id in (1, 2, 3, 4, 5)
260 );
261
262 #[cfg(not(feature = "macros"))]
263 let qry: Query<Customer> = unsafe {
264 Query::new().condition(WhereClause {
265 field: "Id",
266 operator: Operator::In,
267 values: vec!["1".into(), "2".into(), "3".into(), "4".into(), "5".into()],
268 })
269 };
270
271 assert_eq!(qry.condition.len(), 1);
272 assert_eq!(qry.condition[0].field, "Id");
273 assert_eq!(qry.condition[0].operator, Operator::In);
274 assert_eq!(qry.condition[0].values.len(), 5);
275
276 let qry_string = qry.query_string();
277 assert_eq!(
278 qry_string,
279 "select * from Customer where Id IN ('1', '2', '3', '4', '5')"
280 );
281 }
282
283 #[test]
284 fn test_in_operator_with_strings() {
285 let title1 = "Mr";
286 let title2 = "Mrs";
287 #[cfg(feature = "macros")]
288 let qry = qb_sql!(
289 select * from Customer
290 where title in (title1, title2, "Dr")
291 );
292
293 #[cfg(not(feature = "macros"))]
294 let qry: Query<Customer> = unsafe {
295 Query::new().condition(WhereClause {
296 field: "Title",
297 operator: Operator::In,
298 values: vec![title1.into(), title2.into(), "Dr".into()],
299 })
300 };
301
302 assert_eq!(qry.condition.len(), 1);
303 assert_eq!(qry.condition[0].values.len(), 3);
304
305 let qry_string = qry.query_string();
306 assert_eq!(
307 qry_string,
308 "select * from Customer where Title IN ('Mr', 'Mrs', 'Dr')"
309 );
310 }
311
312 #[test]
313 fn test_in_iterator() {
314 let ids = vec![1, 2, 3, 4, 5];
315 #[cfg(feature = "macros")]
316 let qry = qb_sql!(
317 select * from Customer
318 where id in (ids)
319 );
320 #[cfg(not(feature = "macros"))]
321 let qry: Query<Customer> = unsafe {
322 Query::new().condition(WhereClause {
323 field: "Id",
324 operator: Operator::In,
325 values: ids.iter().map(|id| id.to_string()).collect(),
326 })
327 };
328
329 assert_eq!(qry.condition.len(), 1);
330 assert_eq!(qry.condition[0].field, "Id");
331 assert_eq!(qry.condition[0].operator, Operator::In);
332 assert_eq!(qry.condition[0].values.len(), 5);
333
334 let qry_string = qry.query_string();
335 assert_eq!(
336 qry_string,
337 "select * from Customer where Id IN ('1', '2', '3', '4', '5')"
338 );
339 }
340
341 #[test]
342 fn test_nested_fields() {
343 #[cfg(feature = "macros")]
344 let qry = qb_sql!(
345 select * from Customer
346 where primary_email_addr.address like "%@example.com"
347 );
348
349 #[cfg(not(feature = "macros"))]
350 let qry: Query<Customer> = unsafe {
351 Query::new().condition(WhereClause {
352 field: "PrimaryEmailAddr.Address",
353 operator: Operator::Like,
354 values: vec!["%@example.com".into()],
355 })
356 };
357
358 assert_eq!(qry.condition.len(), 1);
359 assert_eq!(qry.condition[0].field, "PrimaryEmailAddr.Address");
360
361 let qry_string = qry.query_string();
362 assert_eq!(
363 qry_string,
364 "select * from Customer where PrimaryEmailAddr.Address LIKE '%@example.com'"
365 );
366 }
367
368 #[test]
369 fn test_vec_fields() {
370 let ids = vec!["1", "2", "3"];
371 #[cfg(feature = "macros")]
372 let qry = qb_sql!(
373 select * from Attachable where attachable_ref.entity_ref.value in (ids)
375 );
376
377 #[cfg(not(feature = "macros"))]
378 let qry: Query<Attachable> = unsafe {
379 Query::new().condition(WhereClause {
380 field: "AttachableRef.EntityRef.Value",
381 operator: Operator::In,
382 values: ids.iter().map(|f| f.to_string()).collect(),
383 })
384 };
385
386 assert_eq!(qry.condition.len(), 1);
387 assert_eq!(qry.condition[0].field, "AttachableRef.EntityRef.Value");
388 assert_eq!(qry.condition[0].operator, Operator::In);
389 assert_eq!(qry.condition[0].values.len(), 3);
390
391 let qry_string = qry.query_string();
392 assert_eq!(
393 qry_string,
394 "select * from Attachable where AttachableRef.EntityRef.Value IN ('1', '2', '3')"
395 );
396 }
397}