chuchi_postgres/filter/
whr.rs

1/// Possible operators
2/// - = | != | < | <= | > | >= | LIKE | IN
3/// - AND | OR
4///
5/// ~ | ~= | =~ are shortcuts for LIKE
6/// ## Example
7/// ```
8/// use chuchi_postgres::filter;
9/// let a = "val";
10/// let b = "val2".to_string();
11/// let c: Option<String> = None;
12/// let query = filter!(&a AND "b" != &b OR &c ORDER "a" ASC "b" DESC);
13///
14/// assert_eq!(r#" WHERE "a" = $1 AND "b" != $2 OR "c" IS NULL ORDER BY "a" ASC, "b" DESC"#, query.to_string());
15/// ```
16#[macro_export]
17macro_rules! filter {
18	// order
19	(cont; $f:ident, ORDER $value:tt $($tt:tt)*) => ({
20		$crate::filter_order!($f, $value);
21	});
22	// limit
23	(cont; $f:ident, LIMIT $value:tt $($tt:tt)*) => ({
24		$crate::filter_limit!($f, $value);
25	});
26	// offset
27	(cont; $f:ident, OFFSET $value:tt $($tt:tt)*) => ({
28		$crate::filter_offset!($f, $value);
29	});
30	(cont; $f:ident, $($tt:tt)*) => ({
31		$crate::filter_inner!($f, $($tt)*);
32	});
33
34	($($tt:tt)*) => ({
35		#[allow(unused_mut)]
36		let mut f = $crate::filter::Filter::new();
37		$crate::filter!(cont; f, $($tt)*);
38
39		f
40	});
41}
42
43/// Possible operators
44/// - = | != | < | <= | > | >= | LIKE | IN
45/// - AND | OR
46///
47/// ~ | ~= | =~ are shortcuts for LIKE
48/// ## Example
49/// ```
50/// use chuchi_postgres::whr;
51/// let a = "val";
52/// let b = "val2".to_string();
53/// let c: Option<String> = None;
54/// let query = whr!(&a AND "b" != &b OR &c);
55///
56/// assert_eq!(r#" WHERE "a" = $1 AND "b" != $2 OR "c" IS NULL"#, query.to_string());
57/// ```
58#[macro_export]
59macro_rules! whr {
60	($($tt:tt)*) => ({
61		#[allow(unused_mut)]
62		let mut f = $crate::filter::WhereFilter::new();
63		$crate::filter_inner!(f, $($tt)*);
64
65		f
66	});
67}
68
69#[doc(hidden)]
70#[macro_export]
71macro_rules! filter_inner {
72	($f:ident,) => ();
73
74	// nested
75	($f:ident, ($($tt:tt)+) $($rest:tt)*) => ({
76		// we need to swap the where
77		let mut prev_where = std::mem::take(&mut $f.whr);
78		$crate::filter_inner!($f, $($tt)*);
79		std::mem::swap(&mut $f.whr, &mut prev_where);
80		$f.whr.push($crate::filter::WherePart::Nested(prev_where));
81		$crate::whr_log!($f, $($rest)*);
82	});
83
84	// reference ident eq
85	($f:ident, &$id:ident $($tt:tt)*) => (
86		$crate::whr_comp!($f, stringify!($id), Eq, &$id $($tt)*);
87	);
88	// ident eq
89	($f:ident, $id:ident $($tt:tt)*) => (
90		$crate::whr_comp!($f, stringify!($id), Eq, $id $($tt)*);
91	);
92
93	// eq
94	($f:ident, $name:literal = $($tt:tt)+) => (
95		$crate::whr_comp!($f, $name, Eq, $($tt)*);
96	);
97	// ne
98	($f:ident, $name:literal != $($tt:tt)+) => (
99		$crate::whr_comp!($f, $name, Ne, $($tt)*);
100	);
101	// lt
102	($f:ident, $name:literal < $($tt:tt)+) => (
103		$crate::whr_comp!($f, $name, Lt, $($tt)*);
104	);
105	// lte
106	($f:ident, $name:literal <= $($tt:tt)+) => (
107		$crate::whr_comp!($f, $name, Lte, $($tt)*);
108	);
109	// gt
110	($f:ident, $name:literal > $($tt:tt)+) => (
111		$crate::whr_comp!($f, $name, Gt, $($tt)*);
112	);
113	// gte
114	($f:ident, $name:literal >= $($tt:tt)+) => (
115		$crate::whr_comp!($f, $name, Gte, $($tt)*);
116	);
117	// like
118	($f:ident, $name:literal LIKE $($tt:tt)+) => (
119		$crate::whr_comp!($f, $name, Like, $($tt)*);
120	);
121	// like %val%
122	($f:ident, $name:literal ~ $($tt:tt)+) => (
123		$crate::whr_comp!($f, $name, ~, $($tt)*);
124	);
125	// like %val
126	($f:ident, $name:literal ~= $($tt:tt)+) => (
127		$crate::whr_comp!($f, $name, ~=, $($tt)*);
128	);
129	// like val%
130	($f:ident, $name:literal =~ $($tt:tt)+) => (
131		$crate::whr_comp!($f, $name, =~, $($tt)*);
132	);
133	// in
134	($f:ident, $name:literal IN $($tt:tt)+) => (
135		$crate::whr_comp_in!($f, $name, $($tt)*);
136	);
137}
138
139#[doc(hidden)]
140#[macro_export]
141macro_rules! whr_comp {
142	($f:ident, $name:expr, $symb:tt, &$value:tt $($tt:tt)*) => (
143		$crate::whr_comp!(symb; $f, $name, $symb, &$value, $($tt)*);
144	);
145	($f:ident, $name:expr, $symb:tt, $value:tt $($tt:tt)*) => (
146		$crate::whr_comp!(symb; $f, $name, $symb, $value, $($tt)*);
147	);
148
149	(symb; $f:ident, $name:expr, ~, $value:expr, $($tt:tt)*) => (
150		let param = $crate::filter::Param::new_owned($name, format!("%{}%", $value));
151		$crate::whr_comp!(fin; $f, param, Like, $($tt)*);
152	);
153	(symb; $f:ident, $name:expr, ~=, $value:expr, $($tt:tt)*) => (
154		let param = $crate::filter::Param::new_owned($name, format!("%{}", $value));
155		$crate::whr_comp!(fin; $f, param, Like, $($tt)*);
156	);
157	(symb; $f:ident, $name:expr, =~, $value:expr, $($tt:tt)*) => (
158		let param = $crate::filter::Param::new_owned($name, format!("{}%", $value));
159		$crate::whr_comp!(fin; $f, param, Like, $($tt)*);
160	);
161	(symb; $f:ident, $name:expr, $symb:ident, $value:expr, $($tt:tt)*) => (
162		let param = $crate::filter::Param::new($name, $value);
163		$crate::whr_comp!(fin; $f, param, $symb, $($tt)*);
164	);
165	(fin; $f:ident, $param:expr, $symb:ident, $($tt:tt)*) => (
166		let symb = $crate::filter::Operator::$symb;
167
168		let mut cont = true;
169
170		// todo: can this become a noop
171		if $param.is_null() {
172			match symb {
173				$crate::filter::Operator::Eq => {
174					$f.whr.push($crate::filter::WhereOperation {
175						kind: $crate::filter::Operator::IsNull,
176						column: $param.name.into()
177					});
178					cont = false;
179				},
180				$crate::filter::Operator::Ne => {
181					$f.whr.push($crate::filter::WhereOperation {
182						kind: $crate::filter::Operator::IsNotNull,
183						column: $param.name.into()
184					});
185					cont = false;
186				},
187				_ => {}
188			}
189		}
190
191		if cont {
192			$f.whr.push($crate::filter::WhereOperation {
193				kind: symb,
194				column: $param.name.into()
195			});
196			$f.params.push($param);
197		}
198
199		$crate::whr_log!($f, $($tt)*);
200	);
201	// LIMIT
202}
203
204#[doc(hidden)]
205#[macro_export]
206macro_rules! whr_comp_in {
207	($f:ident, $name:expr, &$value:tt $($tt:tt)*) => (
208		$crate::whr_comp_in!(two; $f, $name, &$value, $($tt)*);
209	);
210	($f:ident, $name:expr, $value:tt $($tt:tt)*) => (
211		$crate::whr_comp_in!(two; $f, $name, $value, $($tt)*);
212	);
213
214	(two; $f:ident, $name:expr, $value:expr, $($tt:tt)*) => (
215		{
216			let mut c = 0;
217			for val in $value {
218				c += 1;
219				let param = $crate::filter::Param::new($name, val);
220				$f.params.push(param);
221			}
222
223			$f.whr.push($crate::filter::WhereOperation {
224				kind: $crate::filter::Operator::In { length: c },
225				column: $name.into()
226			});
227		}
228
229		$crate::whr_log!($f, $($tt)*);
230	);
231}
232
233#[doc(hidden)]
234#[macro_export]
235macro_rules! whr_log {
236	($f:ident, AND $($tt:tt)+) => (
237		$f.whr.push($crate::filter::WherePart::And);
238		$crate::filter_inner!($f, $($tt)+);
239	);
240	($f:ident, OR $($tt:tt)+) => (
241		$f.whr.push($crate::filter::WherePart::Or);
242		$crate::filter_inner!($f, $($tt)+);
243	);
244
245	($f:ident, ORDER $($tt:tt)+) => (
246		$crate::filter_order!($f, $($tt)+);
247	);
248	($f:ident, LIMIT $($tt:tt)+) => (
249		$crate::filter_limit!($f, $($tt)+);
250	);
251	($f:ident, OFFSET $($tt:tt)+) => (
252		$crate::filter_offset!($f, $($tt)+);
253	);
254	($f:ident,) => ();
255}
256
257#[doc(hidden)]
258#[macro_export]
259macro_rules! filter_order {
260	($f:ident, $name:literal DESC $($tt:tt)*) => (
261		$f.order_by.push_desc($name);
262		$crate::filter_order!($f, $($tt)*);
263	);
264	($f:ident, $name:literal ASC $($tt:tt)*) => (
265		$f.order_by.push_asc($name);
266		$crate::filter_order!($f, $($tt)*);
267	);
268	($f:ident, LIMIT $($tt:tt)+) => (
269		$crate::filter_limit!($f, $($tt)+);
270	);
271	($f:ident, OFFSET $($tt:tt)+) => (
272		$crate::filter_offset!($f, $($tt)+);
273	);
274	($f:ident,) => ();
275}
276
277#[doc(hidden)]
278#[macro_export]
279macro_rules! filter_limit {
280	($f:ident, &$value:ident $($tt:tt)*) => (
281		$crate::filter_limit!(val; $f, stringify!($value), &$value, $($tt)*);
282	);
283	($f:ident, $value:ident $($tt:tt)*) => (
284		$crate::filter_limit!(val; $f, stringify!($value), $value, $($tt)*);
285	);
286	($f:ident, $value:literal $($tt:tt)*) => (
287		$f.limit.set_fixed($value);
288
289		$crate::filter_limit!(next; $f, $($tt)*);
290	);
291
292	(val; $f:ident, $name:expr, $value:expr, $($tt:tt)*) => (
293		let param = $crate::filter::Param::new($name, $value);
294		$f.limit.set_param();
295		$f.params.push(param);
296
297		$crate::filter_limit!(next; $f, $($tt)*);
298	);
299
300	(next; $f:ident, OFFSET $($tt:tt)+) => (
301		$crate::filter_offset!($f, $($tt)*);
302	);
303	(next; $f:ident,) => ();
304}
305
306#[doc(hidden)]
307#[macro_export]
308macro_rules! filter_offset {
309	($f:ident, &$value:ident $($tt:tt)*) => (
310		$crate::filter_offset!(val; $f, stringify!($value), &$value, $($tt)*);
311	);
312	($f:ident, $value:ident $($tt:tt)*) => (
313		$crate::filter_offset!(val; $f, stringify!($value), $value, $($tt)*);
314	);
315	($f:ident, $value:literal $($tt:tt)*) => (
316		$f.offset.set_fixed($value);
317
318		$crate::filter_offset!(next; $f, $($tt)*);
319	);
320
321	(val; $f:ident, $name:literal, $value:expr, $($tt:tt)*) => (
322		let param = $crate::filter::Param::new($name, $value);
323		$f.offset.set_param();
324		$f.params.push(param);
325
326		$crate::filter_offset!(next; $f, $($tt)*);
327	);
328
329	(next; $f:ident,) => ();
330}
331
332#[cfg(test)]
333mod tests {
334	use crate::UniqueId;
335
336	#[test]
337	fn test_simple_eq() {
338		let id = &UniqueId::new();
339		let id2 = &UniqueId::new();
340		let query = filter!(id OR "id" != &id2);
341		assert_eq!(query.to_string(), r#" WHERE "id" = $1 OR "id" != $2"#);
342	}
343
344	#[test]
345	fn test_simple_like() {
346		let id = "str";
347		let query = filter!("id" ~ id);
348		assert_eq!(query.to_string(), r#" WHERE "id" LIKE $1"#);
349	}
350
351	#[test]
352	fn test_limit() {
353		let id = &UniqueId::new();
354		let limit = 10;
355		let query = filter!(id LIMIT &limit);
356		assert_eq!(query.to_string(), " WHERE \"id\" = $1 LIMIT $2");
357	}
358
359	#[test]
360	fn test_and_or() {
361		let user_id = &UniqueId::new();
362		let id1 = &UniqueId::new();
363		let id2 = &UniqueId::new();
364		let id3 = &UniqueId::new();
365		let query =
366			filter!(user_id AND ("id" != &id1 OR "id" = &id2) OR "id" = &id3);
367		assert_eq!(
368			query.to_string(),
369			r#" WHERE "user_id" = $1 AND ("id" != $2 OR "id" = $3) OR "id" = $4"#
370		);
371	}
372
373	// #[test]
374	// fn test_order() {
375	// 	let id = &UniqueId::new();
376	// 	let limit = 10;
377	// 	let query = whr!(id ORDER "id" ASC LIMIT limit);
378	// 	assert_eq!(
379	// 		query.sql.to_string().trim(),
380	// 		"\"id\" = $1 ORDER BY \"id\" ASC  LIMIT 10"
381	// 	);
382	// 	let query = whr!(ORDER "id" DESC);
383	// 	assert_eq!(query.sql.to_string().trim(), "ORDER BY \"id\" DESC");
384	// }
385}