criterium 3.1.3

Lightweigt dynamic database queries for rusqlite.
Documentation
// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-only

//! Integrates the NumberCriterium with rusqlite

use rusqlite::types::Value;

use crate::number::AsInteger;
use crate::rusqlite::RusqliteQuery;
use crate::rusqlite::ToRusqliteSingleField;
use crate::sql::Field;
use crate::sql::Prefix;
use crate::NumberCriterium;

/// Implement NumberCriterium for integer types that cast to `i64`
impl<N, F: Field> ToRusqliteSingleField<F> for NumberCriterium<N>
where
	N: AsInteger,
{
	fn get_sql_where(&self, field_name: &str) -> String {
		self.get_number_criterium_sql_where(field_name)
	}

	fn get_inverted_sql_where(&self, field_name: &str) -> Option<String> {
		self.get_number_criterium_inverted_sql_where(field_name)
	}

	fn get_where_values(&self, _is_inverted: bool) -> Vec<Value> {
		self.get_number_criterium_where_values()
	}
}

impl<N> NumberCriterium<N> {
	fn get_number_criterium_sql_where(&self, field_name: &str) -> String {
		match self {
			Self::Equals(_) => format!("{field_name} = ?"),
			Self::LessThan(_) => format!("{field_name} < ?"),
			Self::LessThanOrEqual(_) => format!("{field_name} <= ?"),
			Self::GreaterThan(_) => format!("{field_name} > ?"),
			Self::GreaterThanOrEqual(_) => format!("{field_name} >= ?"),
			Self::InList(l) => {
				if !l.is_empty() {
					format!("{} IN (?{})", field_name, ",?".repeat(l.len() - 1))
				} else {
					"0".to_string()
				}
			}
			Self::IsNone => format!("{field_name} is NULL"),
		}
	}

	fn get_number_criterium_inverted_sql_where(&self, field_name: &str) -> Option<String> {
		Some(match self {
			Self::Equals(_) => format!("{field_name} != ?"),
			Self::LessThan(_) => format!("{field_name} >= ?"),
			Self::LessThanOrEqual(_) => format!("{field_name} > ?"),
			Self::GreaterThan(_) => format!("{field_name} <= ?"),
			Self::GreaterThanOrEqual(_) => format!("{field_name} < ?"),
			Self::InList(l) => {
				if !l.is_empty() {
					format!("{} NOT IN (?{})", field_name, ",?".repeat(l.len() - 1))
				} else {
					"1".to_string()
				}
			}
			Self::IsNone => format!("{field_name} is not NULL"),
		})
	}
}

impl<N> NumberCriterium<N>
where
	N: AsInteger,
{
	fn get_number_criterium_where_values(&self) -> Vec<Value> {
		vec![(match self {
			Self::Equals(n) => n,
			Self::LessThan(n) => n,
			Self::LessThanOrEqual(n) => n,
			Self::GreaterThan(n) => n,
			Self::GreaterThanOrEqual(n) => n,
			Self::InList(l) => {
				return l.iter().map(|i| i.as_criterium_i64().into()).collect();
			}
			Self::IsNone => {
				return Vec::new();
			}
		})
		.as_criterium_i64()
		.into()]
	}

	/// Helper for comparing the result of a subquery to a number criterium
	/// when assembling a rusqlite query.
	///
	/// The `sql_subquery` should be only the subquery in parenthesis.
	///
	/// The `where_values` are the values for the subquery placeholders.
	///
	/// Note for a generic implementation: This query style works because
	/// for the number query one can assume that the number query placeholder always
	/// comes after the inserted query text.
	pub fn assemble_rusqlite_query_with_subquery<F: Field>(
		&self,
		prefix: Prefix,
		sql_subquery: &str,
		mut where_values: Vec<Value>,
	) -> RusqliteQuery<F> {
		where_values.append(&mut self.get_number_criterium_where_values());
		RusqliteQuery {
			used_prefix: prefix.to_string(),
			sql_where_clause: self.get_number_criterium_sql_where(sql_subquery),
			where_values: where_values,
			sql_joins: Vec::new(),
		}
	}
}

macro_rules! impl_for_number_criterium_with_cast {
	($e:ty) => {
		impl<F: Field> ToRusqliteSingleField<F> for NumberCriterium<$e> {
			fn get_sql_where(&self, field_name: &str) -> String {
				self.translate(|n| n as i64)
					.get_number_criterium_sql_where(field_name)
			}

			fn get_inverted_sql_where(&self, field_name: &str) -> Option<String> {
				self.translate(|n| n as i64)
					.get_number_criterium_inverted_sql_where(field_name)
			}

			fn get_where_values(&self, _is_inverted: bool) -> Vec<Value> {
				self.translate(|n| n as i64)
					.get_number_criterium_where_values()
			}
		}
	};
}

// For both isize and usize converting to i64 is usually no problem
// but the guarantee isn't strong enough for me to want to add them to
// the `AsInteger` trait.

impl_for_number_criterium_with_cast!(isize);
impl_for_number_criterium_with_cast!(usize);