criterium 3.1.3

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

use std::fmt::Debug;

use crate::sql::Prefix;
use crate::sql::Table;

/// Trait for datatructures representing Fields in an SQL Database schema or Table.
///
/// It is recommended that one has one type that can represent any fields that could possibly used together in a query.
///
/// Criterium recommends that you represent your fields using enums, so that relevant metadata is available everywhere the field is used and to make it possible for the compiler to check for mistakes.
pub trait Field: Debug + Clone + PartialEq + Eq {
	/// Datatype that is used to represent all possible tables the fields in this field type can have.
	///
	/// It must implement the [Table] trait.
	type TableType: Table;

	/// Returns an sql safe name for the field that can be used without quoting.
	///
	/// This is without the table name!
	///
	/// Stick to `A`-`Z`, `a`-`z`, `0`-`9` and `_` here.
	fn sql_safe_field_name(&self) -> &str;

	/// Returns the table this field is sourced from.
	fn table(&self) -> &Self::TableType;

	/// Constructs the `table.name` form that uniquely identifies the field within a database schema.
	fn sql_safe_full_field_name(&self) -> String {
		format!(
			"{}.{}",
			self.table().sql_safe_table_name(),
			self.sql_safe_field_name()
		)
	}

	/// Constructs the prefixed `table.name` form that criterium uses to generate unique names when possibly dealing with joining the same table multiple times for different purposes.
	fn sql_safe_prefixed_field_name(&self, prefix: &Prefix) -> String {
		let table_name = self.table().sql_safe_table_name();
		if let Some((database, table_name)) = table_name.split_once(".") {
			// Handle database prefixed table names (i.e. temp.some_table)
			format!(
				"{database}.{prefix}{table_name}.{}",
				self.sql_safe_field_name()
			)
		} else {
			format!("{prefix}{table_name}.{}", self.sql_safe_field_name())
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::sql::example_schema::ExampleSchema;

	#[test]
	fn test_prefixing() {
		let example_prefix = Prefix::new("epfx_".to_string());
		// Validate assumptions
		assert_eq!(
			ExampleSchema::FileName.sql_safe_full_field_name(),
			"files.name"
		);
		assert_eq!(
			ExampleSchema::TempKeyValueCacheKey.sql_safe_full_field_name(),
			"temp.key_value_cache.key"
		);
		// Test the prefixed variant
		assert_eq!(
			ExampleSchema::FileName.sql_safe_prefixed_field_name(&example_prefix),
			"epfx_files.name"
		);
		assert_eq!(
			ExampleSchema::TempKeyValueCacheKey.sql_safe_prefixed_field_name(&example_prefix),
			"temp.epfx_key_value_cache.key"
		);
	}
}