criterium 3.1.3

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

use rusqlite::types::Value;

use crate::rusqlite::AssemblyContext;
use crate::rusqlite::InvertableRusqliteQuery;
use crate::rusqlite::RusqliteQuery;

use crate::sql::Field;

/// Implement on primitives to make them useable with rusqlite.
pub trait ToRusqliteSingleField<F: Field> {
	/// Returns an SQL-statement with `?` placeholders
	/// targetting the field with the given name as part
	/// of a `WHERE` clause.
	///
	/// The `field_name` must directly be used in the SQL-Statement
	/// (apply neccessary caution to avoid SQL-injection),
	/// it may include a table name i.e. `some_table.id`.
	///
	/// The returned statement must be chainable using `AND`, `OR` and `NOT` operators.
	/// It should not contain any spaces sourrounding it and should only be put in
	/// parenthesis `(`, `)` when neccessary.
	fn get_sql_where(&self, field_name: &str) -> String;

	/// This gets called in place of `get_inverted_sql_where` if it would make sense to generate the inverted version of a condition.
	///
	/// It it returns `None` the generation mechanism falls back to the default path
	/// of generating the non-inverted version which then gets inverted when needed.
	///
	/// Also see [`get_where_values()`][Self::get_where_values].
	fn get_inverted_sql_where(&self, _field_name: &str) -> Option<String> {
		None
	}

	/// Returns the exact number of values
	/// to fullfill the placeholders returned by `get_sql_where()`.
	///
	/// The `is_inverted` is set to true if the sql was generated by
	/// `get_inverted_sql_where()`.
	fn get_where_values(&self, is_inverted: bool) -> Vec<Value>;

	/// Assemble an `RusqliteQuery` from this field.
	///
	/// Inteded to be used when implementing an enum that has more gneric
	/// criteria as its children.
	///
	/// *Make sure to always pass the `field_name`
	/// in the form of `table.field`.*
	///
	/// There should be no need to change the standard implementation.
	fn assemble_query(
		&self,
		assembly_context: &AssemblyContext,
		field: &F,
	) -> InvertableRusqliteQuery<F> {
		let full_field_name = field.sql_safe_prefixed_field_name(assembly_context.prefix());

		let where_clause = if assembly_context.is_branch_inverted() {
			self.get_inverted_sql_where(&full_field_name)
		} else {
			None
		};
		let is_inverted = where_clause.is_some();
		let where_clause = if let Some(where_clause) = where_clause {
			where_clause
		} else {
			self.get_sql_where(&full_field_name)
		};
		RusqliteQuery {
			used_prefix: assembly_context.prefix().to_string(),
			sql_where_clause: where_clause,
			where_values: self.get_where_values(is_inverted),
			sql_joins: Vec::new(),
		}
		.as_invertable()
		.invert_if(is_inverted)
	}
}