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 _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, Out: Debug + FromRow<'a, Row>>(mut self, cols: Vec<&'static str>) -> QB<Out> {
89 if cols.is_empty() {
90 panic!("Cannot select empty column list. At least one column must be specified.");
91 }
92 self.base.columns = cols;
93 QB {
94 base: self.base,
95 eager: self.eager,
96 batch: self.batch,
97 filters: self.filters,
98 _marker: std::marker::PhantomData,
99 }
100 }
101
102 fn build_projections(&self) -> Vec<String> {
103 let mut projections = Vec::new();
104
105 for col in &self.base.columns {
106 let field = format!("{}.{}", self.base.alias, col);
107 let as_field = format_alised_col_name(&self.base.alias, col);
108 projections.push(format!("{} AS {}", field, as_field));
109 }
110
111 for join in &self.eager {
112 for col in &join.foreign_table.columns {
113 let field = format!("{}.{}", join.foreign_table.alias, col);
114 let as_field = format_alised_col_name(&join.foreign_table.alias, col);
115 projections.push(format!("{} AS {}", field, as_field));
116 }
117 }
118
119 projections
120 }
121
122 fn build_from_clause(&self) -> String {
123 format!(
124 "FROM {} AS {}",
125 with_quotes(self.base.name),
126 self.base.alias
127 )
128 }
129
130 pub fn filter(mut self, cond: Condition) -> Self {
131 self.filters.push(cond);
132 self
133 }
134
135 fn build_joins(&self) -> String {
136 let mut joins = String::new();
137
138 for join in &self.eager {
139 let other_table = format!(
140 "{} AS {}",
141 with_quotes(join.foreign_table.name),
142 join.foreign_table.alias
143 );
144
145 let jt = match join.join_type {
146 JoinType::Inner => "INNER JOIN",
147 JoinType::Left => "LEFT JOIN",
148 };
149
150 let on_base = format!("{}.{}", self.base.alias, join.on.0);
151 let on_other = format!("{}.{}", join.foreign_table.alias, join.on.1);
152
153 joins.push_str(&format!(
154 " {} {} ON {} = {}",
155 jt, other_table, on_base, on_other
156 ));
157 }
158
159 joins
160 }
161
162 pub fn build_query(&self) -> QueryBuilder<'static, Driver> {
163 let projections = self.build_projections().join(", ");
164 let from_clause = self.build_from_clause();
165 let joins = self.build_joins();
166
167 let mut builder = QueryBuilder::new("SELECT ");
168 builder.push(projections);
169 builder.push(" ");
170 builder.push(from_clause);
171 builder.push(" ");
172 builder.push(joins);
173
174 if !self.filters.is_empty() {
175 builder.push(" WHERE ");
176
177 for (i, cond) in self.filters.iter().enumerate() {
178 if i > 0 {
179 builder.push(" AND ");
180 }
181
182 let mut parts = cond.sql.split('?');
183 if let Some(first) = parts.next() {
184 builder.push(first);
185 }
186
187 for (val, part) in cond.values.iter().zip(parts) {
188 val.bind(&mut builder);
189 builder.push(part);
190 }
191 }
192 }
193
194 builder
195 }
196
197 pub fn to_sql(&self) -> String {
198 self.build_query().sql().to_string()
199 }
200}