1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::{filter::Filter, RelationCompare, RelationField};

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct RelationFilter {
    /// Starting field of the relation traversal.
    pub field: RelationField,

    /// Filter the related records need to fulfill.
    pub nested_filter: Box<Filter>,

    /// The type of relation condition to use.
    /// E.g. if all related records or only some need
    /// to fulfill `nested_filter`.
    pub condition: RelationCondition,
}

impl std::fmt::Debug for RelationFilter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RelationFilter")
            .field("field", &format!("{}", self.field))
            .field("nested_filter", &self.nested_filter)
            .field("condition", &self.condition)
            .finish()
    }
}

impl RelationFilter {
    pub fn invert(self, invert: bool) -> Self {
        if invert {
            let is_to_one = !self.field.is_list();

            Self {
                field: self.field,
                nested_filter: self.nested_filter,
                condition: self.condition.invert(invert, is_to_one),
            }
        } else {
            self
        }
    }
}

/// Filter that is solely responsible for checking if
/// a to-one related record is null.
/// Todo there's no good, obvious reason why this is a separate filter.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OneRelationIsNullFilter {
    pub field: RelationField,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RelationCondition {
    /// Every single related record needs to fulfill a condition.
    /// `every` query condition.
    EveryRelatedRecord,

    /// At least one related record needs to fulfill a condition.
    /// `some` query condition.
    AtLeastOneRelatedRecord,

    /// No related record must to fulfill a condition.
    /// `none` query condition.
    NoRelatedRecord,

    /// To-one relation only - the related record must fulfill a condition.
    ToOneRelatedRecord,
}

impl RelationCondition {
    pub fn invert_of_subselect(self) -> bool {
        matches!(self, RelationCondition::EveryRelatedRecord)
    }

    pub fn invert(self, invert: bool, to_one: bool) -> Self {
        if invert {
            match self {
                RelationCondition::EveryRelatedRecord => RelationCondition::NoRelatedRecord,
                RelationCondition::AtLeastOneRelatedRecord => RelationCondition::NoRelatedRecord,
                RelationCondition::NoRelatedRecord if to_one => RelationCondition::ToOneRelatedRecord,
                RelationCondition::NoRelatedRecord => RelationCondition::AtLeastOneRelatedRecord,
                RelationCondition::ToOneRelatedRecord => RelationCondition::NoRelatedRecord,
            }
        } else {
            self
        }
    }
}

impl RelationCompare for RelationField {
    /// Every related record matches the filter.
    fn every_related<T>(&self, filter: T) -> Filter
    where
        T: Into<Filter>,
    {
        Filter::from(RelationFilter {
            field: self.clone(),
            nested_filter: Box::new(filter.into()),
            condition: RelationCondition::EveryRelatedRecord,
        })
    }

    /// At least one related record matches the filter.
    fn at_least_one_related<T>(&self, filter: T) -> Filter
    where
        T: Into<Filter>,
    {
        Filter::from(RelationFilter {
            field: self.clone(),
            nested_filter: Box::new(filter.into()),
            condition: RelationCondition::AtLeastOneRelatedRecord,
        })
    }

    /// To one related record. FIXME
    fn to_one_related<T>(&self, filter: T) -> Filter
    where
        T: Into<Filter>,
    {
        Filter::from(RelationFilter {
            field: self.clone(),
            nested_filter: Box::new(filter.into()),
            condition: RelationCondition::ToOneRelatedRecord,
        })
    }

    /// None of the related records matches the filter.
    fn no_related<T>(&self, filter: T) -> Filter
    where
        T: Into<Filter>,
    {
        Filter::from(RelationFilter {
            field: self.clone(),
            nested_filter: Box::new(filter.into()),
            condition: RelationCondition::NoRelatedRecord,
        })
    }

    /// One of the relations is `Null`.
    fn one_relation_is_null(&self) -> Filter {
        Filter::from(OneRelationIsNullFilter { field: self.clone() })
    }
}