criterium 3.1.3

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

//! Integrates the TagCriterium with rusqlite

use crate::rusqlite::assembler::*;
use crate::sql::Field;
use crate::sql::Table;
use crate::TagCriterium;

impl<T> TagCriterium<T> {
	/// Assemble a rusqite query from this TagCriterium that uses the given `source_field` to join against the given `dock_field`.
	///
	/// Currently there is no way to express more complex join logic using this implementation.
	///
	/// Depending on the type of tag criterium this will either result in some kind of join or fall back to creating a subquery.
	pub fn assemble_rusqlite_query<F: Field, C>(
		&self,
		assembly_context: &AssemblyContext,
		source_field: &F,
		dock_prefix: &Prefix,
		dock_field: &F,
		context: &C,
	) -> InvertableRusqliteQuery<F>
	where
		T: AssembleRusqliteQuery<F, C>,
	{
		self.assemble_rusqlite_query_as_subquery(
			assembly_context,
			source_field,
			dock_prefix,
			dock_field,
			context,
		)
		.as_invertable()
	}

	fn assemble_rusqlite_query_as_subquery<F: Field, C>(
		&self,
		assembly_context: &AssemblyContext,
		source_field: &F,
		dock_prefix: &Prefix,
		dock_field: &F,
		context: &C,
	) -> RusqliteQuery<F>
	where
		T: AssembleRusqliteQuery<F, C>,
	{
		let sub_assembly_context = assembly_context.in_subquery_block();
		match self {
			Self::Has(chain) | Self::HasNot(chain) => {
				let subquery = chain
					.assemble_rusqlite_query(&sub_assembly_context, context)
					.get_corrected_query();
				let source_table_alias = source_field
					.table()
					.sql_safe_prefixed_table_name(sub_assembly_context.prefix());
				let operator = if matches!(self, Self::Has(_)) {
					"IN"
				} else {
					"NOT IN"
				};
				return RusqliteQuery {
					used_prefix: dock_prefix.to_string(),
					sql_where_clause: format!(
						"{} {operator} (
						SELECT {}
						FROM {} as {source_table_alias}
						{}
						WHERE {}
					)",
						dock_field.sql_safe_prefixed_field_name(dock_prefix),
						source_field.sql_safe_prefixed_field_name(sub_assembly_context.prefix()),
						source_field.table().sql_safe_table_name(),
						subquery.joins_to_sql(),
						subquery.sql_where_clause
					),
					sql_joins: vec![],
					where_values: subquery.where_values,
				};
			}
			Self::HasNOf { n, chain } => {
				let subquery = chain
					.assemble_rusqlite_query(&sub_assembly_context, context)
					.get_corrected_query();
				let source_table_alias = source_field
					.table()
					.sql_safe_prefixed_table_name(sub_assembly_context.prefix());
				return n.assemble_rusqlite_query_with_subquery(
					dock_prefix.clone(),
					format!(
						"(
						SELECT COUNT(*)
						FROM {} as {source_table_alias}
						{}
						WHERE {} = {} AND ({})
					)",
						source_field.table().sql_safe_table_name(),
						subquery.joins_to_sql(),
						source_field.sql_safe_prefixed_field_name(sub_assembly_context.prefix()),
						dock_field.sql_safe_prefixed_field_name(dock_prefix),
						subquery.sql_where_clause
					)
					.as_str(),
					subquery.where_values,
				);
			}
		}
	}
}