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