chuchi_postgres/filter/
mod.rs

1use std::borrow::Cow;
2use std::fmt;
3
4use tokio_postgres::types::ToSql;
5use types::time::{Date, DateTime, Timeout};
6use types::uid::UniqueId;
7
8mod whr;
9
10pub type SqlStr = Cow<'static, str>;
11
12#[derive(Debug)]
13#[non_exhaustive]
14pub struct Filter<'a> {
15	pub whr: Where,
16	pub order_by: OrderBy,
17	pub limit: Limit,
18	pub offset: Offset,
19	pub params: Params<'a>,
20}
21
22impl<'a> Filter<'a> {
23	pub fn new() -> Self {
24		Self {
25			whr: Where::new(),
26			order_by: OrderBy::new(),
27			limit: Limit::new(),
28			offset: Offset::new(),
29			params: Params::new(),
30		}
31	}
32
33	pub(crate) fn to_formatter(&'a self) -> FilterFormatter<'a> {
34		FilterFormatter {
35			whr: &self.whr,
36			order_by: &self.order_by,
37			limit: &self.limit,
38			offset: &self.offset,
39			params: &self.params,
40		}
41	}
42}
43
44impl fmt::Display for Filter<'_> {
45	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46		write!(f, "{}", self.to_formatter())
47	}
48}
49
50#[derive(Debug)]
51#[non_exhaustive]
52pub(crate) struct FilterFormatter<'a> {
53	pub whr: &'a Where,
54	pub order_by: &'a OrderBy,
55	pub limit: &'a Limit,
56	pub offset: &'a Offset,
57	pub params: &'a Params<'a>,
58}
59
60impl fmt::Display for FilterFormatter<'_> {
61	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62		self.whr.fmt(f)?;
63
64		self.order_by.fmt(f)?;
65
66		let offset_has_param = matches!(self.offset, Offset::Param);
67		let param_count =
68			self.params.len() - if offset_has_param { 1 } else { 0 };
69
70		match &self.limit {
71			Limit::Fixed(value) => write!(f, " LIMIT {}", value)?,
72			Limit::Param => {
73				write!(f, " LIMIT ${}", param_count)?;
74			}
75			Limit::All => {}
76		}
77
78		match &self.offset {
79			Offset::Zero => {}
80			Offset::Fixed(value) => write!(f, " OFFSET {}", value)?,
81			Offset::Param => {
82				write!(f, " OFFSET ${}", self.params.len())?;
83			}
84		}
85
86		Ok(())
87	}
88}
89
90#[derive(Debug)]
91#[non_exhaustive]
92pub struct WhereFilter<'a> {
93	pub whr: Where,
94	pub params: Params<'a>,
95}
96
97impl<'a> WhereFilter<'a> {
98	pub fn new() -> Self {
99		Self {
100			whr: Where::new(),
101			params: Params::new(),
102		}
103	}
104}
105
106impl fmt::Display for WhereFilter<'_> {
107	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108		self.whr.fmt(f)
109	}
110}
111
112#[derive(Debug, Default)]
113pub struct Where {
114	inner: Vec<WherePart>,
115}
116
117#[derive(Debug)]
118#[non_exhaustive]
119pub enum WherePart {
120	Operation(WhereOperation),
121	And,
122	Or,
123	Nested(Where),
124}
125
126#[derive(Debug)]
127pub struct WhereOperation {
128	pub kind: Operator,
129	pub column: Cow<'static, str>,
130}
131
132#[derive(Debug)]
133#[non_exhaustive]
134pub enum Operator {
135	Eq,
136	Ne,
137	Lt,
138	Lte,
139	Gt,
140	Gte,
141	Like,
142	In { length: usize },
143
144	// rhs will be ignored
145	IsNull,
146	// rhs will be ignored
147	IsNotNull,
148}
149
150#[derive(Debug)]
151#[non_exhaustive]
152pub enum WhereIdent {
153	Param,
154	Name(Cow<'static, str>),
155}
156
157impl Where {
158	pub fn new() -> Self {
159		Self { inner: vec![] }
160	}
161
162	pub fn push(&mut self, part: impl Into<WherePart>) {
163		self.inner.push(part.into());
164	}
165
166	fn is_empty(&self) -> bool {
167		self.inner.is_empty()
168	}
169
170	pub(crate) fn to_formatter<'a>(&'a self) -> WhereFormatter<'a> {
171		WhereFormatter {
172			whr: &self,
173			param_start: 0,
174		}
175	}
176}
177
178impl<'a> fmt::Display for Where {
179	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180		self.to_formatter().fmt(f)
181	}
182}
183
184pub(crate) struct WhereFormatter<'a> {
185	pub whr: &'a Where,
186	/// indexed by zero
187	pub param_start: usize,
188}
189
190impl<'a> WhereFormatter<'a> {
191	/// you need to print " WHERE " before calling this
192	fn fmt_inner(
193		&self,
194		f: &mut fmt::Formatter<'_>,
195	) -> Result<usize, fmt::Error> {
196		let mut param_num = self.param_start;
197
198		for part in &self.whr.inner {
199			match part {
200				WherePart::And => f.write_str(" AND ")?,
201				WherePart::Or => f.write_str(" OR ")?,
202				WherePart::Nested(inner) => {
203					let mut inner = inner.to_formatter();
204					inner.param_start = param_num;
205					f.write_str("(")?;
206					param_num = inner.fmt_inner(f)?;
207					f.write_str(")")?;
208				}
209				WherePart::Operation(op) => match &op.kind {
210					Operator::IsNull | Operator::IsNotNull => {
211						write!(f, "\"{}\" {}", op.column, op.kind.as_str())?;
212					}
213					// handle in special if the length is zero
214					// in this case we wan't the query to always return no results
215					Operator::In { length } if *length == 0 => {
216						write!(f, "1=0")?;
217					}
218					Operator::In { length } => {
219						write!(f, "\"{}\" IN (", op.column)?;
220
221						for i in 0..*length {
222							if i != 0 {
223								f.write_str(", ")?;
224							}
225
226							param_num += 1;
227							write!(f, "${}", param_num)?;
228						}
229
230						f.write_str(")")?;
231					}
232					o => {
233						param_num += 1;
234
235						write!(
236							f,
237							"\"{}\" {} ${}",
238							op.column,
239							o.as_str(),
240							param_num
241						)?;
242					}
243				},
244			}
245		}
246
247		Ok(param_num)
248	}
249}
250
251impl<'a> fmt::Display for WhereFormatter<'a> {
252	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253		if self.whr.is_empty() {
254			return Ok(());
255		}
256
257		f.write_str(" WHERE ")?;
258
259		self.fmt_inner(f)?;
260
261		Ok(())
262	}
263}
264
265impl From<WhereOperation> for WherePart {
266	fn from(op: WhereOperation) -> Self {
267		Self::Operation(op)
268	}
269}
270
271impl Operator {
272	fn as_str(&self) -> &str {
273		match self {
274			Operator::Eq => "=",
275			Operator::Ne => "!=",
276			Operator::Lt => "<",
277			Operator::Lte => "<=",
278			Operator::Gt => ">",
279			Operator::Gte => ">=",
280			Operator::Like => "LIKE",
281			Operator::In { .. } => "IN",
282			Operator::IsNull => "IS NULL",
283			Operator::IsNotNull => "IS NOT NULL",
284		}
285	}
286}
287
288#[derive(Debug)]
289pub struct OrderBy {
290	inner: Vec<OrderByPart>,
291}
292
293#[derive(Debug)]
294pub enum OrderByPart {
295	Asc(Cow<'static, str>),
296	Desc(Cow<'static, str>),
297}
298
299impl OrderBy {
300	pub fn new() -> Self {
301		Self { inner: vec![] }
302	}
303
304	pub fn push_asc(&mut self, column: impl Into<Cow<'static, str>>) {
305		self.inner.push(OrderByPart::Asc(column.into()));
306	}
307
308	pub fn push_desc(&mut self, column: impl Into<Cow<'static, str>>) {
309		self.inner.push(OrderByPart::Desc(column.into()));
310	}
311
312	fn is_empty(&self) -> bool {
313		self.inner.is_empty()
314	}
315}
316
317impl fmt::Display for OrderBy {
318	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319		if self.is_empty() {
320			return Ok(());
321		}
322
323		f.write_str(" ORDER BY ")?;
324
325		for (i, part) in self.inner.iter().enumerate() {
326			if i != 0 {
327				f.write_str(", ")?;
328			}
329
330			match part {
331				OrderByPart::Asc(column) => write!(f, "\"{}\" ASC", column)?,
332				OrderByPart::Desc(column) => write!(f, "\"{}\" DESC", column)?,
333			}
334		}
335
336		Ok(())
337	}
338}
339
340#[derive(Debug)]
341pub enum Limit {
342	Fixed(usize),
343	Param,
344	All,
345}
346
347impl Limit {
348	pub fn new() -> Self {
349		Self::All
350	}
351
352	pub fn set_param(&mut self) {
353		*self = Self::Param;
354	}
355
356	pub fn set_fixed(&mut self, value: usize) {
357		*self = Self::Fixed(value);
358	}
359}
360
361#[derive(Debug)]
362pub enum Offset {
363	Zero,
364	Fixed(usize),
365	Param,
366}
367
368impl Offset {
369	pub fn new() -> Self {
370		Self::Zero
371	}
372
373	pub fn set_param(&mut self) {
374		*self = Self::Param;
375	}
376
377	pub fn set_fixed(&mut self, value: usize) {
378		*self = Self::Fixed(value);
379	}
380}
381
382#[derive(Debug)]
383pub struct Params<'a> {
384	inner: Vec<Param<'a>>,
385}
386
387impl<'a> Params<'a> {
388	pub fn new() -> Self {
389		Self { inner: vec![] }
390	}
391
392	pub fn push(&mut self, param: Param<'a>) {
393		self.inner.push(param);
394	}
395
396	pub fn len(&self) -> usize {
397		self.inner.len()
398	}
399
400	pub fn is_empty(&self) -> bool {
401		self.inner.is_empty()
402	}
403
404	pub fn iter_to_sql(
405		&self,
406	) -> impl ExactSizeIterator<Item = &(dyn ToSql + Sync)> {
407		self.inner.iter().map(|p| p.data.as_ref())
408	}
409}
410
411#[derive(Debug)]
412#[non_exhaustive]
413pub struct Param<'a> {
414	// todo is the name still needed?
415	pub name: &'static str,
416	pub data: CowParamData<'a>,
417	is_null: bool,
418}
419
420impl<'a> Param<'a> {
421	pub fn new<T>(name: &'static str, data: &'a T) -> Self
422	where
423		T: ParamData + ToSql + Send + Sync,
424	{
425		Self {
426			name,
427			is_null: data.is_null(),
428			data: CowParamData::Borrowed(data),
429		}
430	}
431
432	pub fn new_owned<T>(name: &'static str, data: T) -> Self
433	where
434		T: ParamData + ToSql + Send + Sync + 'static,
435	{
436		Self {
437			name,
438			is_null: data.is_null(),
439			data: CowParamData::Owned(Box::new(data)),
440		}
441	}
442
443	pub fn is_null(&self) -> bool {
444		self.is_null
445	}
446}
447
448#[derive(Debug)]
449pub enum CowParamData<'a> {
450	Borrowed(&'a (dyn ToSql + Send + Sync)),
451	Owned(Box<dyn ToSql + Send + Sync>),
452}
453
454impl<'a> CowParamData<'a> {
455	pub fn as_ref(&self) -> &(dyn ToSql + Sync) {
456		match self {
457			CowParamData::Borrowed(data) => *data,
458			CowParamData::Owned(data) => &**data,
459		}
460	}
461}
462
463pub trait ParamData {
464	fn is_null(&self) -> bool;
465}
466
467impl<T> ParamData for &T
468where
469	T: ParamData + ?Sized,
470{
471	fn is_null(&self) -> bool {
472		(**self).is_null()
473	}
474}
475
476impl<T> ParamData for &mut T
477where
478	T: ParamData + ?Sized,
479{
480	fn is_null(&self) -> bool {
481		(**self).is_null()
482	}
483}
484
485impl<T> ParamData for Option<T> {
486	fn is_null(&self) -> bool {
487		self.is_none()
488	}
489}
490
491impl<T> ParamData for Vec<T> {
492	fn is_null(&self) -> bool {
493		// todo should an empty array be considered null?
494		false
495	}
496}
497
498impl<T> ParamData for [T] {
499	fn is_null(&self) -> bool {
500		// todo should an empty array be considered null?
501		false
502	}
503}
504
505impl<'a, T> ParamData for Cow<'a, T>
506where
507	T: ToOwned + ParamData,
508{
509	fn is_null(&self) -> bool {
510		(**self).is_null()
511	}
512}
513
514#[macro_export]
515macro_rules! param_not_null {
516	($impl_for:ty) => {
517		impl $crate::filter::ParamData for $impl_for {
518			fn is_null(&self) -> bool {
519				false
520			}
521		}
522	};
523
524	($( $impl_for:ty ),*) => {
525		$(
526			$crate::param_not_null!($impl_for);
527		)*
528	};
529}
530
531param_not_null!(
532	String, str, bool, f64, f32, i64, i32, i16, i8, UniqueId, DateTime, Date,
533	Timeout
534);
535
536#[cfg(feature = "email")]
537impl ParamData for email_address::EmailAddress {
538	fn is_null(&self) -> bool {
539		false
540	}
541}