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::Field;
use crate::sql::Table;

/// Enumerate different kinds of SQL join operations.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub enum JoinType {
	#[default]
	/// SQL inner join (the default)
	Inner,
	/// SQL outer join
	Left,
	/// SQL right join
	Right,
}

impl ToString for JoinType {
	fn to_string(&self) -> String {
		match self {
			Self::Inner => "inner",
			Self::Left => "left",
			Self::Right => "right",
		}
		.to_string()
	}
}

/// Represents a join statement for SQL.
///
/// All used fields must be safe to use in an SQL Query!
///
/// Will Serialize like this:
/// ```sql
/// {join_type} join "{source_table}"
/// on "{dock_table}.{dock_field}" = "{alias||source_table}.{connector_field||dock_field}"
/// [as "{alias}"]
/// ```
///
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Join<F: Field> {
	/// Which kind of join to perform
	pub join_type: JoinType,

	/// What name the joined table has after the join
	/// defaults to the table of the connector field.
	pub alias: Option<String>,

	/// The alias name of the table docked against
	pub dock_table_alias: String,

	/// The field that gets compared against.
	pub dock_field: F,

	/// The field in the soutce tablethat has to equal `dock_field`
	/// This also determines which table gets joined in.
	pub connector_field: F,
}

impl<F: Field> Join<F> {
	/// Serializes the Join struct to SQL
	///
	/// Note: `to_string()` was intentionally not used here
	/// as relying on that might have uninteded footgun-like
	/// consquences in the future.
	pub fn to_sql(&self) -> String {
		let join_as = if let Some(alias) = &self.alias {
			format!(" as \"{alias}\"")
		} else {
			"".to_string()
		};

		return format!(
			"{} join {}{} on {}.{} = {}.{}",
			self.join_type.to_string(),
			self.connector_field.table().sql_safe_table_name(),
			join_as,
			self.dock_table_alias,
			self.dock_field.sql_safe_field_name(),
			self.table_alias(),
			self.connector_field.sql_safe_field_name()
		);
	}

	/// Returns the name the table can be accessd under in an SQL statement.
	///
	/// This is either the `alias` if set or the `source_table` name.
	pub fn table_alias(&self) -> &str {
		if let Some(alias) = &self.alias {
			alias
		} else {
			self.connector_field.table().sql_safe_table_name()
		}
	}
}