sqlxo 0.9.1

sqlxo: small SQL query builder + derives for filterable ORM-ish patterns
Documentation
use core::fmt;
use smallvec::SmallVec;
use sqlxo_traits::AliasedColumn;
use std::{
	borrow::Cow,
	fmt::{
		Display,
		Formatter,
	},
};

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct QualifiedColumn {
	pub table_alias: String,
	pub column:      &'static str,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SelectType {
	Star,
	StarWithExtras(SmallVec<[AliasedColumn; 4]>),
	StarAndCount,
	StarAndCountExtras(SmallVec<[AliasedColumn; 4]>),
	Exists,
	Columns(SmallVec<[QualifiedColumn; 4]>),
	Projection(Vec<SelectProjection>),
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DeleteType {
	Hard,
	Soft,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SelectProjection {
	pub expression: String,
	pub alias:      Option<String>,
}

pub trait ToHead {
	fn to_head(self) -> Cow<'static, str>;
}

pub struct ReadHead<'a> {
	r#type: SelectType,
	table:  &'a str,
}

impl<'a> ReadHead<'a> {
	pub fn new(table: &'a str, r#type: SelectType) -> Self {
		Self { r#type, table }
	}
}

impl<'a> ToHead for ReadHead<'a> {
	fn to_head(self) -> Cow<'static, str> {
		self.to_string().into()
	}
}

impl<'a> Display for ReadHead<'a> {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match &self.r#type {
			SelectType::Star => {
				write!(f, r#"SELECT "{}".* FROM {}"#, self.table, self.table)
			}
			SelectType::StarWithExtras(cols) => {
				write!(f, r#"SELECT "{}".*"#, self.table)?;
				write_extras(cols, f)?;
				write!(f, " FROM {}", self.table)
			}
			SelectType::StarAndCount => {
				write!(
					f,
					r#"SELECT "{}".*, COUNT(*) OVER() AS total_count FROM {}"#,
					self.table, self.table
				)
			}
			SelectType::StarAndCountExtras(cols) => {
				write!(
					f,
					r#"SELECT "{}".*, COUNT(*) OVER() AS total_count"#,
					self.table
				)?;
				write_extras(cols, f)?;
				write!(f, " FROM {}", self.table)
			}
			SelectType::Exists => {
				write!(f, "SELECT EXISTS(SELECT 1 FROM {}", self.table)
			}
			SelectType::Projection(exprs) => {
				write!(f, "SELECT ")?;
				for (idx, expr) in exprs.iter().enumerate() {
					if idx > 0 {
						write!(f, ", ")?;
					}
					write!(f, "{}", expr.expression)?;
					if let Some(alias) = &expr.alias {
						write!(f, r#" AS "{}""#, alias)?;
					}
				}
				write!(f, " FROM {}", self.table)
			}
			SelectType::Columns(cols) => {
				let mut first = true;
				write!(f, "SELECT ")?;
				for col in cols {
					if !first {
						write!(f, ", ")?;
					}
					first = false;
					write!(f, r#""{}"."{}""#, col.table_alias, col.column)?;
				}
				write!(f, " FROM {}", self.table)
			} /* #[cfg(any(test, feature = "test-utils"))]
			   * BuildType::Raw => write!(f, ""), */
		}
	}
}

fn write_extras(cols: &[AliasedColumn], f: &mut Formatter<'_>) -> fmt::Result {
	for col in cols {
		write!(
			f,
			r#", "{}"."{}" AS "{}""#,
			col.table_alias, col.column, col.alias
		)?;
	}
	Ok(())
}

pub struct DeleteHead<'a> {
	r#type:              DeleteType,
	table:               &'a str,
	delete_marker_field: Option<&'a str>,
}

impl<'a> DeleteHead<'a> {
	pub fn new(
		table: &'a str,
		is_soft: bool,
		delete_marker_field: Option<&'a str>,
	) -> Self {
		Self {
			r#type: if is_soft {
				DeleteType::Soft
			} else {
				DeleteType::Hard
			},
			table,
			delete_marker_field,
		}
	}
}

impl<'a> ToHead for DeleteHead<'a> {
	fn to_head(self) -> Cow<'static, str> {
		self.to_string().into()
	}
}

impl<'a> Display for DeleteHead<'a> {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match &self.r#type {
			DeleteType::Hard => {
				write!(f, "DELETE FROM {}", self.table)
			}
			DeleteType::Soft => {
				let field = self
					.delete_marker_field
					.expect("Soft delete requires delete_marker_field");
				write!(f, "UPDATE {} SET {} = NOW()", self.table, field)
			}
		}
	}
}

pub struct UpdateHead<'a> {
	table: &'a str,
}

impl<'a> UpdateHead<'a> {
	pub fn new(table: &'a str) -> Self {
		Self { table }
	}
}

impl<'a> ToHead for UpdateHead<'a> {
	fn to_head(self) -> Cow<'static, str> {
		format!("UPDATE {} SET ", self.table).into()
	}
}

pub struct InsertHead<'a> {
	table: &'a str,
}

impl<'a> InsertHead<'a> {
	pub fn new(table: &'a str) -> Self {
		Self { table }
	}
}

impl<'a> ToHead for InsertHead<'a> {
	fn to_head(self) -> Cow<'static, str> {
		format!("INSERT INTO {} ", self.table).into()
	}
}