1mod bind;
2mod column;
3pub mod condition;
4use std::fmt::Debug;
5
6#[cfg(any(feature = "postgres", feature = "sqlite"))]
7use crate::driver::{Driver, Row};
8pub use bind::BindValue;
9pub use column::Column;
10pub use condition::Condition;
11use sqlx::FromRow;
12use sqlx::QueryBuilder;
13
14fn with_quotes(s: &str) -> String {
17 format!("\"{}\"", s)
20}
21
22#[derive(Debug)]
23pub struct QB<T: std::fmt::Debug> {
25 pub base: TableInfo,
27 pub eager: Vec<JoinSpec>,
29 pub batch: Vec<JoinSpec>,
31 pub filters: Vec<Condition>,
33 _marker: std::marker::PhantomData<T>,
34}
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 projections.push(format!(
107 "{}.{} AS {}{}",
108 self.base.alias, col, self.base.alias, col
109 ));
110 }
111
112 for join in &self.eager {
113 for col in &join.foreign_table.columns {
114 projections.push(format!(
115 "{}.{} AS {}{}",
116 join.foreign_table.alias, col, join.foreign_table.alias, col
117 ));
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}