criterium 3.1.3

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

//! rusqlite integration for the `CriteriumChain` using the `AssembleRusqliteQuery`.

use crate::rusqlite::AssembleRusqliteQuery;
use crate::rusqlite::AssemblyContext;
use crate::rusqlite::InvertableRusqliteQuery;
use crate::rusqlite::RusqliteQuery;
use crate::rusqlite::TryAssembleRusqliteQuery;
use crate::sql::Field;
use crate::CriteriumChain;

//////////////////////////////////////////////////////////
// Developers!
// The functionality in this file is implemented twice,
// make srue to update both implementations.

impl<T, F, C> TryAssembleRusqliteQuery<F, C> for CriteriumChain<T>
where
	T: TryAssembleRusqliteQuery<F, C>,
	F: Field,
{
	type Error = T::Error;

	fn try_assemble_rusqlite_query(
		&self,
		assembly_context: &AssemblyContext,
		context: &C,
	) -> Result<InvertableRusqliteQuery<F>, T::Error> {
		match self {
			Self::And(list) => try_chain_assembled_criterium_chains(
				&list.criteria,
				&assembly_context.in_and_block(),
				" AND ",
				list.fallback,
				context,
			),
			Self::Or(list) => try_chain_assembled_criterium_chains(
				&list.criteria,
				&assembly_context.in_or_block(),
				" OR ",
				list.fallback,
				context,
			),
			Self::NotChain(c) => Ok(c
				.try_assemble_rusqlite_query(&assembly_context.in_invert_block(), context)?
				.invert()),
			Self::Not(c) => Ok(c
				.try_assemble_rusqlite_query(&assembly_context.in_invert_block(), context)?
				.invert()),
			Self::Match(c) => c.try_assemble_rusqlite_query(assembly_context, context),
			Self::MatchAlways =>
			// true if not inverted, false if inverted
			{
				Ok(
					RusqliteQuery::static_bool(!assembly_context.is_branch_inverted())
						.as_invertable()
						.invert_if(assembly_context.is_branch_inverted()),
				)
			}
			Self::MatchNever =>
			// false if not inverted, true if inverted
			{
				Ok(
					RusqliteQuery::static_bool(assembly_context.is_branch_inverted())
						.as_invertable()
						.invert_if(assembly_context.is_branch_inverted()),
				)
			}
			Self::WithLikelihood {
				matcher,
				likelihood,
			} => Ok(matcher
				.try_assemble_rusqlite_query(assembly_context, context)?
				.get_corrected_query()
				.with_likelihood(*likelihood)
				.as_invertable()),
		}
	}
}

impl<T, F, C> AssembleRusqliteQuery<F, C> for CriteriumChain<T>
where
	T: AssembleRusqliteQuery<F, C>,
	F: Field,
{
	fn assemble_rusqlite_query(
		&self,
		assembly_context: &AssemblyContext,
		context: &C,
	) -> InvertableRusqliteQuery<F> {
		match self {
			Self::And(list) => chain_assembled_criterium_chains(
				&list.criteria,
				&assembly_context.in_and_block(),
				" AND ",
				list.fallback,
				context,
			),
			Self::Or(list) => chain_assembled_criterium_chains(
				&list.criteria,
				&assembly_context.in_or_block(),
				" OR ",
				list.fallback,
				context,
			),
			Self::NotChain(c) => c
				.assemble_rusqlite_query(&assembly_context.in_invert_block(), context)
				.invert(),
			Self::Not(c) => c
				.assemble_rusqlite_query(&assembly_context.in_invert_block(), context)
				.invert(),
			Self::Match(c) => c.assemble_rusqlite_query(assembly_context, context),
			Self::MatchAlways =>
			// true if not inverted, false if inverted
			{
				RusqliteQuery::static_bool(!assembly_context.is_branch_inverted())
					.as_invertable()
					.invert_if(assembly_context.is_branch_inverted())
			}
			Self::MatchNever =>
			// false if not inverted, true if inverted
			{
				RusqliteQuery::static_bool(assembly_context.is_branch_inverted())
					.as_invertable()
					.invert_if(assembly_context.is_branch_inverted())
			}
			Self::WithLikelihood {
				matcher,
				likelihood,
			} => matcher
				.assemble_rusqlite_query(assembly_context, context)
				.get_corrected_query()
				.with_likelihood(*likelihood)
				.as_invertable(),
		}
	}
}

fn try_chain_assembled_criterium_chains<T, F, C>(
	cs: &Vec<CriteriumChain<T>>,
	assembly_context: &AssemblyContext,
	joiner: &str,
	fallback: bool,
	context: &C,
) -> Result<InvertableRusqliteQuery<F>, T::Error>
where
	T: TryAssembleRusqliteQuery<F, C>,
	F: Field,
{
	let mut assembly: Option<RusqliteQuery<F>> = None;
	for c in cs {
		let a = c.try_assemble_finished_rusqlite_query(assembly_context, context)?;
		if let Some(old_assembly) = assembly {
			assembly = Some(old_assembly.concat(a, joiner));
		} else {
			assembly = Some(a);
		}
	}
	if let Some(assembly) = assembly {
		Ok(assembly.parenthesise_where_clause().as_invertable())
	} else {
		Ok(get_fallback_value(assembly_context, fallback))
	}
}

fn chain_assembled_criterium_chains<T, F, C>(
	cs: &Vec<CriteriumChain<T>>,
	assembly_context: &AssemblyContext,
	joiner: &str,
	fallback: bool,
	context: &C,
) -> InvertableRusqliteQuery<F>
where
	T: AssembleRusqliteQuery<F, C>,
	F: Field,
{
	let mut assembly: Option<RusqliteQuery<F>> = None;
	for c in cs {
		let a = c.assemble_finished_rusqlite_query(assembly_context, context);
		if let Some(old_assembly) = assembly {
			assembly = Some(old_assembly.concat(a, joiner));
		} else {
			assembly = Some(a);
		}
	}
	if let Some(assembly) = assembly {
		assembly.parenthesise_where_clause().as_invertable()
	} else {
		get_fallback_value(assembly_context, fallback)
	}
}

fn get_fallback_value<F>(
	assembly_context: &AssemblyContext,
	fallback: bool,
) -> InvertableRusqliteQuery<F>
where
	F: Field,
{
	if fallback != assembly_context.is_branch_inverted() {
		RusqliteQuery::static_bool(true)
			.as_invertable()
			.invert_if(assembly_context.is_branch_inverted())
	} else {
		RusqliteQuery::static_bool(false)
			.as_invertable()
			.invert_if(assembly_context.is_branch_inverted())
	}
}