1mod bind;
2mod column;
3pub mod condition;
4use std::fmt::Debug;
5
6#[cfg(any(feature = "postgres", feature = "sqlite"))]
7use crate::driver::Driver;
8use crate::format_alised_col_name;
9use crate::selectable::Selectable;
10pub use bind::BindValue;
11pub use column::Column;
12pub use condition::Condition;
13use sqlx::QueryBuilder;
14
15pub fn with_quotes(s: &str) -> String {
18 format!("\"{}\"", s)
21}
22
23#[derive(Debug)]
24pub struct QB<T> {
26 pub base: TableInfo,
28 pub eager: Vec<JoinSpec>,
30 pub batch: Vec<JoinSpec>,
32 pub filters: Vec<Condition>,
34 _marker: std::marker::PhantomData<T>,
35}
36#[derive(Clone, Debug)]
37pub struct TableInfo {
39 pub name: &'static str,
41 pub alias: String,
43 pub columns: Vec<&'static str>,
45}
46
47#[derive(Clone, Debug)]
48pub enum JoinType {
50 Inner,
51 Left,
52}
53
54#[derive(Clone, Debug)]
55pub struct JoinSpec {
57 pub join_type: JoinType,
59 pub relation_name: &'static str,
61 pub foreign_table: TableInfo,
63 pub on: (&'static str, &'static str),
65}
66
67impl<T: std::fmt::Debug> QB<T> {
68 pub fn new(base: TableInfo) -> QB<T> {
69 QB {
70 base,
71 eager: Vec::new(),
72 batch: Vec::new(),
73 filters: Vec::new(),
74 _marker: std::marker::PhantomData,
75 }
76 }
77
78 pub fn join_eager(mut self, spec: JoinSpec) -> Self {
79 self.eager.push(spec);
80 self
81 }
82
83 pub fn join_batch(mut self, spec: JoinSpec) -> Self {
84 self.batch.push(spec);
85 self
86 }
87
88 pub fn select<'a, S: Selectable>(mut self, cols: S) -> QB<S::Row> {
89 let cols = cols.collect();
90 if cols.is_empty() {
91 panic!("Cannot select empty column list. At least one column must be specified.");
92 }
93 self.base.columns = cols;
94 QB {
95 base: self.base,
96 eager: self.eager,
97 batch: self.batch,
98 filters: self.filters,
99 _marker: std::marker::PhantomData,
100 }
101 }
102
103 fn build_projections(&self) -> Vec<String> {
104 let mut projections = Vec::new();
105
106 for col in &self.base.columns {
107 let field = format!("{}.{}", self.base.alias, col);
108 let as_field = format_alised_col_name(&self.base.alias, col);
109 projections.push(format!("{} AS {}", field, as_field));
110 }
111
112 for join in &self.eager {
113 for col in &join.foreign_table.columns {
114 let field = format!("{}.{}", join.foreign_table.alias, col);
115 let as_field = format_alised_col_name(&join.foreign_table.alias, col);
116 projections.push(format!("{} AS {}", field, as_field));
117 }
118 }
119
120 projections
121 }
122
123 fn build_from_clause(&self) -> String {
124 format!(
125 "FROM {} AS {}",
126 with_quotes(self.base.name),
127 self.base.alias
128 )
129 }
130
131 pub fn filter(mut self, cond: Condition) -> Self {
132 self.filters.push(cond);
133 self
134 }
135
136 fn build_joins(&self) -> String {
137 let mut joins = String::new();
138
139 for join in &self.eager {
140 let other_table = format!(
141 "{} AS {}",
142 with_quotes(join.foreign_table.name),
143 join.foreign_table.alias
144 );
145
146 let jt = match join.join_type {
147 JoinType::Inner => "INNER JOIN",
148 JoinType::Left => "LEFT JOIN",
149 };
150
151 let on_base = format!("{}.{}", self.base.alias, join.on.0);
152 let on_other = format!("{}.{}", join.foreign_table.alias, join.on.1);
153
154 joins.push_str(&format!(
155 " {} {} ON {} = {}",
156 jt, other_table, on_base, on_other
157 ));
158 }
159
160 joins
161 }
162
163 pub fn build_query(&self) -> QueryBuilder<'static, Driver> {
164 let projections = self.build_projections().join(", ");
165 let from_clause = self.build_from_clause();
166 let joins = self.build_joins();
167
168 let mut builder = QueryBuilder::new("SELECT ");
169 builder.push(projections);
170 builder.push(" ");
171 builder.push(from_clause);
172 builder.push(" ");
173 builder.push(joins);
174
175 if !self.filters.is_empty() {
176 builder.push(" WHERE ");
177
178 for (i, cond) in self.filters.iter().enumerate() {
179 if i > 0 {
180 builder.push(" AND ");
181 }
182
183 let mut parts = cond.sql.split('?');
184 if let Some(first) = parts.next() {
185 builder.push(first);
186 }
187
188 for (val, part) in cond.values.iter().zip(parts) {
189 val.bind(&mut builder);
190 builder.push(part);
191 }
192 }
193 }
194
195 builder
196 }
197
198 pub fn to_sql(&self) -> String {
199 self.build_query().sql().to_string()
200 }
201}