use crate::Row;
use crate::Syntax;
use crate::errors::Result;
use crate::model_traits::{HasSchema, TableColumns, TableInfo};
use crate::query::clause::ParamArgs;
use crate::query::helpers::{build_tail, build_where_clauses, join_sql_parts};
use crate::query::select_cols::SelectBuilder;
use crate::query::select_cols::select_column::SelectRender;
use crate::writers::TableWriter;
use crate::writers::{ColumnWriter, NextParam};
use crate::{Client, WeldsError};
use std::collections::HashSet;
use welds_connections::trace;
#[maybe_async::maybe_async]
impl<T> SelectBuilder<T>
where
T: Send + HasSchema,
{
fn sql_internal<'s, 'args, 'p>(
&'s self,
syntax: Syntax,
args: &'args mut Option<ParamArgs<'p>>,
) -> String
where
's: 'p,
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
let next_params = NextParam::new(syntax);
let wheres = self.qb.wheres.as_slice();
let exists_in = self.qb.exist_ins.as_slice();
let alias = &self.qb.alias;
let mut wheres = build_where_clauses(syntax, &next_params, alias, wheres, args, exists_in);
for j in &self.joins {
j.append_where(syntax, &mut wheres, &next_params, args);
}
let where_sql = if wheres.is_empty() {
None
} else {
Some(format!("WHERE ( {} )", wheres.join(" AND ")))
};
let select_renders = build_select_renders(self);
join_sql_parts(&[
build_head_select(syntax, self.distinct, &select_renders, self),
build_joins(syntax, self),
where_sql,
build_group_by(syntax, &select_renders, self),
build_tail(syntax, &self.qb),
])
.trim()
.to_owned()
}
pub fn to_sql(&self, syntax: Syntax) -> String
where
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
self.sql_internal(syntax, &mut None)
}
pub async fn run(&self, client: &dyn Client) -> Result<Vec<Row>>
where
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
trace::db_error(self.validate_group_by())?;
let syntax = client.syntax();
let mut args: Option<ParamArgs> = Some(Vec::default());
let sql = self.sql_internal(syntax, &mut args);
let args = args.unwrap();
let rows = client.fetch_rows(&sql, &args).await?;
Ok(rows)
}
#[cfg(feature = "unstable-api")]
pub async fn fetch_one<'q, 'c, Ch>(&self, client: &'c dyn Client) -> Result<Ch>
where
'q: 'c,
<T as HasSchema>::Schema: TableInfo + TableColumns,
Ch: Send + HasSchema,
<Ch as HasSchema>::Schema: TableInfo + TableColumns,
Ch: TryFrom<Row>,
WeldsError: From<<Ch as TryFrom<Row>>::Error>,
{
let query: Self = self.clone().limit(1);
let row = query
.run(client)
.await?
.into_iter()
.nth(0)
.ok_or(WeldsError::RowNotFound)?;
let obj: Ch = row.try_into()?;
Ok(obj)
}
#[cfg(feature = "unstable-api")]
pub async fn fetch_one_optional<'q, 'c, Ch>(&self, client: &'c dyn Client) -> Result<Option<Ch>>
where
'q: 'c,
<T as HasSchema>::Schema: TableInfo + TableColumns,
Ch: Send + HasSchema,
<Ch as HasSchema>::Schema: TableInfo + TableColumns,
Ch: TryFrom<Row>,
WeldsError: From<<Ch as TryFrom<Row>>::Error>,
{
let query: Self = self.clone().limit(1);
let mut rows = query.run(client).await?;
let row = rows.pop();
match row {
Some(r) => {
let obj: Ch = r.try_into()?;
Ok(Some(obj))
}
None => Ok(None),
}
}
fn validate_group_by(&self) -> Result<()> {
if self.requires_group_by() && self.group_bys.is_empty() {
return Err(WeldsError::ColumnMissingFromGroupBy);
}
Ok(())
}
fn requires_group_by(&self) -> bool {
self.selects.iter().any(|s| s.is_aggregate())
&& self.selects.iter().any(|s| !s.is_aggregate())
}
}
fn build_head_select<T>(
syntax: Syntax,
distinct: bool,
columns: &[SelectRender],
sb: &SelectBuilder<T>,
) -> Option<String>
where
T: HasSchema,
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
let mut head: Vec<&str> = Vec::default();
head.push("SELECT");
if distinct {
head.push("DISTINCT");
}
let mut cols_text_parts: Vec<_> = Vec::default();
for col in columns {
cols_text_parts.push(col.write(syntax))
}
let cols_text = cols_text_parts.join(", ");
head.push(&cols_text);
head.push("FROM");
let parts = <T as HasSchema>::Schema::identifier();
let tn = TableWriter::new(syntax).write2(parts);
let alias = &sb.qb.alias;
let identifier = format!("{} {}", tn, alias);
head.push(&identifier);
Some(head.join(" "))
}
fn build_select_renders<T>(sb: &SelectBuilder<T>) -> Vec<SelectRender>
where
T: HasSchema,
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
let mut parts: Vec<SelectRender> = Vec::default();
let alias = &sb.qb.alias;
for select in &sb.selects {
parts.push(SelectRender::new(select, alias));
}
for join in &sb.joins {
join.append_select_renders(&mut parts);
}
parts
}
fn build_joins<T>(syntax: Syntax, sb: &SelectBuilder<T>) -> Option<String>
where
T: HasSchema,
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
let mut list = Vec::default();
let alias = &sb.qb.alias;
for join in &sb.joins {
join.append_jointable(syntax, &mut list, alias);
}
Some(list.join(" "))
}
fn build_group_by<T>(
syntax: Syntax,
columns: &[SelectRender],
sb: &SelectBuilder<T>,
) -> Option<String>
where
T: HasSchema,
<T as HasSchema>::Schema: TableInfo + TableColumns,
{
if sb.group_bys.is_empty() {
return None;
}
let writer = ColumnWriter::new(syntax);
let mut cols: Vec<String> = Vec::default();
let mut must_group: HashSet<(&str, &str)> = columns
.iter()
.filter(|x| !x.is_aggregate())
.map(|x| (x.alias.as_str(), x.col_name.as_str()))
.collect();
for group_by in &sb.group_bys {
let alias = group_by.table_alias.as_ref().unwrap_or(&sb.qb.alias);
cols.push(format!("{}.{}", alias, writer.excape(&group_by.col_name)));
must_group.remove(&(alias.as_str(), group_by.col_name.as_str()));
}
Some(format!("GROUP BY {}", cols.join(", ")))
}