1mod bind;
2mod column;
3pub mod condition;
4mod limit_offset;
5use std::fmt::Debug;
6
7#[cfg(any(feature = "postgres", feature = "sqlite"))]
8use crate::driver::Driver;
9use crate::format_alised_col_name;
10use crate::selectable::Selectable;
11pub use bind::BindValue;
12pub use column::Column;
13pub use condition::Condition;
14use sqlx::QueryBuilder;
15
16pub fn with_quotes(s: &str) -> String {
19 format!("\"{}\"", s)
22}
23
24#[derive(Debug)]
25pub struct QB<T> {
27 pub base: TableInfo,
29 pub eager: Vec<JoinSpec>,
31 pub batch: Vec<JoinSpec>,
33 pub filters: Vec<Condition>,
35 pub limit: Option<i32>,
36 pub offset: Option<i32>,
37 _marker: std::marker::PhantomData<T>,
38}
39#[derive(Clone, Debug)]
40pub struct TableInfo {
42 pub name: &'static str,
44 pub alias: String,
46 pub columns: Vec<&'static str>,
48}
49
50#[derive(Clone, Debug)]
51pub enum JoinType {
53 Inner,
54 Left,
55}
56
57#[derive(Clone, Debug)]
58pub struct JoinSpec {
60 pub join_type: JoinType,
62 pub relation_name: &'static str,
64 pub foreign_table: TableInfo,
66 pub on: (&'static str, &'static str),
68}
69
70impl<T: std::fmt::Debug> QB<T> {
71 pub fn new(base: TableInfo) -> QB<T> {
72 QB {
73 base,
74 eager: Vec::new(),
75 batch: Vec::new(),
76 filters: Vec::new(),
77 _marker: std::marker::PhantomData,
78 limit: None,
79 offset: None,
80 }
81 }
82
83 pub fn join_eager(mut self, spec: JoinSpec) -> Self {
84 self.eager.push(spec);
85 self
86 }
87
88 pub fn join_batch(mut self, spec: JoinSpec) -> Self {
89 self.batch.push(spec);
90 self
91 }
92
93 pub fn select<'a, S: Selectable>(mut self, cols: S) -> QB<S::Row> {
94 let cols = cols.collect();
95 if cols.is_empty() {
96 panic!("Cannot select empty column list. At least one column must be specified.");
97 }
98 self.base.columns = cols;
99 QB {
100 base: self.base,
101 eager: self.eager,
102 batch: self.batch,
103 limit: self.limit,
104 offset: self.offset,
105 filters: self.filters,
106 _marker: std::marker::PhantomData,
107 }
108 }
109
110 fn apply_projections(&self, builder: &mut QueryBuilder<'static, Driver>) {
111 let mut projections = Vec::new();
112
113 for col in &self.base.columns {
114 let field = format!("{}.{}", self.base.alias, col);
115 let as_field = format_alised_col_name(&self.base.alias, col);
116 projections.push(format!("{} AS {}", field, as_field));
117 }
118
119 for join in &self.eager {
120 for col in &join.foreign_table.columns {
121 let field = format!("{}.{}", join.foreign_table.alias, col);
122 let as_field = format_alised_col_name(&join.foreign_table.alias, col);
123 projections.push(format!("{} AS {}", field, as_field));
124 }
125 }
126
127 builder.push(projections.join(", "));
128
129 builder.push(" ");
130 }
131
132 fn apply_from_clause(&self, builder: &mut QueryBuilder<'static, Driver>) {
133 builder.push(format!(
134 "FROM {} AS {}",
135 with_quotes(self.base.name),
136 self.base.alias
137 ));
138
139 builder.push(" ");
140 }
141
142 pub fn filter(mut self, cond: Condition) -> Self {
143 self.filters.push(cond);
144 self
145 }
146
147 fn apply_joins(&self, builder: &mut QueryBuilder<'static, Driver>) {
148 let mut joins = String::new();
149
150 for join in &self.eager {
151 let other_table = format!(
152 "{} AS {}",
153 with_quotes(join.foreign_table.name),
154 join.foreign_table.alias
155 );
156
157 let jt = match join.join_type {
158 JoinType::Inner => "INNER JOIN",
159 JoinType::Left => "LEFT JOIN",
160 };
161
162 let on_base = format!("{}.{}", self.base.alias, join.on.0);
163 let on_other = format!("{}.{}", join.foreign_table.alias, join.on.1);
164
165 joins.push_str(&format!(
166 " {} {} ON {} = {}",
167 jt, other_table, on_base, on_other
168 ));
169 }
170
171 builder.push(joins);
172 }
173
174 fn apply_limit<'args>(&self, builder: &mut QueryBuilder<'args, Driver>) {
175 if let Some(l) = self.limit {
176 builder.push(" LIMIT ");
177 builder.push_bind(l);
178 }
179 }
180
181 fn apply_offset<'args>(&self, builder: &mut QueryBuilder<'args, Driver>) {
182 if let Some(o) = self.offset {
183 #[cfg(feature = "sqlite")]
184 if let None = self.limit {
185 builder.push(" LIMIT ");
186 builder.push_bind(-1);
187 }
188 builder.push(" OFFSET ");
189 builder.push_bind(o);
190 }
191 }
192
193 fn apply_filters(&self, builder: &mut QueryBuilder<'static, Driver>) {
194 if !self.filters.is_empty() {
195 builder.push(" WHERE ");
196
197 for (i, cond) in self.filters.iter().enumerate() {
198 if i > 0 {
199 builder.push(" AND ");
200 }
201
202 let mut parts = cond.sql.split('?');
203 if let Some(first) = parts.next() {
204 builder.push(first);
205 }
206
207 for (val, part) in cond.values.iter().zip(parts) {
208 val.bind(builder);
209 builder.push(part);
210 }
211 }
212 }
213 }
214
215 pub fn build_query(&self) -> QueryBuilder<'static, Driver> {
216 let mut builder = QueryBuilder::new("SELECT ");
217
218 self.apply_projections(&mut builder);
219 self.apply_from_clause(&mut builder);
220 self.apply_joins(&mut builder);
221 self.apply_filters(&mut builder);
222 self.apply_limit(&mut builder);
223 self.apply_offset(&mut builder);
224
225 builder
226 }
227
228 pub fn to_sql(&self) -> String {
229 self.build_query().sql().to_string()
230 }
231}