use std::marker::PhantomData;
use crate::Model;
use crate::fields::ForeignKey;
use crate::query::{SqlValue, ToSqlValue};
pub struct Column<M, T> {
name: &'static str,
_marker: PhantomData<fn() -> (M, T)>,
}
impl<M, T> Copy for Column<M, T> {}
impl<M, T> Clone for Column<M, T> {
fn clone(&self) -> Self {
*self
}
}
impl<M, T> std::fmt::Debug for Column<M, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Column({:?})", self.name))
}
}
impl<M, T> Column<M, T> {
pub const fn new(name: &'static str) -> Self {
Self {
name,
_marker: PhantomData,
}
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn asc(self) -> Ordering<M> {
Ordering::new(self.name, false)
}
pub const fn desc(self) -> Ordering<M> {
Ordering::new(self.name, true)
}
}
pub struct Ordering<M> {
name: &'static str,
descending: bool,
_model: PhantomData<fn() -> M>,
}
impl<M> Ordering<M> {
const fn new(name: &'static str, descending: bool) -> Self {
Self {
name,
descending,
_model: PhantomData,
}
}
}
impl<M> Copy for Ordering<M> {}
impl<M> Clone for Ordering<M> {
fn clone(&self) -> Self {
*self
}
}
impl<M> std::fmt::Debug for Ordering<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Ordering({:?}, descending={})",
self.name, self.descending
))
}
}
pub trait ColumnRef<M> {
fn column_name(&self) -> &str;
}
impl<M, T> ColumnRef<M> for Column<M, T> {
fn column_name(&self) -> &str {
self.name
}
}
impl<M> ColumnRef<M> for &str {
fn column_name(&self) -> &str {
self
}
}
pub trait ForeignKeyRef<M, T>: ColumnRef<M> {}
impl<M, T: Model> ForeignKeyRef<M, T> for Column<M, ForeignKey<T>> {}
impl<M, T> ForeignKeyRef<M, T> for &str {}
pub struct ManyToManyField<M, R>(PhantomData<fn() -> (M, R)>);
impl<M, R> ManyToManyField<M, R> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<M, R> Default for ManyToManyField<M, R> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrefetchKind<'a> {
ReverseFk(&'a str),
ManyToMany,
}
pub trait PrefetchRef<M, R> {
fn prefetch_kind(&self) -> PrefetchKind<'_>;
}
impl<M: Model, R> PrefetchRef<M, R> for Column<R, ForeignKey<M>> {
fn prefetch_kind(&self) -> PrefetchKind<'_> {
PrefetchKind::ReverseFk(self.name)
}
}
impl<M, R> PrefetchRef<M, R> for &str {
fn prefetch_kind(&self) -> PrefetchKind<'_> {
PrefetchKind::ReverseFk(self)
}
}
impl<M, R> PrefetchRef<M, R> for ManyToManyField<M, R> {
fn prefetch_kind(&self) -> PrefetchKind<'_> {
PrefetchKind::ManyToMany
}
}
pub trait OrderRef<M> {
fn order_parts(&self) -> (&str, bool);
}
impl<M, T> OrderRef<M> for Column<M, T> {
fn order_parts(&self) -> (&str, bool) {
(self.name, false)
}
}
impl<M> OrderRef<M> for Ordering<M> {
fn order_parts(&self) -> (&str, bool) {
(self.name, self.descending)
}
}
impl<M> OrderRef<M> for &str {
fn order_parts(&self) -> (&str, bool) {
match self.strip_prefix("-") {
Some(name) => (name, true),
None => (*self, false),
}
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be compared to a `{T}` column",
label = "expected a `{T}` value or its plain Rust equivalent"
)]
pub trait FilterValue<T> {
fn into_sql_value(self) -> SqlValue;
}
#[diagnostic::do_not_recommend]
impl<T: ToSqlValue> FilterValue<T> for T {
fn into_sql_value(self) -> SqlValue {
self.to_sql_value()
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a column of this model that accepts a `{V}` value",
note = "pass a typed column such as `Model::NAME` with a value of its field type, or a column name as `&str`"
)]
pub trait FilterColumn<M, V>: ColumnRef<M> {
fn bind(&self, val: V) -> SqlValue;
}
#[diagnostic::do_not_recommend]
impl<M, T, V: FilterValue<T>> FilterColumn<M, V> for Column<M, T> {
fn bind(&self, val: V) -> SqlValue {
val.into_sql_value()
}
}
#[diagnostic::do_not_recommend]
impl<M, V: Into<SqlValue>> FilterColumn<M, V> for &str {
fn bind(&self, val: V) -> SqlValue {
val.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fields::{FieldEmail, FieldInt, FieldText};
struct User;
#[test]
fn filter_values_are_typed() {
let age = Column::<User, FieldInt>::new("age");
assert_eq!(age.bind(18), SqlValue::Int(18));
assert_eq!(age.bind(FieldInt(18)), SqlValue::Int(18));
let email = Column::<User, FieldEmail>::new("email");
assert_eq!(email.bind("a@b.c"), SqlValue::Text("a@b.c".into()));
assert_eq!(
FilterColumn::<User, _>::bind(&"age", 18i64),
SqlValue::BigInt(18)
);
}
#[test]
fn column_name() {
assert_eq!(
Column::<User, FieldText>::new("email").column_name(),
"email"
);
assert_eq!(ColumnRef::<User>::column_name(&"email"), "email");
}
#[test]
fn order_parts() {
let column = Column::<User, FieldText>::new("name");
assert_eq!(column.order_parts(), ("name", false));
assert_eq!(column.desc().order_parts(), ("name", true));
assert_eq!(column.asc().order_parts(), ("name", false));
assert_eq!(OrderRef::<User>::order_parts(&"-name"), ("name", true));
assert_eq!(OrderRef::<User>::order_parts(&"name"), ("name", false));
}
fn assert_traits<T: Send + Sync + Copy>() {}
#[test]
fn column_is_send_sync_copy() {
assert_traits::<Column<*const u8, *const u8>>();
}
}