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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Postgres range predicate payloads.
//!
//! `Range<T>` itself lives in [`crate::pg_types`]. This module owns the
//! query-side payloads for PostgreSQL range operators so `query::condition`
//! and `query::field` can share a small typed representation without storing
//! SQL fragments in the condition tree.
//!
//! Range predicates are SQL-only. They are exposed from root model fields
//! through `explicit_pg_predicate()` because Postgres range canonicalization
//! and operator semantics are not portable to Punnu/Rust evaluation.
use crate::query::condition::FilterValue;
/// PostgreSQL range operator used by a [`RangePredicateLeaf`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RangePredicateOp {
/// `@>`: range contains an element or another range.
Contains,
/// `<@`: range is contained by another range.
ContainedBy,
/// `&&`: ranges overlap.
Overlaps,
/// `<<`: range is strictly left of another range.
StrictlyLeftOf,
/// `>>`: range is strictly right of another range.
StrictlyRightOf,
/// `&<`: range does not extend right of another range.
NotExtendsRightOf,
/// `&>`: range does not extend left of another range.
NotExtendsLeftOf,
/// `-|-`: ranges are adjacent.
AdjacentTo,
}
impl RangePredicateOp {
/// SQL operator token with surrounding spaces for direct emitter insertion.
pub(crate) const fn sql_token(self) -> &'static str {
match self {
Self::Contains => " @> ",
Self::ContainedBy => " <@ ",
Self::Overlaps => " && ",
Self::StrictlyLeftOf => " << ",
Self::StrictlyRightOf => " >> ",
Self::NotExtendsRightOf => " &< ",
Self::NotExtendsLeftOf => " &> ",
Self::AdjacentTo => " -|- ",
}
}
}
/// Payload for `range_column OP $1`.
///
/// Fields are `pub(crate)` so the only construction path is through the
/// typed range predicate methods on `FieldRef`. Downstream code can inspect
/// range predicates via the read-only accessors but cannot forge a raw column
/// string into a public [`crate::query::condition::Condition`] variant.
#[derive(Debug, Clone)]
pub struct RangePredicateLeaf {
/// Column name from a trusted [`crate::query::field::FieldRef`].
pub(crate) column: &'static str,
/// PostgreSQL range operator.
pub(crate) op: RangePredicateOp,
/// Bound RHS value. For [`RangePredicateOp::Contains`] this may be either
/// an element value (`T`) or a range value (`Range<T>`); every other
/// operator carries a range RHS.
pub(crate) value: FilterValue,
/// Explicit scalar cast for `contains(element)` binds. Range-vs-range
/// predicates leave this `None` and keep the existing typed range bind.
pub(crate) rhs_element_cast: Option<&'static str>,
}
impl RangePredicateLeaf {
pub(crate) fn new(column: &'static str, op: RangePredicateOp, value: FilterValue) -> Self {
Self {
column,
op,
value,
rhs_element_cast: None,
}
}
pub(crate) fn with_rhs_element_cast(mut self, cast: &'static str) -> Self {
self.rhs_element_cast = Some(cast);
self
}
/// Column name from the trusted field reference that built this predicate.
pub fn column(&self) -> &'static str {
self.column
}
/// PostgreSQL range operator for this predicate.
pub fn op(&self) -> RangePredicateOp {
self.op
}
/// Bound right-hand-side value for this predicate.
pub fn value(&self) -> &FilterValue {
&self.value
}
/// Explicit scalar cast for `contains(element)` binds, if present.
pub fn rhs_element_cast(&self) -> Option<&'static str> {
self.rhs_element_cast
}
}
mod sealed {
pub trait Sealed {}
impl Sealed for i32 {}
impl Sealed for i64 {}
impl Sealed for rust_decimal::Decimal {}
impl Sealed for time::PrimitiveDateTime {}
impl Sealed for time::OffsetDateTime {}
impl Sealed for time::Date {}
}
/// Element types supported by Djogi's built-in range predicates.
///
/// Sealed so downstream code cannot pair a `Range<T>` field with a RHS value
/// that has no matching Djogi range bind variant.
pub trait RangeElement: sealed::Sealed + Clone + 'static {
/// Wrap a typed `Range<Self>` in the matching query bind carrier.
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue
where
Self: Sized;
/// Postgres scalar subtype used when `range @> element` needs an explicit
/// RHS cast to select the element overload.
fn sql_element_cast() -> &'static str
where
Self: Sized;
}
impl RangeElement for i32 {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeI32(range)
}
fn sql_element_cast() -> &'static str {
"int4"
}
}
impl RangeElement for i64 {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeI64(range)
}
fn sql_element_cast() -> &'static str {
"int8"
}
}
impl RangeElement for rust_decimal::Decimal {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeDecimal(range)
}
fn sql_element_cast() -> &'static str {
"numeric"
}
}
impl RangeElement for time::PrimitiveDateTime {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeTimestamp(range)
}
fn sql_element_cast() -> &'static str {
"timestamp"
}
}
impl RangeElement for time::OffsetDateTime {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeDateTime(range)
}
fn sql_element_cast() -> &'static str {
"timestamptz"
}
}
impl RangeElement for time::Date {
fn into_range_filter_value(range: crate::Range<Self>) -> FilterValue {
FilterValue::RangeDate(range)
}
fn sql_element_cast() -> &'static str {
"date"
}
}