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