use crate::model::Model;
use crate::query::condition::Condition;
use crate::query::field::DjogiFieldProvenance;
use crate::query::q::{IntoQ, Q};
use sassi::{BasicPredicate, IntoBasicPredicate};
use std::ops::{BitAnd, BitOr, BitXor, Not};
mod sealed {
pub trait Sealed {}
}
pub struct PortablePredicate<T: Model> {
inner: BasicPredicate<T>,
field_provenance: Option<DjogiFieldProvenance>,
}
impl<T: Model> PortablePredicate<T> {
#[doc(hidden)]
pub(crate) fn from_djogi_field(
inner: BasicPredicate<T>,
provenance: DjogiFieldProvenance,
) -> Self {
Self {
inner,
field_provenance: Some(provenance),
}
}
#[doc(hidden)]
#[allow(dead_code)] pub(crate) fn always_true() -> Self {
Self {
inner: BasicPredicate::True,
field_provenance: None,
}
}
#[doc(hidden)]
#[allow(dead_code)] pub(crate) fn always_false() -> Self {
Self {
inner: BasicPredicate::False,
field_provenance: None,
}
}
pub fn into_inner(self) -> BasicPredicate<T> {
self.inner
}
#[doc(hidden)]
#[allow(dead_code)] pub(crate) fn inner_ref(&self) -> &BasicPredicate<T> {
&self.inner
}
#[doc(hidden)]
#[allow(dead_code)] pub(crate) fn has_field_provenance(&self) -> bool {
self.field_provenance.is_some()
}
}
impl<T: Model> Clone for PortablePredicate<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
field_provenance: self.field_provenance,
}
}
}
impl<T: Model> std::fmt::Debug for PortablePredicate<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = match &self.inner {
BasicPredicate::True => "True",
BasicPredicate::False => "False",
BasicPredicate::Field(_) => "Field",
BasicPredicate::And(_) => "And",
BasicPredicate::Or(_) => "Or",
BasicPredicate::Not(_) => "Not",
BasicPredicate::Xor(_, _) => "Xor",
_ => "<unknown>",
};
f.debug_struct("PortablePredicate")
.field("provenance", &self.field_provenance)
.field("kind", &kind)
.finish_non_exhaustive()
}
}
impl<T: Model> IntoBasicPredicate<T> for PortablePredicate<T> {
fn into_basic_predicate(self) -> BasicPredicate<T> {
self.inner
}
}
pub trait IntoPortablePredicate<T: Model>: sealed::Sealed {
fn into_portable_predicate(self) -> PortablePredicate<T>;
}
impl<T: Model> sealed::Sealed for PortablePredicate<T> {}
impl<T: Model> IntoPortablePredicate<T> for PortablePredicate<T> {
#[inline]
fn into_portable_predicate(self) -> PortablePredicate<T> {
self
}
}
fn merge_provenance(
lhs: Option<DjogiFieldProvenance>,
rhs: Option<DjogiFieldProvenance>,
) -> Option<DjogiFieldProvenance> {
lhs.or(rhs)
}
impl<T: Model> BitAnd for PortablePredicate<T> {
type Output = PortablePredicate<T>;
fn bitand(self, rhs: Self) -> PortablePredicate<T> {
let provenance = merge_provenance(self.field_provenance, rhs.field_provenance);
PortablePredicate {
inner: self.inner & rhs.inner,
field_provenance: provenance,
}
}
}
impl<T: Model> BitOr for PortablePredicate<T> {
type Output = PortablePredicate<T>;
fn bitor(self, rhs: Self) -> PortablePredicate<T> {
let provenance = merge_provenance(self.field_provenance, rhs.field_provenance);
PortablePredicate {
inner: self.inner | rhs.inner,
field_provenance: provenance,
}
}
}
impl<T: Model> BitXor for PortablePredicate<T> {
type Output = PortablePredicate<T>;
fn bitxor(self, rhs: Self) -> PortablePredicate<T> {
let provenance = merge_provenance(self.field_provenance, rhs.field_provenance);
PortablePredicate {
inner: self.inner ^ rhs.inner,
field_provenance: provenance,
}
}
}
impl<T: Model> Not for PortablePredicate<T> {
type Output = PortablePredicate<T>;
fn not(self) -> PortablePredicate<T> {
PortablePredicate {
inner: !self.inner,
field_provenance: self.field_provenance,
}
}
}
pub struct Predicate<T: Model> {
inner: Q<T>,
}
impl<T: Model> Predicate<T> {
#[doc(hidden)]
pub(crate) fn from_q(inner: Q<T>) -> Self {
Self { inner }
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn into_inner(self) -> Q<T> {
self.inner
}
}
impl<T: Model> Clone for Predicate<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: Model> std::fmt::Debug for Predicate<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Predicate").field(&self.inner).finish()
}
}
impl<T: Model> crate::query::q::__SealedIntoQ for PortablePredicate<T> {}
impl<T: Model> IntoQ<T> for PortablePredicate<T> {
#[inline]
fn into_q(self) -> Q<T> {
Q::Portable(self)
}
}
impl<T: Model> crate::query::q::__SealedIntoQ for Predicate<T> {}
impl<T: Model> IntoQ<T> for Predicate<T> {
#[inline]
fn into_q(self) -> Q<T> {
self.inner
}
}
impl<T: Model> BitAnd for Predicate<T> {
type Output = Predicate<T>;
fn bitand(self, rhs: Self) -> Predicate<T> {
Predicate::from_q(self.inner & rhs.inner)
}
}
impl<T: Model> BitOr for Predicate<T> {
type Output = Predicate<T>;
fn bitor(self, rhs: Self) -> Predicate<T> {
Predicate::from_q(self.inner | rhs.inner)
}
}
impl<T: Model> BitXor for Predicate<T> {
type Output = Predicate<T>;
fn bitxor(self, rhs: Self) -> Predicate<T> {
Predicate::from_q(self.inner ^ rhs.inner)
}
}
impl<T: Model> Not for Predicate<T> {
type Output = Predicate<T>;
fn not(self) -> Predicate<T> {
Predicate::from_q(!self.inner)
}
}
impl<T: Model> BitAnd<Predicate<T>> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitand(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) & rhs.inner)
}
}
impl<T: Model> BitOr<Predicate<T>> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitor(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) | rhs.inner)
}
}
impl<T: Model> BitXor<Predicate<T>> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitxor(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) ^ rhs.inner)
}
}
impl<T: Model> BitAnd<PortablePredicate<T>> for Predicate<T> {
type Output = Predicate<T>;
fn bitand(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(self.inner & Q::Portable(rhs))
}
}
impl<T: Model> BitOr<PortablePredicate<T>> for Predicate<T> {
type Output = Predicate<T>;
fn bitor(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(self.inner | Q::Portable(rhs))
}
}
impl<T: Model> BitXor<PortablePredicate<T>> for Predicate<T> {
type Output = Predicate<T>;
fn bitxor(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(self.inner ^ Q::Portable(rhs))
}
}
impl<T: Model> BitAnd<Condition> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitand(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) & Q::Condition(rhs))
}
}
impl<T: Model> BitOr<Condition> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitor(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) | Q::Condition(rhs))
}
}
impl<T: Model> BitXor<Condition> for PortablePredicate<T> {
type Output = Predicate<T>;
fn bitxor(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(Q::Portable(self) ^ Q::Condition(rhs))
}
}
impl<T: Model> BitAnd<PortablePredicate<T>> for Condition {
type Output = Predicate<T>;
fn bitand(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) & Q::Portable(rhs))
}
}
impl<T: Model> BitOr<PortablePredicate<T>> for Condition {
type Output = Predicate<T>;
fn bitor(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) | Q::Portable(rhs))
}
}
impl<T: Model> BitXor<PortablePredicate<T>> for Condition {
type Output = Predicate<T>;
fn bitxor(self, rhs: PortablePredicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) ^ Q::Portable(rhs))
}
}
impl<T: Model> BitAnd<Condition> for Predicate<T> {
type Output = Predicate<T>;
fn bitand(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(self.inner & Q::Condition(rhs))
}
}
impl<T: Model> BitOr<Condition> for Predicate<T> {
type Output = Predicate<T>;
fn bitor(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(self.inner | Q::Condition(rhs))
}
}
impl<T: Model> BitXor<Condition> for Predicate<T> {
type Output = Predicate<T>;
fn bitxor(self, rhs: Condition) -> Predicate<T> {
Predicate::from_q(self.inner ^ Q::Condition(rhs))
}
}
impl<T: Model> BitAnd<Predicate<T>> for Condition {
type Output = Predicate<T>;
fn bitand(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) & rhs.inner)
}
}
impl<T: Model> BitOr<Predicate<T>> for Condition {
type Output = Predicate<T>;
fn bitor(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) | rhs.inner)
}
}
impl<T: Model> BitXor<Predicate<T>> for Condition {
type Output = Predicate<T>;
fn bitxor(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::from_q(Q::Condition(self) ^ rhs.inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DjogiError;
use crate::descriptor::ModelDescriptor;
use std::future::Future;
#[derive(Debug)]
struct Fake;
impl crate::model::__sealed::Sealed for Fake {}
#[allow(clippy::manual_async_fn)]
impl Model for Fake {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fakes"
}
fn pk_value(&self) -> &i64 {
unimplemented!()
}
fn descriptor() -> &'static ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl Future<Output = Result<Self, DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl Future<Output = Result<Self, DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<Self, DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
}
#[test]
fn always_true_carries_no_provenance() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
assert!(!p.has_field_provenance());
match p.into_inner() {
BasicPredicate::True => {}
other => panic!("expected BasicPredicate::True, got {other:?}"),
}
}
#[test]
fn always_false_carries_no_provenance() {
let p: PortablePredicate<Fake> = PortablePredicate::always_false();
assert!(!p.has_field_provenance());
match p.into_inner() {
BasicPredicate::False => {}
other => panic!("expected BasicPredicate::False, got {other:?}"),
}
}
#[test]
fn into_basic_predicate_yields_inner() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let bp: BasicPredicate<Fake> = p.into_basic_predicate();
match bp {
BasicPredicate::True => {}
other => panic!("expected True, got {other:?}"),
}
}
#[test]
fn into_portable_predicate_is_identity_for_self() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let q: PortablePredicate<Fake> = p.into_portable_predicate();
assert!(!q.has_field_provenance());
}
#[test]
fn pure_portable_and_combines_inner() {
let lhs: PortablePredicate<Fake> = PortablePredicate::always_true();
let rhs: PortablePredicate<Fake> = PortablePredicate::always_false();
let composed = lhs & rhs;
match composed.into_inner() {
BasicPredicate::And(parts) => {
assert_eq!(parts.len(), 2);
match (&parts[0], &parts[1]) {
(BasicPredicate::True, BasicPredicate::False) => {}
other => panic!("expected [True, False], got {other:?}"),
}
}
other => panic!("expected And, got {other:?}"),
}
}
#[test]
fn pure_portable_or_combines_inner() {
let lhs: PortablePredicate<Fake> = PortablePredicate::always_true();
let rhs: PortablePredicate<Fake> = PortablePredicate::always_false();
let composed = lhs | rhs;
match composed.into_inner() {
BasicPredicate::Or(parts) => {
assert_eq!(parts.len(), 2);
}
other => panic!("expected Or, got {other:?}"),
}
}
#[test]
fn pure_portable_xor_combines_inner() {
let lhs: PortablePredicate<Fake> = PortablePredicate::always_true();
let rhs: PortablePredicate<Fake> = PortablePredicate::always_false();
let composed = lhs ^ rhs;
match composed.into_inner() {
BasicPredicate::Xor(_, _) => {}
other => panic!("expected Xor, got {other:?}"),
}
}
#[test]
fn pure_portable_not_negates_inner() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let negated = !p;
match negated.into_inner() {
BasicPredicate::False => {}
other => panic!("expected False, got {other:?}"),
}
}
#[test]
fn double_negation_collapses_through_sassi() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let inner = p.into_inner();
let composed = !(!PortablePredicate {
inner,
field_provenance: None,
});
match composed.into_inner() {
BasicPredicate::True => {}
other => panic!("expected True after double-negation, got {other:?}"),
}
}
#[test]
fn manual_clone_does_not_require_t_clone() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let q = p.clone();
assert!(!q.has_field_provenance());
}
#[test]
fn portable_predicate_into_q_yields_q_portable() {
let p: PortablePredicate<Fake> = PortablePredicate::always_true();
let q: Q<Fake> = p.into_q();
match q {
Q::Portable(_) => {}
other => panic!("expected Q::Portable(_), got {other:?}"),
}
}
#[test]
fn predicate_into_q_unwraps_inner() {
let q_inner: Q<Fake> = Q::always_true();
let p: Predicate<Fake> = Predicate::from_q(q_inner);
match p.into_q() {
Q::Portable(_) => {}
other => panic!("expected Q::Portable(_), got {other:?}"),
}
}
#[test]
fn condition_into_q_wraps_in_q_condition() {
use crate::query::condition::{Condition, FilterValue, Leaf};
let c = Condition::Leaf(Leaf::eq_raw("col", FilterValue::Bool(true)));
let q: Q<Fake> = c.into_q();
match q {
Q::Condition(_) => {}
other => panic!("expected Q::Condition(_), got {other:?}"),
}
}
#[test]
fn mixed_portable_and_condition_yields_predicate() {
use crate::query::condition::{Condition, FilterValue, Leaf};
let portable: PortablePredicate<Fake> = PortablePredicate::always_true();
let condition = Condition::Leaf(Leaf::eq_raw("col", FilterValue::Bool(true)));
let combined: Predicate<Fake> = portable & condition;
match combined.into_q() {
Q::Compound { parts, .. } => {
assert_eq!(parts.len(), 2);
assert!(matches!(parts[0], Q::Portable(_)));
assert!(matches!(parts[1], Q::Condition(_)));
}
other => panic!("expected Q::Compound{{And, ..}}, got {other:?}"),
}
}
#[test]
fn mixed_condition_and_portable_yields_predicate() {
use crate::query::condition::{Condition, FilterValue, Leaf};
let portable: PortablePredicate<Fake> = PortablePredicate::always_true();
let condition = Condition::Leaf(Leaf::eq_raw("col", FilterValue::Bool(true)));
let combined: Predicate<Fake> = condition & portable;
match combined.into_q() {
Q::Compound { parts, .. } => {
assert_eq!(parts.len(), 2);
assert!(matches!(parts[0], Q::Condition(_)));
assert!(matches!(parts[1], Q::Portable(_)));
}
other => panic!("expected Q::Compound{{And, ..}}, got {other:?}"),
}
}
#[test]
fn mixed_three_term_composition_compiles_without_manual_q() {
use crate::query::condition::{Condition, FilterValue, Leaf};
let a: PortablePredicate<Fake> = PortablePredicate::always_true();
let b: Condition = Condition::Leaf(Leaf::eq_raw("x", FilterValue::Bool(false)));
let c: PortablePredicate<Fake> = PortablePredicate::always_false();
let result: Predicate<Fake> = (a & b) | c;
match result.into_q() {
Q::Compound { op, parts } => {
assert_eq!(parts.len(), 2);
assert!(matches!(parts[0], Q::Compound { .. }));
assert!(matches!(parts[1], Q::Portable(_)));
let _ = op;
}
other => panic!("expected Q::Compound (outer OR), got {other:?}"),
}
}
#[test]
fn predicate_not_wraps_in_q_negated() {
let inner: Q<Fake> = Q::Compound {
op: crate::query::q::CompoundOp::And,
parts: vec![Q::always_true(), Q::always_false()],
};
let p: Predicate<Fake> = Predicate::from_q(inner);
let negated: Predicate<Fake> = !p;
match negated.into_q() {
Q::Negated(_) => {}
other => panic!("expected Q::Negated, got {other:?}"),
}
}
#[test]
fn expr_bool_into_q_yields_q_expression() {
use crate::expr::Expr;
let expr: Expr<bool> = Expr::literal(true);
let q: Q<Fake> = expr.into_q();
match q {
Q::Expression(_) => {}
other => panic!("expected Q::Expression(_), got {other:?}"),
}
}
}