criterium 3.1.3

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

use crate::sql::Prefix;
use crate::BooleanJoiner;
use crate::LogicPath;

#[derive(Debug, Clone)]
/// The AssemblyContext provides information on how to construct a query by providing
/// construction parameters via the [prefix][Self::prefix] and
/// indicating opportunities for optimization using the [logic_path][Self::logic_path].
pub struct AssemblyContext {
	prefix: Prefix,
	logic_path: LogicPath,
}

impl AssemblyContext {
	/// Construct a new, blank assembly context
	pub fn new_root() -> Self {
		Self {
			prefix: Prefix::empty(),
			logic_path: LogicPath::new_root(),
		}
	}

	/// Retrieve the current prefix for this assembly context
	pub fn prefix(&self) -> &Prefix {
		&self.prefix
	}

	/// Retrieve the current logic path for this assembly context
	pub fn logic_path(&self) -> &LogicPath {
		&self.logic_path
	}

	/// Append a token to the prefix, make sure to end the token with an `_` character.
	///
	/// See [Prefix::with] for details.
	pub fn prefix_with(&self, token: &str) -> Self {
		Self {
			prefix: self.prefix.with(token),
			logic_path: self.logic_path,
		}
	}

	/// Generates a unique prefix that may be shared if the current block joiner matches `group` for the returned context. Similar to how [prefix_with()][Self::prefix_with] works.
	///
	/// See [Prefix::unique_reason] for details.
	pub fn prefix_with_unique_reason(&self, reason: &str, group: Option<BooleanJoiner>) -> Self {
		Self {
			prefix: self.prefix.with_unique_reason(reason, group),
			logic_path: self.logic_path,
		}
	}

	/// Adapt the context to fit a usecase where the child statements will be concatenated using a boolen `and` joiner.
	pub fn in_and_block(&self) -> Self {
		Self {
			prefix: self.prefix.in_block(Some(BooleanJoiner::And)),
			logic_path: self.logic_path.and(),
		}
	}

	/// Adapt the context to fit a usecase where the child statements will be concatenated using a boolen `or` joiner.
	pub fn in_or_block(&self) -> Self {
		Self {
			prefix: self.prefix.in_block(Some(BooleanJoiner::Or)),
			logic_path: self.logic_path.or(),
		}
	}

	/// Adapt the context ot fit a usecase where its child statements are inverted.
	pub fn in_invert_block(&self) -> Self {
		Self {
			prefix: self.prefix.clone(),
			logic_path: self.logic_path.not(),
		}
	}

	/// Adapt the assembly context to being inside a subquery.
	///
	/// This implies:
	/// * setting a unique prefix
	/// * resetting the logic_path to a root state so that optimizations inside the subquery work.
	pub fn in_subquery_block(&self) -> Self {
		Self {
			prefix: self.prefix.with_unique_reason("subquery_", None),
			logic_path: LogicPath::new_root(),
		}
	}

	/// shorthand for `.logic_path().is_branch_inverted()`.
	///
	/// See [LogicPath::is_branch_inverted] for more information.
	pub fn is_branch_inverted(&self) -> bool {
		self.logic_path.is_branch_inverted()
	}
}