sqlxo/blocks/
mod.rs

1use sqlx::{
2	Postgres,
3	Type,
4};
5use sqlxo_traits::{
6	Filterable,
7	Sortable,
8	SqlJoin,
9};
10use sqlxo_traits::{
11	QueryContext,
12	SqlWrite,
13};
14
15mod expression;
16mod head;
17mod pagination;
18mod sort;
19
20pub use expression::Expression;
21pub use head::{
22	AggregationType,
23	DeleteHead,
24	InsertHead,
25	ReadHead,
26	SelectType,
27	UpdateHead,
28};
29pub use pagination::{
30	Page,
31	Pagination,
32};
33pub use sort::SortOrder;
34
35use crate::blocks::head::ToHead;
36
37/// TODO: add modifier traits
38/// and()
39/// or()
40/// allow to wrap existing r#where clause in a and/or statement with another
41pub trait BuildableFilter<C: QueryContext> {
42	fn r#where(self, e: Expression<C::Query>) -> Self;
43}
44
45pub trait BuildableJoin<C: QueryContext> {
46	fn join(self, j: C::Join) -> Self;
47}
48
49pub trait BuildableSort<C: QueryContext> {
50	fn order_by(self, s: SortOrder<C::Sort>) -> Self;
51}
52
53pub trait BuildablePage<C: QueryContext> {
54	fn paginate(self, p: Pagination) -> Self;
55}
56
57pub struct SqlWriter {
58	qb:             sqlx::QueryBuilder<'static, Postgres>,
59	has_join:       bool,
60	has_where:      bool,
61	has_sort:       bool,
62	has_pagination: bool,
63}
64
65impl SqlWriter {
66	pub fn new(head: impl ToHead) -> Self {
67		let qb =
68			sqlx::QueryBuilder::<Postgres>::new(head.to_head().to_string());
69
70		Self {
71			qb,
72			has_join: false,
73			has_where: false,
74			has_sort: false,
75			has_pagination: false,
76		}
77	}
78
79	pub fn into_builder(self) -> sqlx::QueryBuilder<'static, Postgres> {
80		self.qb
81	}
82
83	/// Get mutable access to the underlying QueryBuilder for advanced
84	/// operations
85	pub fn query_builder_mut(
86		&mut self,
87	) -> &mut sqlx::QueryBuilder<'static, Postgres> {
88		&mut self.qb
89	}
90
91	pub fn push_joins<J: SqlJoin>(&mut self, joins: &Vec<J>) {
92		if self.has_join {
93			return;
94		}
95
96		for j in joins {
97			self.qb.push(j.to_sql());
98		}
99	}
100
101	pub fn push_where<F: Filterable>(&mut self, expr: &Expression<F>) {
102		if self.has_where {
103			return;
104		}
105
106		self.qb.push(" WHERE ");
107		self.has_where = true;
108		expr.write(self);
109	}
110
111	pub fn push_soft_delete_filter<F: Filterable>(
112		&mut self,
113		delete_field: &str,
114		existing_expr: Option<&Expression<F>>,
115	) {
116		if self.has_where {
117			return;
118		}
119
120		self.qb.push(" WHERE ");
121		self.has_where = true;
122
123		// Add soft delete filter
124		self.qb.push(delete_field);
125		self.qb.push(" IS NULL");
126
127		// If there's an existing expression, AND it
128		if let Some(expr) = existing_expr {
129			self.qb.push(" AND (");
130			expr.write(self);
131			self.qb.push(")");
132		}
133	}
134
135	pub fn push_sort<S: Sortable>(&mut self, sort: &SortOrder<S>) {
136		if self.has_sort {
137			return;
138		}
139
140		self.qb.push(" ORDER BY ");
141		self.has_sort = true;
142		self.qb.push(sort.to_sql());
143	}
144
145	pub fn push_pagination(&mut self, p: &Pagination) {
146		if self.has_pagination {
147			return;
148		}
149
150		self.qb.push(" LIMIT ");
151		self.bind(p.page_size);
152		self.qb.push(" OFFSET ");
153		self.bind(p.page * p.page_size);
154	}
155}
156
157impl SqlWrite for SqlWriter {
158	fn push(&mut self, s: &str) {
159		self.qb.push(s);
160	}
161
162	fn bind<T>(&mut self, value: T)
163	where
164		T: sqlx::Encode<'static, Postgres> + Send + 'static,
165		T: Type<Postgres>,
166	{
167		self.qb.push_bind(value);
168	}
169}