use rusqlite::params_from_iter;
use rusqlite::types::Value;
use rusqlite::ParamsFromIter;
use std::slice::Iter;
use crate::sql::Field;
use crate::sql::Join;
use crate::sql::JoinType;
use crate::sql::Prefix;
use crate::sql::Table;
#[derive(Debug, Clone)]
pub struct RusqliteQuery<F: Field> {
pub used_prefix: String,
pub sql_where_clause: String,
pub sql_joins: Vec<Join<F>>,
pub where_values: Vec<Value>,
}
impl<F: Field> RusqliteQuery<F> {
pub fn negate_where_clause(mut self) -> Self {
self.sql_where_clause = format!("NOT ({})", self.sql_where_clause);
return self;
}
pub fn parenthesise_where_clause(mut self) -> Self {
self.sql_where_clause = format!("({})", self.sql_where_clause);
return self;
}
pub fn with_likelihood(mut self, likelihood: f32) -> Self {
if !likelihood.is_nan() {
self.sql_where_clause = format!(
"likelihood(({}), {:.5})",
self.sql_where_clause,
likelihood.clamp(0.001, 0.999)
)
}
return self;
}
pub fn concat(mut self, mut other: Self, where_joiner: &str) -> Self {
self.sql_where_clause = self.sql_where_clause + where_joiner + &other.sql_where_clause;
self.where_values.append(&mut other.where_values);
for join in other.sql_joins {
self.add_join_clause(join);
}
return self;
}
#[inline]
pub fn and(self, other: Self) -> Self {
self.concat(other, " AND ")
}
#[inline]
pub fn or(self, other: Self) -> Self {
self.concat(other, " OR ")
}
pub fn from_static_sql(where_clause: String) -> Self {
Self {
used_prefix: "".to_string(),
sql_where_clause: where_clause,
where_values: Vec::new(),
sql_joins: Vec::new(),
}
}
pub fn from_static_sql_str(where_clause: &str) -> Self {
Self::from_static_sql(where_clause.to_string())
}
pub fn static_bool(value: bool) -> Self {
if value {
Self::from_static_sql_str("1")
} else {
Self::from_static_sql_str("0")
}
}
pub fn test_if_null(prefix: &Prefix, field: &F, invert: bool) -> Self {
let mut where_clause = format!("{} is NULL", field.sql_safe_prefixed_field_name(prefix));
if invert {
where_clause = format!("NOT ({})", where_clause);
}
Self {
used_prefix: prefix.to_string(),
sql_where_clause: where_clause,
where_values: Vec::new(),
sql_joins: Vec::new(),
}
}
pub fn add_join_clause(&mut self, join: Join<F>) {
if !self.sql_joins.contains(&join) {
self.sql_joins.push(join);
}
}
#[inline]
pub fn inner_join(
self,
reason: Option<&str>,
connector_field: F,
dock_prefix: Option<&Prefix>,
dock_field: F,
) -> Self {
self.join(
JoinType::Inner,
reason,
connector_field,
dock_prefix,
dock_field,
)
}
#[inline]
pub fn left_join(
self,
reason: Option<&str>,
connector_field: F,
dock_prefix: Option<&Prefix>,
dock_field: F,
) -> Self {
self.join(
JoinType::Left,
reason,
connector_field,
dock_prefix,
dock_field,
)
}
#[allow(clippy::too_many_arguments)]
pub fn join(
mut self,
join_type: JoinType,
reason: Option<&str>,
connector_field: F,
dock_prefix: Option<&Prefix>,
dock_field: F,
) -> Self {
let dock_table = dock_field.table().sql_safe_table_name();
let dock_table = if let Some(dock_prefix) = dock_prefix {
dock_prefix.to_string() + dock_table
} else {
self.used_prefix.clone() + dock_table
};
let mut alias: Option<String> = None;
if let Some(reason) = reason {
alias = Some(self.used_prefix.clone() + reason);
} else if !self.used_prefix.is_empty() {
alias = Some(self.used_prefix.clone() + connector_field.table().sql_safe_table_name());
}
self.add_join_clause(Join {
join_type: join_type,
connector_field: connector_field,
dock_table_alias: dock_table,
dock_field: dock_field,
alias: alias,
});
if let Some(dock_prefix) = dock_prefix {
return self.with_prefix(dock_prefix);
}
return self;
}
pub fn with_prefix(mut self, prefix: &Prefix) -> Self {
self.used_prefix = prefix.to_string();
return self;
}
pub fn joins_to_sql(&self) -> String {
let mut out: String = "".to_string();
let mut first = true;
for join in &self.sql_joins {
if first {
first = false;
} else {
out += "\n";
}
out += &join.to_sql();
}
return out;
}
pub fn where_values_as_params(&self) -> ParamsFromIter<Iter<'_, Value>> {
params_from_iter(self.where_values.iter())
}
pub fn as_invertable(self) -> InvertableRusqliteQuery<F> {
InvertableRusqliteQuery::AsRequested(self)
}
}
pub enum InvertableRusqliteQuery<F: Field> {
AsRequested(RusqliteQuery<F>),
Inverted(RusqliteQuery<F>),
}
impl<F: Field> InvertableRusqliteQuery<F> {
pub fn invert(self) -> Self {
match self {
Self::AsRequested(q) => Self::Inverted(q),
Self::Inverted(q) => Self::AsRequested(q),
}
}
pub fn invert_if(self, do_invert: bool) -> Self {
if do_invert {
self.invert()
} else {
self
}
}
pub fn is_inverted(&self) -> bool {
matches!(self, Self::Inverted(_))
}
pub fn get_corrected_query(self) -> RusqliteQuery<F> {
match self {
Self::AsRequested(q) => q,
Self::Inverted(q) => q.negate_where_clause(),
}
}
fn modify(&mut self) -> &mut RusqliteQuery<F> {
match self {
Self::AsRequested(ref mut q) => q,
Self::Inverted(ref mut q) => q,
}
}
fn inner(self) -> RusqliteQuery<F> {
match self {
Self::AsRequested(q) => q,
Self::Inverted(q) => q,
}
}
pub fn inner_join(
self,
reason: Option<&str>,
connector_field: F,
dock_prefix: Option<&Prefix>,
dock_field: F,
) -> Self {
let is_inverted = self.is_inverted();
self.inner()
.inner_join(reason, connector_field, dock_prefix, dock_field)
.as_invertable()
.invert_if(is_inverted)
}
pub fn with_prefix(mut self, prefix: &Prefix) -> Self {
self.modify().used_prefix = prefix.to_string();
self
}
}