criterium 3.1.3

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

use crate::CriteriumChain;

/// Encodes information on how the syntax tree below a given query is structured.
///
/// This way it is possible to optimize queries on the condition of a specific tree structure.
///
/// An example would be turning a subquery into a `JOIN` or `IN` given a plain `AND` or `OR` structure.
///
/// This struct itself is not mutable, it can only generate copies of itself with the given operation appended through the `and`, `or` and `not` methods.
#[derive(Debug, Clone, Copy)]
pub struct LogicPath {
	depth: usize,
	is_plain_and: bool,
	is_plain_or: bool,
	/// Parity over the whole path
	is_inverted: bool,
	/// Parity since the last `AND` or `OR`
	is_branch_inverted: bool,
}

impl LogicPath {
	/// Creates a new root for a logic path
	pub fn new_root() -> Self {
		LogicPath {
			depth: 0,
			is_plain_and: false,
			is_plain_or: true,
			is_inverted: false,
			is_branch_inverted: false,
		}
	}

	/// Returns the depth/length of the path,
	/// starts at 0.
	pub fn depth(&self) -> usize {
		self.depth
	}

	/// Returns wheter this struct represents the root node of a logic path
	pub fn is_root(&self) -> bool {
		self.depth == 0
	}

	/// Wheter the logic path is made up of only `AND` connections without any inversions.
	pub fn is_plain_and(&self) -> bool {
		self.is_plain_and
	}

	/// Wheter the logic_path is made up of only `OR` connections without any inversions.
	pub fn is_plain_or(&self) -> bool {
		self.is_plain_or
	}

	/// Returns the parity of all inversions so far and returns `true` for an uneven amount of inversions.
	///
	/// This can be used to determine wheter a match of the query could include or exclude an item.
	pub fn is_excluding(&self) -> bool {
		self.is_inverted
	}

	/// Returns wheter there has been an uneven amount of inversions since the last `AND` or `OR` statement.
	pub fn is_branch_inverted(&self) -> bool {
		self.is_branch_inverted
	}

	/// Return the path with an `AND` connection appended.
	pub fn and(&self) -> Self {
		Self {
			is_plain_or: false,
			is_plain_and: self.is_plain_and || self.is_root(),
			is_inverted: self.is_inverted,
			is_branch_inverted: false,
			depth: self.depth + 1,
		}
	}

	/// Return the path with an `OR` connection appended.
	pub fn or(&self) -> Self {
		Self {
			is_plain_and: false,
			is_plain_or: self.is_plain_or || self.is_root(),
			is_inverted: self.is_inverted,
			is_branch_inverted: false,
			depth: self.depth + 1,
		}
	}

	/// Return the path with an inversion appended.
	pub fn not(&self) -> Self {
		Self {
			is_plain_and: false,
			is_plain_or: false,
			is_inverted: !self.is_inverted,
			is_branch_inverted: !self.is_branch_inverted,
			depth: self.depth + 1,
		}
	}

	/// Apply the appropriate function for the given CriteriumChain to self.
	///
	/// `MatchAlways`, `MatchNever`, `Match` and `WithLikelihood` will return a copy of `self`.
	pub fn traverse<T>(&self, chain: &CriteriumChain<T>) -> Self {
		match chain {
			CriteriumChain::And(_) => self.and(),
			CriteriumChain::Or(_) => self.or(),
			CriteriumChain::NotChain(_) | CriteriumChain::Not(_) => self.not(),
			CriteriumChain::MatchAlways
			| CriteriumChain::MatchNever
			| CriteriumChain::Match(_)
			| CriteriumChain::WithLikelihood { .. } => *self,
		}
	}
}