use crate::abstract_layer::DbType;
use crate::model::Model;
use crate::query::filter::{FilterExpr, OrderBy};
use crate::query::filter_formatter::FilterFormatter;
use std::fmt::Write;
use std::marker::PhantomData;
pub struct RangeBounds {
pub start: Option<usize>,
pub end: Option<usize>,
}
impl From<std::ops::Range<usize>> for RangeBounds {
fn from(range: std::ops::Range<usize>) -> Self {
RangeBounds {
start: Some(range.start),
end: Some(range.end),
}
}
}
impl From<std::ops::RangeTo<usize>> for RangeBounds {
fn from(range: std::ops::RangeTo<usize>) -> Self {
RangeBounds {
start: None,
end: Some(range.end),
}
}
}
impl From<std::ops::RangeFrom<usize>> for RangeBounds {
fn from(range: std::ops::RangeFrom<usize>) -> Self {
RangeBounds {
start: Some(range.start),
end: None,
}
}
}
pub struct Select<T: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
_marker: PhantomData<T>,
}
impl<T: Model> Clone for Select<T> {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
}
pub struct RelatedSelect<T: Model, R: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
_marker: PhantomData<(T, R)>,
}
pub struct MultiTableSelect<T: Model, R1: Model, R2: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
_marker: PhantomData<(T, R1, R2)>,
}
pub struct FourTableSelect<T: Model, R1: Model, R2: Model, R3: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
_marker: PhantomData<(T, R1, R2, R3)>,
}
pub struct AggregateSelect<T: Model, R = crate::model::Value> {
aggregate_func: String, column_name: String,
filters: Vec<FilterExpr>,
_marker: PhantomData<(T, R)>,
}
pub struct MappedSelect<T: Model, V> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
column_names: Vec<String>, alias_names: Option<Vec<String>>, _marker: PhantomData<(T, V)>,
}
pub struct GroupedSelect<T: Model, V> {
column_names: Vec<String>, aggregate_funcs: Vec<Option<String>>, group_by_columns: Vec<String>, having_filters: Vec<FilterExpr>, filters: Vec<FilterExpr>, order_by: Vec<OrderBy>, range_start: Option<usize>,
range_end: Option<usize>,
_marker: PhantomData<(T, V)>,
}
impl<T: Model, V> Clone for MappedSelect<T, V> {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
column_names: self.column_names.clone(),
alias_names: self.alias_names.clone(),
_marker: PhantomData,
}
}
}
impl<T: Model, V> Clone for GroupedSelect<T, V> {
fn clone(&self) -> Self {
Self {
column_names: self.column_names.clone(),
aggregate_funcs: self.aggregate_funcs.clone(),
group_by_columns: self.group_by_columns.clone(),
having_filters: self.having_filters.clone(),
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
}
impl<T: Model, V> Default for GroupedSelect<T, V> {
fn default() -> Self {
Self {
column_names: Vec::new(),
aggregate_funcs: Vec::new(),
group_by_columns: Vec::new(),
having_filters: Vec::new(),
filters: Vec::new(),
order_by: Vec::new(),
range_start: None,
range_end: None,
_marker: PhantomData,
}
}
}
impl<T: Model, R> AggregateSelect<T, R> {
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
write!(
&mut sql,
"SELECT {}({}) FROM {}",
self.aggregate_func,
self.column_name,
T::TABLE_NAME
)
.expect("Failed to write aggregate SELECT clause");
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = 1;
let formatter = FilterFormatter::new(db_type);
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
(sql, params)
}
}
impl<T: Model, V> MappedSelect<T, V> {
pub fn column_names(&self) -> &[String] {
&self.column_names
}
pub fn with_aliases(mut self, aliases: Vec<String>) -> Self {
self.alias_names = Some(aliases);
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let columns = if let Some(ref aliases) = self.alias_names {
self.column_names
.iter()
.zip(aliases.iter())
.map(|(col, alias)| format!("{} AS {}", col, alias))
.collect::<Vec<_>>()
.join(", ")
} else {
self.column_names.join(", ")
};
write!(&mut sql, "SELECT {} FROM {}", columns, T::TABLE_NAME)
.expect("Failed to write SELECT clause");
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = 1;
let formatter = FilterFormatter::new(db_type);
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
pub fn to_sql(&self) -> String {
#[cfg(feature = "sqlite")]
let db_type = DbType::Sqlite;
#[cfg(all(not(feature = "sqlite"), feature = "postgresql"))]
let db_type = DbType::PostgreSQL;
#[cfg(all(
not(feature = "sqlite"),
not(feature = "postgresql"),
feature = "mysql"
))]
let db_type = DbType::MySQL;
#[cfg(not(any(feature = "sqlite", feature = "postgresql", feature = "mysql")))]
let db_type = DbType::None;
let (sql, _) = self.to_sql_with_params(db_type);
sql
}
}
impl<T: Model, V> GroupedSelect<T, V> {
pub fn new() -> Self {
Self::default()
}
pub fn select_column<F, V2>(self, f: F) -> GroupedSelect<T, V2>
where
F: FnOnce(<T as Model>::Where) -> V2,
V2: SelectColumnResult,
{
let where_obj = <T as Model>::Where::default();
let result = f(where_obj);
GroupedSelect {
column_names: self
.column_names
.into_iter()
.chain(result.column_names())
.collect(),
aggregate_funcs: self
.aggregate_funcs
.into_iter()
.chain(result.aggregate_funcs())
.collect(),
group_by_columns: self.group_by_columns,
having_filters: self.having_filters,
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
pub fn group_by<F, G>(mut self, f: F) -> Self
where
F: FnOnce(<T as Model>::Where) -> G,
G: GroupByColumns,
{
let where_obj = <T as Model>::Where::default();
let group_cols = f(where_obj);
self.group_by_columns = group_cols.column_names();
self
}
pub fn having<F>(mut self, f: F) -> Self
where
F: FnOnce(<T as Model>::Where) -> WhereExpr,
{
let where_obj = <T as Model>::Where::default();
let expr = f(where_obj);
self.having_filters.push(expr.into());
self
}
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn order_by<F, O>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> O,
O: Into<OrderBy>,
{
let where_obj = T::Where::default();
let order = f(where_obj).into();
self.order_by.push(order);
self
}
pub fn order_by_desc<F, O>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> O,
O: Into<OrderBy>,
{
let where_obj = T::Where::default();
let mut order = f(where_obj).into();
order.direction = crate::query::filter::OrderDirection::Desc;
self.order_by.push(order);
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
let columns = self
.column_names
.iter()
.zip(self.aggregate_funcs.iter())
.map(|(col, agg)| match agg {
Some(func) => format!("{}({})", func, col),
None => col.clone(),
})
.collect::<Vec<_>>()
.join(", ");
write!(&mut sql, "SELECT {} FROM {}", columns, T::TABLE_NAME)
.expect("Failed to write SELECT clause");
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type);
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.group_by_columns.is_empty() {
sql.push_str(" GROUP BY ");
sql.push_str(&self.group_by_columns.join(", "));
}
if !self.having_filters.is_empty() {
sql.push_str(" HAVING ");
#[cfg(feature = "postgresql")]
let formatter = if matches!(db_type, crate::DbType::PostgreSQL) {
FilterFormatter::new(db_type).with_postgresql_having_cast(true)
} else {
FilterFormatter::new(db_type)
};
#[cfg(not(feature = "postgresql"))]
let formatter = FilterFormatter::new(db_type);
for (i, filter) in self.having_filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
pub fn to_sql(&self) -> String {
#[cfg(feature = "sqlite")]
let db_type = DbType::Sqlite;
#[cfg(all(not(feature = "sqlite"), feature = "postgresql"))]
let db_type = DbType::PostgreSQL;
#[cfg(all(
not(feature = "sqlite"),
not(feature = "postgresql"),
feature = "mysql"
))]
let db_type = DbType::MySQL;
#[cfg(not(any(feature = "sqlite", feature = "postgresql", feature = "mysql")))]
let db_type = DbType::None;
let (sql, _) = self.to_sql_with_params(db_type);
sql
}
pub fn build_sql(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
self.to_sql_with_params(db_type)
}
pub fn column_count(&self) -> usize {
self.column_names.len()
}
}
impl<T: Model> Select<T> {
pub fn new() -> Self {
Self {
filters: Vec::new(),
order_by: Vec::new(),
range_start: None,
range_end: None,
_marker: PhantomData,
}
}
pub fn from<T2, R: Model>(self) -> RelatedSelect<T, R>
where
T2: Model + 'static,
{
RelatedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
pub fn from3<T2, R1: Model, R2: Model>(self) -> MultiTableSelect<T, R1, R2>
where
T2: Model + 'static,
{
MultiTableSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
pub fn from4<T2, R1: Model, R2: Model, R3: Model>(self) -> FourTableSelect<T, R1, R2, R3>
where
T2: Model + 'static,
{
FourTableSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
#[allow(dead_code)]
fn aggregate(self, func: &str, column: &str) -> AggregateSelect<T> {
AggregateSelect {
aggregate_func: func.to_string(),
column_name: column.to_string(),
filters: self.filters,
_marker: PhantomData,
}
}
fn aggregate_typed<R>(self, func: &str, column: &str) -> AggregateSelect<T, R> {
AggregateSelect {
aggregate_func: func.to_string(),
column_name: column.to_string(),
filters: self.filters,
_marker: PhantomData,
}
}
pub fn count<F, C>(self, f: F) -> AggregateSelect<T, usize>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<C>,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
self.aggregate_typed("COUNT", column.column_name())
}
pub fn sum<F, C>(self, f: F) -> AggregateSelect<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<C>,
C: AggregateResultType + 'static,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
self.aggregate_typed("SUM", column.column_name())
}
pub fn avg<F, C>(self, f: F) -> AggregateSelect<T, Option<f64>>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<C>,
C: AggregateResultType + 'static,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
self.aggregate_typed("AVG", column.column_name())
}
pub fn max<F, C>(self, f: F) -> AggregateSelect<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<C>,
C: AggregateResultType + 'static,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
self.aggregate_typed("MAX", column.column_name())
}
pub fn min<F, C>(self, f: F) -> AggregateSelect<T, C::Output>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<C>,
C: AggregateResultType + 'static,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
self.aggregate_typed("MIN", column.column_name())
}
pub fn map_to<F, M>(self, f: F) -> MappedSelect<T, M::Output>
where
F: FnOnce(<T as Model>::Where) -> M,
M: MapToResult,
{
let where_obj = <T as Model>::Where::default();
let result = f(where_obj);
MappedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
column_names: result.column_names(),
alias_names: None,
_marker: PhantomData,
}
}
pub fn map_to_model<F, TargetModel>(self, f: F) -> MappedSelect<T, TargetModel>
where
F: FnOnce(<T as Model>::Where) -> TypedColumn<<TargetModel as Model>::QueryBuilder>,
TargetModel: Model,
{
let where_obj = <T as Model>::Where::default();
let column = f(where_obj);
let alias_names = TargetModel::COLUMNS.iter().map(|s| s.to_string()).collect();
MappedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
column_names: vec![column.column_name.to_string()],
alias_names: Some(alias_names),
_marker: PhantomData,
}
}
pub fn select_column<F, V>(self, f: F) -> GroupedSelect<T, V>
where
F: FnOnce(<T as Model>::Where) -> V,
V: SelectColumnResult,
{
let where_obj = <T as Model>::Where::default();
let result = f(where_obj);
GroupedSelect {
column_names: result.column_names(),
aggregate_funcs: result.aggregate_funcs(),
group_by_columns: Vec::new(),
having_filters: Vec::new(),
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
_marker: PhantomData,
}
}
}
impl<T: Model> Select<T> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
#[doc(hidden)]
pub fn filter_cmp<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn order_by<F, O>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> O,
O: Into<OrderBy>,
{
let where_obj = T::Where::default();
let order = f(where_obj).into();
self.order_by.push(order);
self
}
pub fn order_by_desc<F, O>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> O,
O: Into<OrderBy>,
{
let where_obj = T::Where::default();
let mut order = f(where_obj).into();
order.direction = crate::query::filter::OrderDirection::Desc;
self.order_by.push(order);
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql(&self) -> String {
#[cfg(feature = "sqlite")]
let db_type = DbType::Sqlite;
#[cfg(all(not(feature = "sqlite"), feature = "postgresql"))]
let db_type = DbType::PostgreSQL;
#[cfg(all(
not(feature = "sqlite"),
not(feature = "postgresql"),
feature = "mysql"
))]
let db_type = DbType::MySQL;
#[cfg(not(any(feature = "sqlite", feature = "postgresql", feature = "mysql")))]
let db_type = DbType::None;
let (sql, _) = self.to_sql_with_params(db_type);
sql
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
write!(
&mut sql,
"SELECT {} FROM {}",
T::COLUMNS.join(", "),
T::TABLE_NAME
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let mut param_idx = 1;
let formatter = FilterFormatter::new(db_type);
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
}
impl<T: Model> Default for Select<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Model, R: Model> RelatedSelect<T, R> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where, R::Where) -> WhereExpr,
{
let t_where = T::Where::default();
let r_where = R::Where::default();
let expr = f(t_where, r_where);
self.filters.push(expr.into());
self
}
pub fn order_by<F>(mut self, f: F) -> Self
where
F: FnOnce(WhereColumn<T>) -> OrderBy,
{
let column = WhereColumn::new();
let order = f(column);
self.order_by.push(order);
self
}
pub fn range<RRR: Into<RangeBounds>>(mut self, range: RRR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {} FROM {} AS t0, {} AS t1",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
R::TABLE_NAME
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
}
impl<T: Model, R1: Model, R2: Model> MultiTableSelect<T, R1, R2> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where, R1::Where, R2::Where) -> WhereExpr,
{
let t_where = T::Where::default();
let r1_where = R1::Where::default();
let r2_where = R2::Where::default();
let expr = f(t_where, r1_where, r2_where);
self.filters.push(expr.into());
self
}
pub fn order_by<F>(mut self, f: F) -> Self
where
F: FnOnce(WhereColumn<T>) -> OrderBy,
{
let column = WhereColumn::new();
let order = f(column);
self.order_by.push(order);
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {} FROM {} AS t0, {} AS t1, {} AS t2",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
R1::TABLE_NAME,
R2::TABLE_NAME
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
}
impl<T: Model, R1: Model, R2: Model, R3: Model> FourTableSelect<T, R1, R2, R3> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where, R1::Where, R2::Where, R3::Where) -> WhereExpr,
{
let t_where = T::Where::default();
let r1_where = R1::Where::default();
let r2_where = R2::Where::default();
let r3_where = R3::Where::default();
let expr = f(t_where, r1_where, r2_where, r3_where);
self.filters.push(expr.into());
self
}
pub fn order_by<F>(mut self, f: F) -> Self
where
F: FnOnce(WhereColumn<T>) -> OrderBy,
{
let column = WhereColumn::new();
let order = f(column);
self.order_by.push(order);
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {} FROM {} AS t0, {} AS t1, {} AS t2, {} AS t3",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
R1::TABLE_NAME,
R2::TABLE_NAME,
R3::TABLE_NAME
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_strs: Vec<String> = self.order_by.iter().map(|o| o.to_sql()).collect();
sql.push_str(&order_strs.join(", "));
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
}
pub struct WhereColumn<T: Model> {
_marker: PhantomData<T>,
}
impl<T: Model> WhereColumn<T> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
pub struct WhereExpr {
inner: FilterExpr,
}
impl From<WhereExpr> for FilterExpr {
fn from(expr: WhereExpr) -> Self {
expr.inner
}
}
impl WhereExpr {
pub fn from_filter(inner: FilterExpr) -> Self {
Self { inner }
}
pub fn and(self, other: WhereExpr) -> Self {
Self {
inner: FilterExpr::And(Box::new(self.inner), Box::new(other.inner)),
}
}
pub fn or(self, other: WhereExpr) -> Self {
Self {
inner: FilterExpr::Or(Box::new(self.inner), Box::new(other.inner)),
}
}
}
pub struct AgeColumn {
column_name: &'static str,
}
impl AgeColumn {
pub fn new(name: &'static str) -> Self {
Self { column_name: name }
}
pub fn column_name(&self) -> &'static str {
self.column_name
}
pub fn ge(self, value: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name.to_string(),
operator: ">=".to_string(),
value: crate::query::filter::Value::Integer(value as i64),
},
}
}
pub fn gt(self, value: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name.to_string(),
operator: ">".to_string(),
value: crate::query::filter::Value::Integer(value as i64),
},
}
}
pub fn le(self, value: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name.to_string(),
operator: "<=".to_string(),
value: crate::query::filter::Value::Integer(value as i64),
},
}
}
pub fn lt(self, value: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name.to_string(),
operator: "<".to_string(),
value: crate::query::filter::Value::Integer(value as i64),
},
}
}
}
pub trait AggregateResultType {
type Output;
}
impl AggregateResultType for i32 {
type Output = Option<i32>; }
impl AggregateResultType for i64 {
type Output = Option<i64>;
}
impl AggregateResultType for f64 {
type Output = Option<f64>;
}
impl AggregateResultType for String {
type Output = Option<String>;
}
impl AggregateResultType for usize {
type Output = usize;
}
pub trait MapToResult {
type Output;
fn column_names(&self) -> Vec<String>;
}
pub trait SelectColumnResult {
type Output;
fn column_names(&self) -> Vec<String>;
fn aggregate_funcs(&self) -> Vec<Option<String>>;
}
pub trait GroupByColumns {
fn column_names(&self) -> Vec<String>;
}
impl<T> MapToResult for TypedColumn<T> {
type Output = T;
fn column_names(&self) -> Vec<String> {
vec![self.column_name.to_string()]
}
}
impl<T> SelectColumnResult for TypedColumn<T> {
type Output = T;
fn column_names(&self) -> Vec<String> {
vec![self.column_name.to_string()]
}
fn aggregate_funcs(&self) -> Vec<Option<String>> {
vec![self.aggregate_func.clone()]
}
}
impl<T> GroupByColumns for TypedColumn<T> {
fn column_names(&self) -> Vec<String> {
vec![self.column_name.to_string()]
}
}
impl<T1, T2> MapToResult for (TypedColumn<T1>, TypedColumn<T2>) {
type Output = (T1, T2);
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
]
}
}
impl<T1, T2> SelectColumnResult for (TypedColumn<T1>, TypedColumn<T2>) {
type Output = (T1, T2);
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
]
}
fn aggregate_funcs(&self) -> Vec<Option<String>> {
vec![self.0.aggregate_func.clone(), self.1.aggregate_func.clone()]
}
}
impl<T1, T2> GroupByColumns for (TypedColumn<T1>, TypedColumn<T2>) {
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
]
}
}
impl<T1, T2, T3> MapToResult for (TypedColumn<T1>, TypedColumn<T2>, TypedColumn<T3>) {
type Output = (T1, T2, T3);
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
self.2.column_name.to_string(),
]
}
}
impl<T1, T2, T3> SelectColumnResult for (TypedColumn<T1>, TypedColumn<T2>, TypedColumn<T3>) {
type Output = (T1, T2, T3);
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
self.2.column_name.to_string(),
]
}
fn aggregate_funcs(&self) -> Vec<Option<String>> {
vec![
self.0.aggregate_func.clone(),
self.1.aggregate_func.clone(),
self.2.aggregate_func.clone(),
]
}
}
impl<T1, T2, T3> GroupByColumns for (TypedColumn<T1>, TypedColumn<T2>, TypedColumn<T3>) {
fn column_names(&self) -> Vec<String> {
vec![
self.0.column_name.to_string(),
self.1.column_name.to_string(),
self.2.column_name.to_string(),
]
}
}
pub trait ColumnValueType {
fn to_filter_value(value: Self) -> crate::query::filter::Value;
fn supports_comparison() -> bool;
}
macro_rules! impl_column_value_type_for_int {
($($t:ty),*) => {
$(
impl ColumnValueType for $t {
fn to_filter_value(value: Self) -> crate::query::filter::Value {
crate::query::filter::Value::Integer(value as i64)
}
fn supports_comparison() -> bool {
true
}
}
)*
};
}
impl_column_value_type_for_int!(i8, i16, i32, i64, u8, u16, u32, u64, isize, usize);
macro_rules! impl_column_value_type_for_float {
($($t:ty),*) => {
$(
impl ColumnValueType for $t {
fn to_filter_value(value: Self) -> crate::query::filter::Value {
crate::query::filter::Value::Real(value as f64)
}
fn supports_comparison() -> bool {
true
}
}
)*
};
}
impl_column_value_type_for_float!(f32, f64);
impl ColumnValueType for String {
fn to_filter_value(value: Self) -> crate::query::filter::Value {
crate::query::filter::Value::Text(value)
}
fn supports_comparison() -> bool {
false }
}
impl ColumnValueType for &str {
fn to_filter_value(value: Self) -> crate::query::filter::Value {
crate::query::filter::Value::Text(value.to_string())
}
fn supports_comparison() -> bool {
false
}
}
pub trait IsInValue<T> {
fn to_in_value(self) -> T;
}
macro_rules! impl_is_in_value_for_numeric {
($($t:ty),* $(,)?) => {
$(
impl IsInValue<$t> for $t {
fn to_in_value(self) -> $t {
self
}
}
impl IsInValue<$t> for &$t {
fn to_in_value(self) -> $t {
*self
}
}
impl IsInValue<$t> for &&$t {
fn to_in_value(self) -> $t {
**self
}
}
)*
};
}
impl_is_in_value_for_numeric!(i8, i16, i32, i64, u8, u16, u32, u64, isize, usize, f32, f64,);
impl IsInValue<String> for String {
fn to_in_value(self) -> String {
self
}
}
impl IsInValue<String> for &String {
fn to_in_value(self) -> String {
self.clone()
}
}
impl IsInValue<String> for &&String {
fn to_in_value(self) -> String {
(*self).clone()
}
}
impl IsInValue<String> for &str {
fn to_in_value(self) -> String {
self.to_string()
}
}
impl IsInValue<String> for &&str {
fn to_in_value(self) -> String {
(*self).to_string()
}
}
pub trait IsInValues<T> {
fn to_in_expr(self, column: String) -> WhereExpr;
}
impl<T: ColumnValueType, I, V> IsInValues<T> for I
where
I: IntoIterator<Item = V>,
V: IsInValue<T>,
{
fn to_in_expr(self, column: String) -> WhereExpr {
WhereExpr {
inner: FilterExpr::In {
column,
values: self
.into_iter()
.map(|v| ColumnValueType::to_filter_value(v.to_in_value()))
.collect(),
},
}
}
}
pub struct SubqueryParam {
pub sql: String,
pub params: Vec<crate::model::Value>,
}
impl<T: ColumnValueType> IsInValues<T> for SubqueryParam {
fn to_in_expr(self, column: String) -> WhereExpr {
WhereExpr {
inner: FilterExpr::InSubquery {
column,
subquery_sql: self.sql,
subquery_params: self.params,
},
}
}
}
impl<T: Model, V: ColumnValueType> IsInValues<V> for MappedSelect<T, V> {
fn to_in_expr(self, column: String) -> WhereExpr {
#[cfg(feature = "sqlite")]
let db_type = DbType::Sqlite;
#[cfg(all(not(feature = "sqlite"), feature = "postgresql"))]
let db_type = DbType::PostgreSQL;
#[cfg(all(
not(feature = "sqlite"),
not(feature = "postgresql"),
feature = "mysql"
))]
let db_type = DbType::MySQL;
#[cfg(not(any(feature = "sqlite", feature = "postgresql", feature = "mysql")))]
let db_type = DbType::None;
let (sql, params) = self.to_sql_with_params(db_type);
WhereExpr {
inner: FilterExpr::InSubquery {
column,
subquery_sql: sql,
subquery_params: params,
},
}
}
}
pub struct TypedColumn<T> {
column_name: &'static str,
aggregate_func: Option<String>, _marker: PhantomData<T>,
}
impl<T> TypedColumn<T> {
pub fn new(name: &'static str) -> Self {
Self {
column_name: name,
aggregate_func: None,
_marker: PhantomData,
}
}
pub fn with_aggregate(name: &'static str, func: String) -> Self {
Self {
column_name: name,
aggregate_func: Some(func),
_marker: PhantomData,
}
}
pub fn column_name(&self) -> &'static str {
self.column_name
}
pub fn aggregate_func(&self) -> Option<&String> {
self.aggregate_func.as_ref()
}
pub fn asc(self) -> OrderBy {
OrderBy::asc(self.column_name.to_string())
}
pub fn desc(self) -> OrderBy {
OrderBy::desc(self.column_name.to_string())
}
}
impl<T> From<TypedColumn<T>> for OrderBy {
fn from(col: TypedColumn<T>) -> Self {
OrderBy::asc(col.column_name.to_string())
}
}
impl<T: crate::model::FromValue> crate::model::FromRowValues for TypedColumn<T> {
fn from_row_values(values: &[crate::model::Value]) -> anyhow::Result<Self> {
if values.is_empty() {
return Err(anyhow::anyhow!("Expected at least 1 value for TypedColumn"));
}
let _parsed = T::from_value(&values[0])?;
Ok(TypedColumn {
column_name: "",
aggregate_func: None,
_marker: PhantomData,
})
}
}
pub type NumericColumn = TypedColumn<i64>;
pub enum ColumnValue {
Literal(crate::query::filter::Value),
ColumnRef(String),
}
impl From<i32> for ColumnValue {
fn from(v: i32) -> Self {
ColumnValue::Literal(crate::query::filter::Value::Integer(v as i64))
}
}
impl From<String> for ColumnValue {
fn from(v: String) -> Self {
ColumnValue::Literal(crate::query::filter::Value::Text(v))
}
}
impl From<&str> for ColumnValue {
fn from(v: &str) -> Self {
ColumnValue::Literal(crate::query::filter::Value::Text(v.to_string()))
}
}
impl<T> From<TypedColumn<T>> for ColumnValue {
fn from(col: TypedColumn<T>) -> Self {
ColumnValue::ColumnRef(col.column_name.to_string())
}
}
impl<T: ColumnValueType> TypedColumn<T> {
pub fn eq(self, value: impl Into<ColumnValue>) -> WhereExpr {
match value.into() {
ColumnValue::Literal(v) => WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name.to_string(),
operator: "=".to_string(),
value: v,
},
},
ColumnValue::ColumnRef(other_column) => WhereExpr {
inner: FilterExpr::ColumnComparison {
left_column: self.column_name.to_string(),
operator: "=".to_string(),
right_column: other_column,
},
},
}
}
pub fn is_in(self, values: impl IsInValues<T>) -> WhereExpr {
values.to_in_expr(self.column_name.to_string())
}
}
impl<T: ColumnValueType> TypedColumn<T> {
pub fn ge(self, value: T) -> WhereExpr {
debug_assert!(
T::supports_comparison(),
"Type does not support comparison operations"
);
let column_name = if let Some(ref func) = self.aggregate_func {
format!("{}({})", func, self.column_name)
} else {
self.column_name.to_string()
};
WhereExpr {
inner: FilterExpr::Comparison {
column: column_name,
operator: ">=".to_string(),
value: ColumnValueType::to_filter_value(value),
},
}
}
pub fn gt(self, value: T) -> WhereExpr {
debug_assert!(
T::supports_comparison(),
"Type does not support comparison operations"
);
let column_name = if let Some(ref func) = self.aggregate_func {
format!("{}({})", func, self.column_name)
} else {
self.column_name.to_string()
};
WhereExpr {
inner: FilterExpr::Comparison {
column: column_name,
operator: ">".to_string(),
value: ColumnValueType::to_filter_value(value),
},
}
}
pub fn le(self, value: T) -> WhereExpr {
debug_assert!(
T::supports_comparison(),
"Type does not support comparison operations"
);
let column_name = if let Some(ref func) = self.aggregate_func {
format!("{}({})", func, self.column_name)
} else {
self.column_name.to_string()
};
WhereExpr {
inner: FilterExpr::Comparison {
column: column_name,
operator: "<=".to_string(),
value: ColumnValueType::to_filter_value(value),
},
}
}
pub fn lt(self, value: T) -> WhereExpr {
debug_assert!(
T::supports_comparison(),
"Type does not support comparison operations"
);
let column_name = if let Some(ref func) = self.aggregate_func {
format!("{}({})", func, self.column_name)
} else {
self.column_name.to_string()
};
WhereExpr {
inner: FilterExpr::Comparison {
column: column_name,
operator: "<".to_string(),
value: ColumnValueType::to_filter_value(value),
},
}
}
}
impl<T: ColumnValueType + 'static> TypedColumn<T> {
pub fn count(self) -> TypedColumn<usize> {
TypedColumn::with_aggregate(self.column_name, "COUNT".to_string())
}
pub fn sum(self) -> TypedColumn<T>
where
T: AggregateResultType,
{
TypedColumn::with_aggregate(self.column_name, "SUM".to_string())
}
pub fn avg(self) -> TypedColumn<f64> {
TypedColumn::with_aggregate(self.column_name, "AVG".to_string())
}
pub fn max(self) -> TypedColumn<T>
where
T: AggregateResultType,
{
TypedColumn::with_aggregate(self.column_name, "MAX".to_string())
}
pub fn min(self) -> TypedColumn<T>
where
T: AggregateResultType,
{
TypedColumn::with_aggregate(self.column_name, "MIN".to_string())
}
}
pub struct ColumnProxy {
column_name: String,
}
impl ColumnProxy {
pub fn new(name: &str) -> Self {
Self {
column_name: name.to_string(),
}
}
}
impl std::ops::BitOr<i32> for ColumnProxy {
type Output = WhereExpr;
fn bitor(self, rhs: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name,
operator: ">=".to_string(),
value: crate::query::filter::Value::Integer(rhs as i64),
},
}
}
}
impl std::ops::Shr<i32> for ColumnProxy {
type Output = WhereExpr;
fn shr(self, rhs: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name,
operator: ">".to_string(),
value: crate::query::filter::Value::Integer(rhs as i64),
},
}
}
}
impl std::ops::Shl<i32> for ColumnProxy {
type Output = WhereExpr;
fn shl(self, rhs: i32) -> WhereExpr {
WhereExpr {
inner: FilterExpr::Comparison {
column: self.column_name,
operator: "<".to_string(),
value: crate::query::filter::Value::Integer(rhs as i64),
},
}
}
}
pub trait ColumnBuilder {
type Output;
fn gt(self, value: impl Into<FilterValue>) -> FilterExpr;
fn ge(self, value: impl Into<FilterValue>) -> FilterExpr;
fn lt(self, value: impl Into<FilterValue>) -> FilterExpr;
fn le(self, value: impl Into<FilterValue>) -> FilterExpr;
fn eq(self, value: impl Into<FilterValue>) -> FilterExpr;
fn ne(self, value: impl Into<FilterValue>) -> FilterExpr;
fn like(self, pattern: &str) -> FilterExpr;
fn contains(self, pattern: &str) -> FilterExpr;
fn into_some(self) -> FilterExpr;
fn into_none(self) -> FilterExpr;
fn asc(self) -> OrderBy;
fn desc(self) -> OrderBy;
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FilterValue {
inner: crate::query::filter::Value,
}
impl From<i32> for FilterValue {
fn from(v: i32) -> Self {
Self {
inner: crate::query::filter::Value::Integer(v as i64),
}
}
}
impl From<i64> for FilterValue {
fn from(v: i64) -> Self {
Self {
inner: crate::query::filter::Value::Integer(v),
}
}
}
impl From<String> for FilterValue {
fn from(v: String) -> Self {
Self {
inner: crate::query::filter::Value::Text(v),
}
}
}
impl From<&str> for FilterValue {
fn from(v: &str) -> Self {
Self {
inner: crate::query::filter::Value::Text(v.to_string()),
}
}
}
#[allow(dead_code)]
pub struct LeftJoinedSelect<T: Model, J: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
join_table: String,
join_alias: String,
on_condition: FilterExpr,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for LeftJoinedSelect<T, J> {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
join_table: self.join_table.clone(),
join_alias: self.join_alias.clone(),
on_condition: self.on_condition.clone(),
_marker: PhantomData,
}
}
}
#[allow(dead_code)]
pub struct InnerJoinedSelect<T: Model, J: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
join_table: String,
join_alias: String,
on_condition: FilterExpr,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for InnerJoinedSelect<T, J> {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
join_table: self.join_table.clone(),
join_alias: self.join_alias.clone(),
on_condition: self.on_condition.clone(),
_marker: PhantomData,
}
}
}
#[allow(dead_code)]
pub struct RightJoinedSelect<T: Model, J: Model> {
filters: Vec<FilterExpr>,
order_by: Vec<OrderBy>,
range_start: Option<usize>,
range_end: Option<usize>,
join_table: String,
join_alias: String,
on_condition: FilterExpr,
_marker: PhantomData<(T, J)>,
}
impl<T: Model, J: Model> Clone for RightJoinedSelect<T, J> {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
order_by: self.order_by.clone(),
range_start: self.range_start,
range_end: self.range_end,
join_table: self.join_table.clone(),
join_alias: self.join_alias.clone(),
on_condition: self.on_condition.clone(),
_marker: PhantomData,
}
}
}
impl<T: Model> Select<T> {
pub fn left_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> LeftJoinedSelect<T, J> {
let t_where = T::Where::default();
let j_where = J::Where::default();
let expr = f(t_where, j_where);
LeftJoinedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
join_table: J::TABLE_NAME.to_string(),
join_alias: "t1".to_string(),
on_condition: expr.into(),
_marker: PhantomData,
}
}
pub fn inner_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> InnerJoinedSelect<T, J> {
let t_where = T::Where::default();
let j_where = J::Where::default();
let expr = f(t_where, j_where);
InnerJoinedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
join_table: J::TABLE_NAME.to_string(),
join_alias: "t1".to_string(),
on_condition: expr.into(),
_marker: PhantomData,
}
}
pub fn right_join<J: Model>(
self,
f: impl FnOnce(T::Where, J::Where) -> WhereExpr,
) -> RightJoinedSelect<T, J> {
let t_where = T::Where::default();
let j_where = J::Where::default();
let expr = f(t_where, j_where);
RightJoinedSelect {
filters: self.filters,
order_by: self.order_by,
range_start: self.range_start,
range_end: self.range_end,
join_table: J::TABLE_NAME.to_string(),
join_alias: "t1".to_string(),
on_condition: expr.into(),
_marker: PhantomData,
}
}
}
impl<T: Model, J: Model> LeftJoinedSelect<T, J> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {}, {} FROM {} AS t0 LEFT JOIN {} AS {}",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
J::COLUMNS
.iter()
.map(|c| format!("t1.{} as j_{}", c, c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
self.join_table,
self.join_alias
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
sql.push_str(" ON ");
self.format_join_condition(&self.on_condition, &mut sql);
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
fn format_join_condition(&self, filter: &FilterExpr, sql: &mut String) {
if let FilterExpr::ColumnComparison {
left_column,
operator,
right_column,
} = filter
{
write!(sql, "t0.{} {} t1.{}", left_column, operator, right_column)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
}
}
}
impl<T: Model, J: Model> InnerJoinedSelect<T, J> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {}, {} FROM {} AS t0 INNER JOIN {} AS {}",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
J::COLUMNS
.iter()
.map(|c| format!("t1.{} as j_{}", c, c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
self.join_table,
self.join_alias
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
sql.push_str(" ON ");
self.format_join_condition(&self.on_condition, &mut sql);
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
fn format_join_condition(&self, filter: &FilterExpr, sql: &mut String) {
if let FilterExpr::ColumnComparison {
left_column,
operator,
right_column,
} = filter
{
write!(sql, "t0.{} {} t1.{}", left_column, operator, right_column)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
}
}
}
impl<T: Model, J: Model> RightJoinedSelect<T, J> {
pub fn filter<F>(mut self, f: F) -> Self
where
F: FnOnce(T::Where) -> WhereExpr,
{
let where_obj = T::Where::default();
let expr = f(where_obj);
self.filters.push(expr.into());
self
}
pub fn range<RR: Into<RangeBounds>>(mut self, range: RR) -> Self {
let bounds = range.into();
self.range_start = bounds.start;
self.range_end = bounds.end;
self
}
pub fn to_sql_with_params(&self, db_type: DbType) -> (String, Vec<crate::model::Value>) {
let mut sql = String::new();
let mut params = Vec::new();
let mut param_idx = 1;
write!(
&mut sql,
"SELECT {}, {} FROM {} AS t0 RIGHT JOIN {} AS {}",
T::COLUMNS
.iter()
.map(|c| format!("t0.{}", c))
.collect::<Vec<_>>()
.join(", "),
J::COLUMNS
.iter()
.map(|c| format!("t1.{} as j_{}", c, c))
.collect::<Vec<_>>()
.join(", "),
T::TABLE_NAME,
self.join_table,
self.join_alias
)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
sql.push_str(" ON ");
self.format_join_condition(&self.on_condition, &mut sql);
if !self.filters.is_empty() {
sql.push_str(" WHERE ");
let formatter = FilterFormatter::new(db_type)
.with_table_prefix("t0")
.with_right_table_prefix("t1");
for (i, filter) in self.filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let filter_sql = formatter.format(filter, &mut param_idx, &mut params);
sql.push_str(&filter_sql);
}
}
if let Some(end) = self.range_end {
let limit = if let Some(start) = self.range_start {
end - start
} else {
end
};
write!(&mut sql, " LIMIT {}", limit).expect("Failed to write LIMIT clause");
}
if let Some(start) = self.range_start {
write!(&mut sql, " OFFSET {}", start).expect("Failed to write OFFSET clause");
}
(sql, params)
}
fn format_join_condition(&self, filter: &FilterExpr, sql: &mut String) {
if let FilterExpr::ColumnComparison {
left_column,
operator,
right_column,
} = filter
{
write!(sql, "t0.{} {} t1.{}", left_column, operator, right_column)
.unwrap_or_else(|e| panic!("Failed to write SQL: {}", e));
}
}
}