use crate::jsonb::Jsonb;
use crate::model::Model;
use crate::query::condition::{Condition, FilterValue, Leaf, LookupOp};
use crate::query::predicate::PortablePredicate;
use crate::tracked::Tracked;
use std::marker::PhantomData;
pub struct FieldRef<M: Model, V> {
column: &'static str,
_m: PhantomData<fn() -> M>,
_v: PhantomData<fn() -> V>,
}
impl<M: Model, V> Copy for FieldRef<M, V> {}
impl<M: Model, V> Clone for FieldRef<M, V> {
fn clone(&self) -> Self {
*self
}
}
impl<M: Model, V> std::fmt::Debug for FieldRef<M, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FieldRef({})", self.column)
}
}
impl<M: Model, V> FieldRef<M, V> {
pub(crate) const fn new(column: &'static str) -> Self {
Self {
column,
_m: PhantomData,
_v: PhantomData,
}
}
#[doc(hidden)]
pub fn column(self) -> &'static str {
self.column
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn as_expr(self) -> crate::expr::Expr<V> {
crate::expr::Expr::from_node(crate::expr::node::ExprNode::Field {
column: self.column,
})
}
}
#[doc(hidden)]
pub mod __macro_support {
use super::FieldRef;
use crate::ident::assert_plain_ident;
use crate::model::Model;
#[doc(hidden)]
pub fn __make_field_ref<M: Model, V>(
prefix: Option<&'static str>,
column: &'static str,
) -> FieldRef<M, V> {
assert_plain_ident(column, "field_column");
let resolved = match prefix {
Some(p) => {
assert_plain_ident(p, "field_path_prefix");
intern_composed_path(p, column)
}
None => column,
};
FieldRef::new(resolved)
}
fn intern_composed_path(prefix: &'static str, column: &'static str) -> &'static str {
use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
static INTERN: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
let set_mutex = INTERN.get_or_init(|| Mutex::new(HashSet::new()));
let candidate = format!("{prefix}.{column}");
let mut set = set_mutex.lock().expect("field-path intern mutex poisoned");
if let Some(existing) = set.get(candidate.as_str()) {
return existing;
}
let leaked: &'static str = Box::leak(candidate.into_boxed_str());
set.insert(leaked);
leaked
}
#[cfg(test)]
#[allow(clippy::manual_async_fn)]
mod tests {
use super::*;
use crate::DjogiError;
use crate::descriptor::ModelDescriptor;
use std::future::Future;
struct M;
impl crate::model::__sealed::Sealed for M {}
impl Model for M {
type Pk = crate::types::HeerId;
type Fields = ();
fn table_name() -> &'static str {
"ms"
}
fn pk_value(&self) -> &Self::Pk {
unreachable!()
}
fn descriptor() -> &'static ModelDescriptor {
unreachable!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: Self::Pk,
) -> impl Future<Output = Result<Self, DjogiError>> + Send {
async { unreachable!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl Future<Output = Result<Self, DjogiError>> + Send {
async { unreachable!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), DjogiError>> + Send + 'ctx {
async { unreachable!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), DjogiError>> + Send {
async { unreachable!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<Self, DjogiError>> + Send + 'ctx {
async { unreachable!() }
}
}
fn try_make(column: &'static str) -> std::thread::Result<FieldRef<M, String>> {
std::panic::catch_unwind(|| __make_field_ref::<M, String>(None, column))
}
fn try_make_with_prefix(
prefix: &'static str,
column: &'static str,
) -> std::thread::Result<FieldRef<M, String>> {
std::panic::catch_unwind(|| __make_field_ref::<M, String>(Some(prefix), column))
}
#[test]
fn accepts_plain_column_name() {
assert!(try_make("title").is_ok());
assert!(try_make("view_count").is_ok());
}
#[test]
fn rejects_leading_digit() {
assert!(try_make("1col").is_err());
}
#[test]
fn rejects_reserved_keyword() {
assert!(try_make("select").is_err());
}
#[test]
fn rejects_sql_metacharacter_payload() {
assert!(try_make("col) OR 1=1 --").is_err());
}
#[test]
fn prefix_composes_dot_qualified_path() {
let r = try_make_with_prefix("department", "name");
assert!(r.is_ok());
assert_eq!(r.unwrap().column(), "department.name");
}
#[test]
fn prefix_validates_prefix_segment() {
assert!(try_make_with_prefix("select", "name").is_err());
}
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct DjogiFieldProvenance {
_seal: (),
}
#[doc(hidden)]
impl DjogiFieldProvenance {
fn mint_provenance() -> Self {
Self { _seal: () }
}
pub(crate) fn __mirjzson_mint() -> Self {
Self::mint_provenance()
}
}
pub trait DjogiPortableOrd:
PartialOrd + postgres_types::ToSql + Clone + Send + Sync + 'static
{
}
pub trait DjogiPortableEq:
PartialEq + postgres_types::ToSql + Clone + Send + Sync + 'static
{
}
impl DjogiPortableOrd for i8 {}
impl DjogiPortableOrd for i16 {}
impl DjogiPortableOrd for i32 {}
impl DjogiPortableOrd for i64 {}
impl DjogiPortableOrd for u32 {}
impl DjogiPortableOrd for time::OffsetDateTime {}
impl DjogiPortableOrd for time::Date {}
impl DjogiPortableOrd for uuid::Uuid {}
impl DjogiPortableOrd for crate::HeerId {}
impl DjogiPortableOrd for crate::RanjId {}
impl DjogiPortableOrd for crate::HeerIdDesc {}
impl DjogiPortableOrd for crate::RanjIdDesc {}
impl DjogiPortableOrd for rust_decimal::Decimal {}
impl<V> DjogiPortableOrd for Tracked<V> where V: DjogiPortableOrd {}
impl DjogiPortableEq for String {}
impl DjogiPortableEq for i8 {}
impl DjogiPortableEq for i16 {}
impl DjogiPortableEq for i64 {}
impl DjogiPortableEq for u32 {}
impl DjogiPortableEq for bool {}
impl DjogiPortableEq for time::OffsetDateTime {}
impl DjogiPortableEq for time::Date {}
impl DjogiPortableEq for uuid::Uuid {}
impl DjogiPortableEq for rust_decimal::Decimal {}
impl<V> DjogiPortableEq for V where
V: crate::primary_key::PrimaryKey
+ PartialEq
+ postgres_types::ToSql
+ Clone
+ Send
+ Sync
+ 'static
{
}
impl<V> DjogiPortableEq for Option<V>
where
V: DjogiPortableEq,
Option<V>: postgres_types::ToSql,
{
}
impl<V> DjogiPortableEq for Tracked<V> where V: DjogiPortableEq {}
pub trait ExplicitPgOrderable {}
impl ExplicitPgOrderable for String {}
impl ExplicitPgOrderable for &'static str {}
impl ExplicitPgOrderable for bool {}
impl ExplicitPgOrderable for i8 {}
impl ExplicitPgOrderable for i16 {}
impl ExplicitPgOrderable for i32 {}
impl ExplicitPgOrderable for i64 {}
impl ExplicitPgOrderable for u8 {}
impl ExplicitPgOrderable for u16 {}
impl ExplicitPgOrderable for u32 {}
impl ExplicitPgOrderable for u64 {}
impl ExplicitPgOrderable for f32 {}
impl ExplicitPgOrderable for f64 {}
impl ExplicitPgOrderable for time::PrimitiveDateTime {}
impl ExplicitPgOrderable for time::OffsetDateTime {}
impl ExplicitPgOrderable for time::Date {}
impl ExplicitPgOrderable for uuid::Uuid {}
impl ExplicitPgOrderable for rust_decimal::Decimal {}
impl ExplicitPgOrderable for crate::HeerId {}
impl ExplicitPgOrderable for crate::RanjId {}
impl ExplicitPgOrderable for crate::HeerIdDesc {}
impl ExplicitPgOrderable for crate::RanjIdDesc {}
impl ExplicitPgOrderable for crate::Interval {}
impl<V> ExplicitPgOrderable for Tracked<V> where V: ExplicitPgOrderable {}
#[cfg(feature = "network")]
impl ExplicitPgOrderable for std::net::IpAddr {}
#[cfg(feature = "network")]
impl ExplicitPgOrderable for crate::CidrAddr {}
#[cfg(feature = "network")]
impl ExplicitPgOrderable for crate::MacAddr {}
mod visage_scalar_seal {
pub trait Sealed {}
}
pub trait DjogiVisageScalar: visage_scalar_seal::Sealed {}
macro_rules! impl_djogi_visage_scalar {
($($t:ty),+ $(,)?) => {
$(
impl visage_scalar_seal::Sealed for $t {}
impl DjogiVisageScalar for $t {}
)+
};
}
impl_djogi_visage_scalar!(
i8,
i16,
i32,
i64,
u8,
u16,
u32,
u64,
f32,
f64,
bool,
String,
&'static str,
rust_decimal::Decimal,
time::OffsetDateTime,
time::Date,
time::PrimitiveDateTime,
uuid::Uuid,
crate::HeerId,
crate::RanjId,
crate::HeerIdDesc,
crate::RanjIdDesc,
crate::Interval,
);
#[cfg(feature = "network")]
impl_djogi_visage_scalar!(crate::CidrAddr, crate::MacAddr);
#[cfg(feature = "network")]
impl visage_scalar_seal::Sealed for std::net::IpAddr {}
#[cfg(feature = "network")]
impl DjogiVisageScalar for std::net::IpAddr {}
impl<V: DjogiVisageScalar> visage_scalar_seal::Sealed for crate::Tracked<V> {}
impl<V: DjogiVisageScalar> DjogiVisageScalar for crate::Tracked<V> {}
#[doc(hidden)]
pub trait DjogiPortableArrayEqElement: IntoArrayFilterValue + DjogiPortableEq {}
impl DjogiPortableArrayEqElement for String {}
impl DjogiPortableArrayEqElement for i16 {}
impl DjogiPortableArrayEqElement for i32 {}
impl DjogiPortableArrayEqElement for i64 {}
impl DjogiPortableArrayEqElement for bool {}
impl DjogiPortableArrayEqElement for time::OffsetDateTime {}
impl DjogiPortableArrayEqElement for time::Date {}
impl DjogiPortableArrayEqElement for uuid::Uuid {}
impl DjogiPortableArrayEqElement for rust_decimal::Decimal {}
impl DjogiPortableArrayEqElement for crate::types::HeerId {}
impl DjogiPortableArrayEqElement for crate::types::RanjId {}
impl DjogiPortableArrayEqElement for crate::types::HeerIdDesc {}
impl DjogiPortableArrayEqElement for crate::types::RanjIdDesc {}
impl<V> DjogiPortableEq for Vec<V>
where
V: DjogiPortableArrayEqElement,
Vec<V>: postgres_types::ToSql + Clone + Send + Sync + 'static,
{
}
pub struct DjogiField<M: Model, V> {
pub(crate) portable: sassi::Field<M, V>,
pub(crate) sql: FieldRef<M, V>,
pub(crate) extractor: fn(&M) -> &V,
}
pub struct DjogiPresentField<M: Model, V> {
portable: sassi::PresentField<M, V>,
sql: FieldRef<M, V>,
}
pub struct ExplicitPgPredicateField<M: Model, V> {
sql: FieldRef<M, V>,
}
impl<M: Model, V> Copy for DjogiField<M, V> {}
impl<M: Model, V> Clone for DjogiField<M, V> {
fn clone(&self) -> Self {
*self
}
}
impl<M: Model, V> std::fmt::Debug for DjogiField<M, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DjogiField")
.field("column", &self.sql.column())
.finish_non_exhaustive()
}
}
impl<M: Model, V> Copy for DjogiPresentField<M, V> {}
impl<M: Model, V> Clone for DjogiPresentField<M, V> {
fn clone(&self) -> Self {
*self
}
}
impl<M: Model, V> std::fmt::Debug for DjogiPresentField<M, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DjogiPresentField")
.field("column", &self.sql.column())
.finish_non_exhaustive()
}
}
impl<M: Model, V> Copy for ExplicitPgPredicateField<M, V> {}
impl<M: Model, V> Clone for ExplicitPgPredicateField<M, V> {
fn clone(&self) -> Self {
*self
}
}
impl<M: Model, V> std::fmt::Debug for ExplicitPgPredicateField<M, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExplicitPgPredicateField")
.field("column", &self.sql.column())
.finish_non_exhaustive()
}
}
impl<M: Model, V> DjogiField<M, V> {
#[doc(hidden)]
pub(crate) fn __portable_field(self) -> sassi::Field<M, V> {
self.portable
}
#[doc(hidden)]
pub(crate) fn __sql_field(self) -> FieldRef<M, V> {
self.sql
}
#[must_use = "ExplicitPgPredicateField is lazy — drop it and the predicate is omitted"]
pub fn explicit_pg_predicate(self) -> ExplicitPgPredicateField<M, V> {
ExplicitPgPredicateField { sql: self.sql }
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn as_expr(self) -> crate::expr::Expr<V> {
self.sql.as_expr()
}
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn asc(self) -> crate::query::order::OrderExpr {
self.sql.asc()
}
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn desc(self) -> crate::query::order::OrderExpr {
self.sql.desc()
}
#[doc(hidden)]
pub fn column(self) -> &'static str {
self.sql.column()
}
}
impl<M: Model, V: IntoFilterValue> DjogiField<M, V> {
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn set(self, value: V) -> crate::query::update::UpdateAssignment {
self.sql.set(value)
}
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn set_expr(self, expr: crate::expr::Expr<V>) -> crate::query::update::UpdateAssignment {
self.sql.set_expr(expr)
}
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn set_field<T>(self, other: T) -> crate::query::update::UpdateAssignment
where
T: IntoSqlField<M, V>,
{
self.sql.set_field(other.into_sql_field())
}
}
impl<M: Model, V> DjogiField<M, V>
where
V: IntoFilterValue + crate::expr::arithmetic::Numeric + Into<crate::expr::Expr<V>>,
{
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn increment(self, amount: V) -> crate::query::update::UpdateAssignment {
self.sql.increment(amount)
}
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn decrement(self, amount: V) -> crate::query::update::UpdateAssignment {
self.sql.decrement(amount)
}
}
impl<M: Model, V> DjogiField<M, V> {
#[must_use = "column mappings are lazy — drop one and the INSERT silently omits the column"]
pub fn copy_from<S: Model>(
self,
source: crate::query::insert_select::InsertSelectSource<S, V>,
) -> crate::query::insert_select::InsertSelectColumn<S, M> {
self.sql.copy_from(source)
}
#[must_use = "InsertSelectSource is lazy — drop one and the source projection is silently omitted"]
pub fn as_insert_source(self) -> crate::query::insert_select::InsertSelectSource<M, V> {
self.sql.as_insert_source()
}
#[must_use = "an ExcludedRef is lazy — use it in a DO UPDATE SET assignment or a condition"]
pub fn excluded(self) -> crate::query::insert_select::ExcludedRef<M, V> {
self.sql.excluded()
}
#[must_use = "a ConflictExpr is lazy — use it in a DO UPDATE SET assignment"]
pub fn as_conflict_expr(self) -> crate::query::insert_select::ConflictExpr<M, V> {
self.sql.as_conflict_expr()
}
#[must_use = "a ConflictExpr is lazy — use it in a DO UPDATE SET assignment"]
pub fn into_conflict_expr(self) -> crate::query::insert_select::ConflictExpr<M, V> {
use crate::query::insert_select::IntoConflictExpr;
self.sql.into_conflict_expr()
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_set<S: Model>(
self,
value: crate::query::insert_select::ExcludedRef<M, V>,
) -> crate::query::insert_select::ConflictUpdate<S, M> {
self.sql.conflict_set(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_set_expr<S: Model, E>(
self,
value: E,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_set_expr(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_set_value<S: Model>(
self,
value: V,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_set_value(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_excluded<S: Model>(self) -> crate::query::insert_select::ConflictUpdate<S, M> {
self.sql.conflict_excluded()
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_add<S: Model>(
self,
value: V,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
V: crate::expr::arithmetic::Numeric + Into<crate::expr::Expr<V>>,
{
self.sql.conflict_add(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_sub<S: Model>(
self,
value: V,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
V: crate::expr::arithmetic::Numeric + Into<crate::expr::Expr<V>>,
{
self.sql.conflict_sub(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_mul<S: Model>(
self,
value: V,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
V: crate::expr::arithmetic::Numeric + Into<crate::expr::Expr<V>>,
{
self.sql.conflict_mul(value)
}
#[must_use = "a ConflictUpdate is lazy — drop one and DO UPDATE SET silently omits the column"]
pub fn conflict_div<S: Model>(
self,
value: V,
) -> crate::query::insert_select::ConflictUpdate<S, M>
where
V: crate::expr::arithmetic::Numeric + Into<crate::expr::Expr<V>>,
{
self.sql.conflict_div(value)
}
pub fn conflict_eq<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_eq(rhs)
}
pub fn conflict_eq_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_eq_value(value)
}
pub fn conflict_neq<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_neq(rhs)
}
pub fn conflict_neq_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_neq_value(value)
}
pub fn conflict_gt<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_gt(rhs)
}
pub fn conflict_gt_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_gt_value(value)
}
pub fn conflict_gte<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_gte(rhs)
}
pub fn conflict_gte_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_gte_value(value)
}
pub fn conflict_lt<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_lt(rhs)
}
pub fn conflict_lt_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_lt_value(value)
}
pub fn conflict_lte<E>(self, rhs: E) -> crate::query::insert_select::ConflictCondition<M>
where
E: crate::query::insert_select::IntoConflictExpr<M, V>,
{
self.sql.conflict_lte(rhs)
}
pub fn conflict_lte_value(self, value: V) -> crate::query::insert_select::ConflictCondition<M>
where
V: Into<crate::expr::Expr<V>>,
{
self.sql.conflict_lte_value(value)
}
}
impl<M: Model> DjogiField<M, bool> {
#[must_use = "a ConflictCondition is inert until placed in an OnConflictClause"]
pub fn conflict_is_true(self) -> crate::query::insert_select::ConflictCondition<M> {
self.sql.conflict_is_true()
}
#[must_use = "a ConflictCondition is inert until placed in an OnConflictClause"]
pub fn conflict_is_false(self) -> crate::query::insert_select::ConflictCondition<M> {
self.sql.conflict_is_false()
}
}
impl<M: Model, V> DjogiField<M, Option<V>> {
#[must_use = "a ConflictCondition is inert until placed in an OnConflictClause"]
pub fn conflict_is_null(self) -> crate::query::insert_select::ConflictCondition<M> {
self.sql.conflict_is_null()
}
#[must_use = "a ConflictCondition is inert until placed in an OnConflictClause"]
pub fn conflict_is_not_null(self) -> crate::query::insert_select::ConflictCondition<M> {
self.sql.conflict_is_not_null()
}
#[must_use = "a ConflictExpr is lazy — use it in a DO UPDATE SET assignment"]
pub fn conflict_coalesce_excluded(
self,
) -> crate::query::insert_select::ConflictExpr<M, Option<V>> {
self.sql.conflict_coalesce_excluded()
}
}
impl<M: Model, V> DjogiField<M, V> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn count(self) -> crate::expr::AggregateExpr<i64> {
self.sql.count()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn count_star(self) -> crate::expr::AggregateExpr<i64> {
self.sql.count_star()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn array_agg(self) -> crate::expr::AggregateExpr<Vec<V>> {
self.sql.array_agg()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn json_agg(self) -> crate::expr::AggregateExpr<serde_json::Value> {
self.sql.json_agg()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn json_object_agg<V2>(
self,
value: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<serde_json::Value> {
self.sql.json_object_agg(value.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn jsonb_object_agg<V2>(
self,
value: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<serde_json::Value> {
self.sql.jsonb_object_agg(value.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn grouping(self) -> crate::expr::AggregateExpr<i32, crate::expr::aggregate::MetadataAgg> {
self.sql.grouping()
}
}
impl<M: Model, V: crate::expr::arithmetic::Numeric> DjogiField<M, V> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn sum(self) -> crate::expr::AggregateExpr<V> {
self.sql.sum()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn avg(self) -> crate::expr::AggregateExpr<f64> {
self.sql.avg()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn stddev_pop(self) -> crate::expr::AggregateExpr<f64> {
self.sql.stddev_pop()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn stddev_samp(self) -> crate::expr::AggregateExpr<f64> {
self.sql.stddev_samp()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn stddev(self) -> crate::expr::AggregateExpr<f64> {
self.sql.stddev()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn variance(self) -> crate::expr::AggregateExpr<f64> {
self.sql.variance()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn var_pop(self) -> crate::expr::AggregateExpr<f64> {
self.sql.var_pop()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn var_samp(self) -> crate::expr::AggregateExpr<f64> {
self.sql.var_samp()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn covar_pop<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.covar_pop(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn covar_samp<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.covar_samp(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn corr<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.corr(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_avgx<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_avgx(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_avgy<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_avgy(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_count<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<i64> {
self.sql.regr_count(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_intercept<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_intercept(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_r2<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_r2(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_slope<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_slope(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_sxx<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_sxx(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_sxy<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_sxy(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn regr_syy<V2: crate::expr::arithmetic::Numeric>(
self,
x: DjogiField<M, V2>,
) -> crate::expr::AggregateExpr<f64> {
self.sql.regr_syy(x.sql)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn percentile_cont(
self,
p: f64,
) -> crate::expr::AggregateExpr<f64, crate::expr::aggregate::OrderedSetAgg> {
self.sql.percentile_cont(p)
}
}
impl<M: Model, V: IntoFilterValue> DjogiField<M, V> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn min(self) -> crate::expr::AggregateExpr<V> {
self.sql.min()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn max(self) -> crate::expr::AggregateExpr<V> {
self.sql.max()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn percentile_disc(
self,
p: f64,
) -> crate::expr::AggregateExpr<V, crate::expr::aggregate::OrderedSetAgg> {
self.sql.percentile_disc(p)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn mode(self) -> crate::expr::AggregateExpr<V, crate::expr::aggregate::OrderedSetAgg> {
self.sql.mode()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn rank_of(
self,
value: V,
) -> crate::expr::AggregateExpr<i64, crate::expr::aggregate::HypotheticalSetAgg> {
self.sql.rank_of(value)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn dense_rank_of(
self,
value: V,
) -> crate::expr::AggregateExpr<i64, crate::expr::aggregate::HypotheticalSetAgg> {
self.sql.dense_rank_of(value)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn percent_rank_of(
self,
value: V,
) -> crate::expr::AggregateExpr<f64, crate::expr::aggregate::HypotheticalSetAgg> {
self.sql.percent_rank_of(value)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cume_dist_of(
self,
value: V,
) -> crate::expr::AggregateExpr<f64, crate::expr::aggregate::HypotheticalSetAgg> {
self.sql.cume_dist_of(value)
}
}
impl<M: Model, V: crate::expr::aggregate::IntegerColumn> DjogiField<M, V> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn bit_and(self) -> crate::expr::AggregateExpr<V> {
self.sql.bit_and()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn bit_or(self) -> crate::expr::AggregateExpr<V> {
self.sql.bit_or()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn bit_xor(self) -> crate::expr::AggregateExpr<V> {
self.sql.bit_xor()
}
}
impl<M: Model, V> DjogiField<M, V>
where
V: DjogiPortableEq,
{
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.eq(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn neq<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.neq(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_<I, P>(self, values: I) -> PortablePredicate<M>
where
I: IntoIterator<Item = P>,
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.in_(
values
.into_iter()
.map(P::into_portable_field_value)
.collect(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in<I, P>(self, values: I) -> PortablePredicate<M>
where
I: IntoIterator<Item = P>,
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.not_in(
values
.into_iter()
.map(P::into_portable_field_value)
.collect(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
}
impl<M: Model> DjogiField<M, crate::Interval> {
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn increment(self, amount: crate::Interval) -> crate::query::update::UpdateAssignment {
self.sql.increment(amount)
}
#[must_use = "assignments are lazy — drop one and the SET clause is silently omitted"]
pub fn decrement(self, amount: crate::Interval) -> crate::query::update::UpdateAssignment {
self.sql.decrement(amount)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::Interval) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::Interval) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
self.sql.not_in_list(values)
}
}
impl<M: Model> DjogiField<M, Option<crate::Interval>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::Interval) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::Interval) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, std::net::IpAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: std::net::IpAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: std::net::IpAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, Option<std::net::IpAddr>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: std::net::IpAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: std::net::IpAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, crate::CidrAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::CidrAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::CidrAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, Option<crate::CidrAddr>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::CidrAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::CidrAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, crate::MacAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::MacAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::MacAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiField<M, Option<crate::MacAddr>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::MacAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::MacAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
self.sql.not_in_list(values)
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiPresentField<M, std::net::IpAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: std::net::IpAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: std::net::IpAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = std::net::IpAddr>,
{
let mut values = values.into_iter().peekable();
if values.peek().is_none() {
self.sql.is_not_null()
} else {
self.sql.not_in_list(values)
}
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiPresentField<M, crate::CidrAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::CidrAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::CidrAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::CidrAddr>,
{
let mut values = values.into_iter().peekable();
if values.peek().is_none() {
self.sql.is_not_null()
} else {
self.sql.not_in_list(values)
}
}
}
#[cfg(feature = "network")]
impl<M: Model> DjogiPresentField<M, crate::MacAddr> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::MacAddr) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::MacAddr) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::MacAddr>,
{
let mut values = values.into_iter().peekable();
if values.peek().is_none() {
self.sql.is_not_null()
} else {
self.sql.not_in_list(values)
}
}
}
impl<M: Model> DjogiField<M, Vec<u8>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: Vec<u8>) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: Vec<u8>) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
self.sql.not_in_list(values)
}
}
impl<M: Model> DjogiField<M, Option<Vec<u8>>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: Vec<u8>) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: Vec<u8>) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
self.sql.not_in_list(values)
}
}
impl<M: Model> DjogiPresentField<M, Vec<u8>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: Vec<u8>) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: Vec<u8>) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = Vec<u8>>,
{
let mut values = values.into_iter().peekable();
if values.peek().is_none() {
self.sql.is_not_null()
} else {
self.sql.not_in_list(values)
}
}
}
impl<M: Model, V> DjogiField<M, V>
where
V: DjogiPortableOrd,
{
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.gt(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.gte(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.lt(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.lte(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn between<P>(self, low: P, high: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<V>,
{
let inner = self.portable.between(
low.into_portable_field_value(),
high.into_portable_field_value(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
}
impl<M: Model, V: DjogiVisageScalar> DjogiField<M, V> {
fn quantified_visage<__V>(
self,
op: crate::expr::node::QuantifiedCmpOp,
quantifier: crate::expr::node::SubqueryQuantifier,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::QuantifiedSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
op,
quantifier,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
negated: false,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
negated: true,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Eq,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
}
impl<M: Model, U: Send + Sync + 'static> DjogiField<M, Option<U>> {
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn is_null(self) -> PortablePredicate<M> {
let inner = self.portable.is_null();
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn is_not_null(self) -> PortablePredicate<M> {
let inner = self.portable.is_not_null();
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
pub fn some(self) -> DjogiPresentField<M, U> {
DjogiPresentField {
portable: self.portable.some(),
sql: FieldRef::<M, U>::new(self.sql.column()),
}
}
}
impl<M: Model> DjogiField<M, String> {
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn contains(self, needle: &str) -> PortablePredicate<M> {
let inner = self.portable.icontains(needle);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn icontains(self, needle: &str) -> PortablePredicate<M> {
self.contains(needle)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn starts_with(self, prefix: &str) -> PortablePredicate<M> {
let inner = self.portable.istarts_with(prefix);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn istarts_with(self, prefix: &str) -> PortablePredicate<M> {
self.starts_with(prefix)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn ends_with(self, suffix: &str) -> PortablePredicate<M> {
let inner = self.portable.iends_with(suffix);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn iends_with(self, suffix: &str) -> PortablePredicate<M> {
self.ends_with(suffix)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn contains_case_sensitive(self, needle: &str) -> PortablePredicate<M> {
let inner = self.portable.contains(needle);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn starts_with_case_sensitive(self, prefix: &str) -> PortablePredicate<M> {
let inner = self.portable.starts_with(prefix);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn ends_with_case_sensitive(self, suffix: &str) -> PortablePredicate<M> {
let inner = self.portable.ends_with(suffix);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn iexact(self, value: &str) -> PortablePredicate<M> {
let inner = self.portable.iexact(value);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn string_agg(self, sep: impl Into<String>) -> crate::expr::AggregateExpr<String> {
self.sql.string_agg(sep)
}
#[cfg(feature = "trgm")]
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn trgm_similarity(self, pattern: impl Into<String>) -> crate::expr::Expr<f64> {
self.sql.trgm_similarity(pattern)
}
}
impl<M: Model> DjogiField<M, bool> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn bool_and(self) -> crate::expr::AggregateExpr<bool> {
self.sql.bool_and()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn bool_or(self) -> crate::expr::AggregateExpr<bool> {
self.sql.bool_or()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn every(self) -> crate::expr::AggregateExpr<bool> {
self.sql.every()
}
}
impl<M: Model, U> DjogiPresentField<M, U>
where
U: DjogiPortableEq,
{
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.eq(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn neq<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.neq(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_<I, P>(self, values: I) -> PortablePredicate<M>
where
I: IntoIterator<Item = P>,
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.in_(
values
.into_iter()
.map(P::into_portable_field_value)
.collect(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in<I, P>(self, values: I) -> PortablePredicate<M>
where
I: IntoIterator<Item = P>,
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.not_in(
values
.into_iter()
.map(P::into_portable_field_value)
.collect(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
}
impl<M: Model> DjogiPresentField<M, crate::Interval> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq(self, value: crate::Interval) -> Condition {
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq(self, value: crate::Interval) -> Condition {
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in<I>(self, values: I) -> Condition
where
I: IntoIterator<Item = crate::Interval>,
{
let mut values = values.into_iter().peekable();
if values.peek().is_none() {
self.sql.is_not_null()
} else {
self.sql.not_in_list(values)
}
}
}
impl<M: Model, U> DjogiPresentField<M, U>
where
U: DjogiPortableOrd,
{
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.gt(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.gte(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.lt(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.lte(value.into_portable_field_value());
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn between<P>(self, low: P, high: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
let inner = self.portable.between(
low.into_portable_field_value(),
high.into_portable_field_value(),
);
PortablePredicate::from_djogi_field(inner, DjogiFieldProvenance::mint_provenance())
}
}
impl<M: Model, U> DjogiField<M, Option<U>>
where
U: DjogiPortableOrd,
{
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
self.some().gt(value)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
self.some().gte(value)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
self.some().lt(value)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte<P>(self, value: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
self.some().lte(value)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn between<P>(self, low: P, high: P) -> PortablePredicate<M>
where
P: IntoPortableFieldValue<U>,
{
self.some().between(low, high)
}
}
impl<M: Model, U: DjogiVisageScalar + Send + Sync + 'static> DjogiField<M, Option<U>> {
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
negated: false,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
negated: true,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
fn quantified_visage_opt<__V>(
self,
op: crate::expr::node::QuantifiedCmpOp,
quantifier: crate::expr::node::SubqueryQuantifier,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::QuantifiedSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.sql.column(),
}),
op,
quantifier,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Eq,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
}
impl<M: Model, V> ExplicitPgPredicateField<M, V> {
#[doc(hidden)]
pub(crate) fn __column(self) -> &'static str {
self.sql.column()
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.eq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.neq(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iexact<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.iexact(value)
}
}
impl<M: Model, V: ExplicitPgOrderable> ExplicitPgPredicateField<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn gt<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.gt(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn gte<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.gte(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn lt<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.lt(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn lte<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.lte(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn between<P>(self, low: P, high: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
self.sql.between(low, high)
}
}
impl<M: Model, V> ExplicitPgPredicateField<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_list<I, P>(self, values: I) -> Condition
where
I: IntoIterator<Item = P>,
P: IntoFieldFilterValue<V>,
{
self.sql.in_list(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in_list<I, P>(self, values: I) -> Condition
where
I: IntoIterator<Item = P>,
P: IntoFieldFilterValue<V>,
{
self.sql.not_in_list(values)
}
}
impl<M: Model, V> ExplicitPgPredicateField<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_null(self) -> Condition {
self.sql.is_null()
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_not_null(self) -> Condition {
self.sql.is_not_null()
}
}
impl<M: Model> ExplicitPgPredicateField<M, String> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, value: impl Into<String>) -> Condition {
self.sql.contains(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn icontains(self, value: impl Into<String>) -> Condition {
self.sql.icontains(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn starts_with(self, value: impl Into<String>) -> Condition {
self.sql.starts_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn istarts_with(self, value: impl Into<String>) -> Condition {
self.sql.istarts_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn ends_with(self, value: impl Into<String>) -> Condition {
self.sql.ends_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iends_with(self, value: impl Into<String>) -> Condition {
self.sql.iends_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn regex(self, value: impl Into<String>) -> Condition {
self.sql.regex(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iregex(self, value: impl Into<String>) -> Condition {
self.sql.iregex(value)
}
#[cfg(feature = "trgm")]
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn trgm_similar_to(self, pattern: impl Into<String>) -> Condition {
self.sql.trgm_similar_to(pattern)
}
}
impl<M: Model, V: IntoArrayFilterValue + Clone + 'static> ExplicitPgPredicateField<M, Vec<V>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, values: &[V]) -> Condition {
self.sql.contains(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contained_by(self, values: &[V]) -> Condition {
self.sql.contained_by(values)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn overlap(self, values: &[V]) -> Condition {
self.sql.overlap(values)
}
}
impl<M: Model, T> ExplicitPgPredicateField<M, crate::Range<T>>
where
T: crate::range::RangeElement + IntoFilterValue,
{
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, value: T) -> Condition {
self.sql.contains(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains_range(self, range: crate::Range<T>) -> Condition {
self.sql.contains_range(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contained_by(self, range: crate::Range<T>) -> Condition {
self.sql.contained_by(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn overlaps(self, range: crate::Range<T>) -> Condition {
self.sql.overlaps(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_left_of(self, range: crate::Range<T>) -> Condition {
self.sql.strictly_left_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_right_of(self, range: crate::Range<T>) -> Condition {
self.sql.strictly_right_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_right_of(self, range: crate::Range<T>) -> Condition {
self.sql.not_extends_right_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_left_of(self, range: crate::Range<T>) -> Condition {
self.sql.not_extends_left_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn adjacent_to(self, range: crate::Range<T>) -> Condition {
self.sql.adjacent_to(range)
}
}
impl<M: Model, T> DjogiPresentField<M, crate::Range<T>>
where
T: crate::range::RangeElement + IntoFilterValue,
{
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, value: T) -> Condition {
self.sql.contains(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains_range(self, range: crate::Range<T>) -> Condition {
self.sql.contains_range(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contained_by(self, range: crate::Range<T>) -> Condition {
self.sql.contained_by(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn overlaps(self, range: crate::Range<T>) -> Condition {
self.sql.overlaps(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_left_of(self, range: crate::Range<T>) -> Condition {
self.sql.strictly_left_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_right_of(self, range: crate::Range<T>) -> Condition {
self.sql.strictly_right_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_right_of(self, range: crate::Range<T>) -> Condition {
self.sql.not_extends_right_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_left_of(self, range: crate::Range<T>) -> Condition {
self.sql.not_extends_left_of(range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn adjacent_to(self, range: crate::Range<T>) -> Condition {
self.sql.adjacent_to(range)
}
}
impl<M: Model, V: IntoArrayFilterValue + Clone + 'static> DjogiField<M, Vec<V>> {
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn len(self) -> crate::expr::Expr<i32> {
self.sql.len()
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn is_empty(self) -> crate::expr::Expr<bool> {
self.len().eq(0)
}
}
impl<M: Model, T> ExplicitPgPredicateField<M, Jsonb<T>> {
#[must_use = "JsonbPathRef is lazy — dropping one silently omits the filter"]
pub fn path<V>(self, dotted: &'static str) -> crate::jsonb::JsonbPathRef<M, V> {
self.sql.path(dotted)
}
#[must_use = "typed path handles are lazy — dropping one silently omits the filter"]
pub fn typed(self) -> T::Path<M>
where
T: crate::jsonb::JsonbSchema,
{
self.sql.typed()
}
}
impl<M: Model, T> ExplicitPgPredicateField<M, Option<Jsonb<T>>> {
#[must_use = "JsonbPathRef is lazy — dropping one silently omits the filter"]
pub fn path<V>(self, dotted: &'static str) -> crate::jsonb::JsonbPathRef<M, V> {
self.sql.path(dotted)
}
#[must_use = "typed path handles are lazy — dropping one silently omits the filter"]
pub fn typed(self) -> T::Path<M>
where
T: crate::jsonb::JsonbSchema,
{
self.sql.typed()
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> ExplicitPgPredicateField<M, crate::geo::GeoPoint> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within_km(self, center: crate::geo::GeoPoint, km: f64) -> Condition {
self.sql.within_km(center, km)
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn distance_to(self, center: &crate::geo::GeoPoint) -> crate::expr::Expr<f64> {
self.sql.distance_to(center)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> DjogiField<M, crate::geo::GeoPoint> {
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn order_by_distance(self, center: crate::geo::GeoPoint) -> crate::query::order::OrderExpr {
self.sql.order_by_distance(center)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn make_line(self) -> crate::expr::AggregateExpr<crate::geo::LineString> {
self.sql.make_line()
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> ExplicitPgPredicateField<M, Option<crate::geo::GeoPoint>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within_km(self, center: crate::geo::GeoPoint, km: f64) -> Condition {
self.sql.within_km(center, km)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> DjogiField<M, Option<crate::geo::GeoPoint>> {
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn order_by_distance(self, center: crate::geo::GeoPoint) -> crate::query::order::OrderExpr {
self.sql.order_by_distance(center)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model, G: crate::geo::GeographyValue> ExplicitPgPredicateField<M, G> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains<O: crate::geo::GeographyValue>(self, other: &O) -> Condition {
self.sql.contains(other)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn intersects<O: crate::geo::GeographyValue>(self, other: &O) -> Condition {
self.sql.intersects(other)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn touches<O: crate::geo::GeographyValue>(self, other: &O) -> Condition {
self.sql.touches(other)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within<O: crate::geo::GeographyValue>(self, other: &O) -> Condition {
self.sql.within(other)
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn bounded_by(
self,
min_lat: f64,
min_lon: f64,
max_lat: f64,
max_lon: f64,
) -> crate::expr::Expr<bool> {
self.sql.bounded_by(min_lat, min_lon, max_lat, max_lon)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model, G: crate::geo::GeographyValue> DjogiField<M, G> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn convex_hull(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
self.sql.convex_hull()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn centroid(self) -> crate::expr::AggregateExpr<crate::geo::GeoPoint> {
self.sql.centroid()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn collect(self) -> crate::expr::AggregateExpr<crate::geo::MultiPoint> {
self.sql.collect()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn extent(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
self.sql.extent()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn extent_3d(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
self.sql.extent_3d()
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> DjogiField<M, crate::geo::Polygon> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.union()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn polygon_agg(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.polygon_agg()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_intersecting(self) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
self.sql.cluster_intersecting()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_within(
self,
distance: f64,
) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
self.sql.cluster_within(distance)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn mem_union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.mem_union()
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> DjogiField<M, crate::geo::LineString> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn polygonize(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.polygonize()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn line_agg(self) -> crate::expr::AggregateExpr<crate::geo::MultiLineString> {
self.sql.line_agg()
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> DjogiField<M, crate::geo::MultiPolygon> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.union()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn mem_union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
self.sql.mem_union()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_intersecting(self) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
self.sql.cluster_intersecting()
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_within(
self,
distance: f64,
) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
self.sql.cluster_within(distance)
}
}
mod sql_field_seal {
pub trait Sealed {}
}
pub trait IntoSqlField<M: Model, V>: sql_field_seal::Sealed {
fn into_sql_field(self) -> FieldRef<M, V>;
}
impl<M: Model, V> sql_field_seal::Sealed for FieldRef<M, V> {}
impl<M: Model, V> IntoSqlField<M, V> for FieldRef<M, V> {
fn into_sql_field(self) -> FieldRef<M, V> {
self
}
}
impl<M: Model, V> sql_field_seal::Sealed for DjogiField<M, V> {}
impl<M: Model, V> IntoSqlField<M, V> for DjogiField<M, V> {
fn into_sql_field(self) -> FieldRef<M, V> {
self.__sql_field()
}
}
#[doc(hidden)]
pub mod djogi_field_macro_support {
use super::{__macro_support::__make_field_ref, DjogiField, FieldRef};
use crate::model::Model;
#[doc(hidden)]
pub fn __make_djogi_field<M, V>(column: &'static str, extract: fn(&M) -> &V) -> DjogiField<M, V>
where
M: Model,
{
let sql: FieldRef<M, V> = __make_field_ref::<M, V>(None, column);
let portable = ::sassi::Field::<M, V>::new(column, extract);
DjogiField {
portable,
sql,
extractor: extract,
}
}
}
pub trait IntoFilterValue {
fn into_filter_value(self) -> FilterValue;
fn jsonb_sql_cast() -> Option<crate::jsonb::JsonbSqlCast>
where
Self: Sized,
{
crate::jsonb::path::jsonb_sql_cast_for_type(std::any::type_name::<Self>())
}
}
pub trait IntoFieldFilterValue<FieldValue> {
fn into_field_filter_value(self) -> FilterValue;
}
impl<V> IntoFieldFilterValue<V> for V
where
V: IntoFilterValue,
{
fn into_field_filter_value(self) -> FilterValue {
self.into_filter_value()
}
}
impl IntoFieldFilterValue<String> for &str {
fn into_field_filter_value(self) -> FilterValue {
FilterValue::String(self.to_owned())
}
}
impl<V> IntoFieldFilterValue<Option<V>> for V
where
V: IntoFilterValue,
{
fn into_field_filter_value(self) -> FilterValue {
self.into_filter_value()
}
}
impl IntoFieldFilterValue<Option<String>> for &str {
fn into_field_filter_value(self) -> FilterValue {
FilterValue::String(self.to_owned())
}
}
impl<V> IntoFieldFilterValue<Tracked<V>> for V
where
V: IntoFilterValue,
{
fn into_field_filter_value(self) -> FilterValue {
self.into_filter_value()
}
}
impl IntoFieldFilterValue<Tracked<String>> for &str {
fn into_field_filter_value(self) -> FilterValue {
FilterValue::String(self.to_owned())
}
}
pub trait IntoPortableFieldValue<FieldValue> {
fn into_portable_field_value(self) -> FieldValue;
}
impl<V> IntoPortableFieldValue<V> for V {
fn into_portable_field_value(self) -> V {
self
}
}
impl IntoPortableFieldValue<String> for &str {
fn into_portable_field_value(self) -> String {
self.to_owned()
}
}
impl<V> IntoPortableFieldValue<Option<V>> for V {
fn into_portable_field_value(self) -> Option<V> {
Some(self)
}
}
impl IntoPortableFieldValue<Option<String>> for &str {
fn into_portable_field_value(self) -> Option<String> {
Some(self.to_owned())
}
}
impl<V> IntoPortableFieldValue<Tracked<V>> for V {
fn into_portable_field_value(self) -> Tracked<V> {
Tracked::new(self)
}
}
impl IntoPortableFieldValue<Tracked<String>> for &str {
fn into_portable_field_value(self) -> Tracked<String> {
Tracked::new(self.to_owned())
}
}
impl IntoFilterValue for String {
fn into_filter_value(self) -> FilterValue {
FilterValue::String(self)
}
}
impl IntoFilterValue for &str {
fn into_filter_value(self) -> FilterValue {
FilterValue::String(self.to_owned())
}
}
impl IntoFilterValue for i16 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I16(self)
}
}
impl IntoFilterValue for i32 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I32(self)
}
}
impl IntoFilterValue for i64 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I64(self)
}
}
impl IntoFilterValue for i8 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I16(i16::from(self))
}
}
impl IntoFilterValue for u8 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I16(i16::from(self))
}
}
impl IntoFilterValue for u16 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I32(i32::from(self))
}
}
impl IntoFilterValue for u32 {
fn into_filter_value(self) -> FilterValue {
FilterValue::I64(i64::from(self))
}
}
impl IntoFilterValue for u64 {
fn into_filter_value(self) -> FilterValue {
FilterValue::Decimal(rust_decimal::Decimal::from(self))
}
}
impl IntoFilterValue for f32 {
fn into_filter_value(self) -> FilterValue {
FilterValue::F32(self)
}
}
impl IntoFilterValue for f64 {
fn into_filter_value(self) -> FilterValue {
FilterValue::F64(self)
}
}
impl IntoFilterValue for bool {
fn into_filter_value(self) -> FilterValue {
FilterValue::Bool(self)
}
}
impl IntoFilterValue for time::PrimitiveDateTime {
fn into_filter_value(self) -> FilterValue {
FilterValue::Timestamp(self)
}
}
impl IntoFilterValue for time::OffsetDateTime {
fn into_filter_value(self) -> FilterValue {
FilterValue::DateTime(self)
}
}
impl IntoFilterValue for time::Date {
fn into_filter_value(self) -> FilterValue {
FilterValue::Date(self)
}
}
impl IntoFilterValue for uuid::Uuid {
fn into_filter_value(self) -> FilterValue {
FilterValue::Uuid(self)
}
}
impl IntoFilterValue for crate::HeerId {
fn into_filter_value(self) -> FilterValue {
FilterValue::HeerId(self)
}
}
impl IntoFilterValue for crate::RanjId {
fn into_filter_value(self) -> FilterValue {
FilterValue::RanjId(self)
}
}
impl IntoFilterValue for crate::HeerIdDesc {
fn into_filter_value(self) -> FilterValue {
FilterValue::HeerIdDesc(self)
}
}
impl IntoFilterValue for crate::RanjIdDesc {
fn into_filter_value(self) -> FilterValue {
FilterValue::RanjIdDesc(self)
}
}
impl IntoFilterValue for rust_decimal::Decimal {
fn into_filter_value(self) -> FilterValue {
FilterValue::Decimal(self)
}
}
impl IntoFilterValue for crate::Interval {
fn into_filter_value(self) -> FilterValue {
FilterValue::Interval(self)
}
}
impl<T> IntoFilterValue for crate::Range<T>
where
T: crate::range::RangeElement,
{
fn into_filter_value(self) -> FilterValue {
T::into_range_filter_value(self)
}
}
#[cfg(feature = "network")]
impl IntoFilterValue for std::net::IpAddr {
fn into_filter_value(self) -> FilterValue {
FilterValue::Inet(self)
}
}
#[cfg(feature = "network")]
impl IntoFilterValue for crate::CidrAddr {
fn into_filter_value(self) -> FilterValue {
FilterValue::Cidr(self)
}
}
#[cfg(feature = "network")]
impl IntoFilterValue for crate::MacAddr {
fn into_filter_value(self) -> FilterValue {
FilterValue::Macaddr(self)
}
}
impl IntoFilterValue for Vec<u8> {
fn into_filter_value(self) -> FilterValue {
FilterValue::Bytea(self)
}
}
impl<V> IntoFilterValue for Vec<V>
where
V: IntoArrayFilterValue,
{
fn into_filter_value(self) -> FilterValue {
V::into_array_filter_value(self)
}
}
impl<M: Model, V> FieldRef<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn eq<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Eq,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn neq<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Neq,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn gt<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Gt,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn gte<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Gte,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn lt<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Lt,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn lte<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Lte,
value.into_field_filter_value(),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn between<P>(self, a: P, b: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Between,
FilterValue::Pair(
Box::new(a.into_field_filter_value()),
Box::new(b.into_field_filter_value()),
),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iexact<P>(self, value: P) -> Condition
where
P: IntoFieldFilterValue<V>,
{
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IExact,
value.into_field_filter_value(),
))
}
}
impl<M: Model, V> FieldRef<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn in_list<I, P>(self, values: I) -> Condition
where
I: IntoIterator<Item = P>,
P: IntoFieldFilterValue<V>,
{
let list = FilterValue::List(
values
.into_iter()
.map(P::into_field_filter_value)
.collect::<Vec<_>>(),
);
Condition::Leaf(Leaf::new(self.column, LookupOp::In, list))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_in_list<I, P>(self, values: I) -> Condition
where
I: IntoIterator<Item = P>,
P: IntoFieldFilterValue<V>,
{
let list = FilterValue::List(
values
.into_iter()
.map(P::into_field_filter_value)
.collect::<Vec<_>>(),
);
Condition::Leaf(Leaf::new(self.column, LookupOp::NotIn, list))
}
}
impl<M: Model, V: DjogiVisageScalar> FieldRef<M, V> {
fn quantified_visage<__V>(
self,
op: crate::expr::node::QuantifiedCmpOp,
quantifier: crate::expr::node::SubqueryQuantifier,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::QuantifiedSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
op,
quantifier,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
negated: false,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
negated: true,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, V>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage(
crate::expr::node::QuantifiedCmpOp::Eq,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
}
impl<M: Model, U: DjogiVisageScalar> FieldRef<M, Option<U>> {
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
negated: false,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn not_in_visage<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::InSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
negated: true,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
fn quantified_visage_opt<__V>(
self,
op: crate::expr::node::QuantifiedCmpOp,
quantifier: crate::expr::node::SubqueryQuantifier,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
crate::expr::Expr::from_node(crate::expr::node::ExprNode::QuantifiedSubquery {
lhs: Box::new(crate::expr::node::ExprNode::Field {
column: self.column,
}),
op,
quantifier,
subquery: Box::new(subquery.__into_subquery_node()),
})
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_any<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::Any,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn gte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Gte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lt_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lt,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn lte_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Lte,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn eq_all<__V>(
self,
subquery: crate::query::visage_queryset::VisageSubquery<__V, U>,
) -> crate::expr::Expr<bool>
where
__V: crate::visage::DjogiVisage,
{
self.quantified_visage_opt(
crate::expr::node::QuantifiedCmpOp::Eq,
crate::expr::node::SubqueryQuantifier::All,
subquery,
)
}
}
impl<M: Model> FieldRef<M, String> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, value: impl Into<String>) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IContains,
FilterValue::String(value.into()),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn icontains(self, value: impl Into<String>) -> Condition {
self.contains(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn starts_with(self, value: impl Into<String>) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IStartsWith,
FilterValue::String(value.into()),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn istarts_with(self, value: impl Into<String>) -> Condition {
self.starts_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn ends_with(self, value: impl Into<String>) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IEndsWith,
FilterValue::String(value.into()),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iends_with(self, value: impl Into<String>) -> Condition {
self.ends_with(value)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn regex(self, value: impl Into<String>) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::Regex,
FilterValue::String(value.into()),
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn iregex(self, value: impl Into<String>) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IRegex,
FilterValue::String(value.into()),
))
}
}
#[cfg(feature = "trgm")]
impl<M: Model> FieldRef<M, String> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn trgm_similar_to(self, pattern: impl Into<String>) -> Condition {
Condition::Expr(crate::expr::Expr::from_node(
crate::expr::node::ExprNode::TrgmSimilarTo {
column: self.column,
pattern: pattern.into(),
},
))
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn trgm_similarity(self, pattern: impl Into<String>) -> crate::expr::Expr<f64> {
crate::expr::Expr::from_node(crate::expr::node::ExprNode::TrgmSimilarityScore {
column: self.column,
pattern: pattern.into(),
})
}
}
impl<M: Model, V> FieldRef<M, V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_null(self) -> Condition {
Condition::Leaf(Leaf::new(self.column, LookupOp::IsNull, FilterValue::Null))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_not_null(self) -> Condition {
Condition::Leaf(Leaf::new(
self.column,
LookupOp::IsNotNull,
FilterValue::Null,
))
}
}
mod array_sealed {
pub trait Sealed {}
impl Sealed for String {}
impl Sealed for i16 {}
impl Sealed for i32 {}
impl Sealed for i64 {}
impl Sealed for f32 {}
impl Sealed for f64 {}
impl Sealed for bool {}
impl Sealed for time::OffsetDateTime {}
impl Sealed for time::Date {}
impl Sealed for uuid::Uuid {}
impl Sealed for rust_decimal::Decimal {}
impl Sealed for crate::types::HeerId {}
impl Sealed for crate::types::RanjId {}
impl Sealed for crate::types::HeerIdDesc {}
impl Sealed for crate::types::RanjIdDesc {}
}
pub trait IntoArrayFilterValue: array_sealed::Sealed {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue
where
Self: Sized;
}
impl IntoArrayFilterValue for String {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayString(values)
}
}
impl IntoArrayFilterValue for i16 {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayI16(values)
}
}
impl IntoArrayFilterValue for i32 {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayI32(values)
}
}
impl IntoArrayFilterValue for i64 {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayI64(values)
}
}
impl IntoArrayFilterValue for f32 {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayF32(values)
}
}
impl IntoArrayFilterValue for f64 {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayF64(values)
}
}
impl IntoArrayFilterValue for bool {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayBool(values)
}
}
impl IntoArrayFilterValue for time::OffsetDateTime {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayDateTime(values)
}
}
impl IntoArrayFilterValue for time::Date {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayDate(values)
}
}
impl IntoArrayFilterValue for uuid::Uuid {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayUuid(values)
}
}
impl IntoArrayFilterValue for rust_decimal::Decimal {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayDecimal(values)
}
}
impl IntoArrayFilterValue for crate::types::HeerId {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayHeerId(values)
}
}
impl IntoArrayFilterValue for crate::types::RanjId {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayRanjId(values)
}
}
impl IntoArrayFilterValue for crate::types::HeerIdDesc {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayHeerIdDesc(values)
}
}
impl IntoArrayFilterValue for crate::types::RanjIdDesc {
fn into_array_filter_value(values: Vec<Self>) -> FilterValue {
FilterValue::ArrayRanjIdDesc(values)
}
}
impl<M: Model, V: IntoArrayFilterValue + Clone + 'static> FieldRef<M, Vec<V>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, values: &[V]) -> Condition {
Condition::ArrayContains(crate::array::ArrayContainsLeaf {
column: self.column,
values: V::into_array_filter_value(values.to_vec()),
})
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contained_by(self, values: &[V]) -> Condition {
Condition::ArrayContainedBy(crate::array::ArrayContainedByLeaf {
column: self.column,
values: V::into_array_filter_value(values.to_vec()),
})
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn overlap(self, values: &[V]) -> Condition {
Condition::ArrayOverlap(crate::array::ArrayOverlapLeaf {
column: self.column,
values: V::into_array_filter_value(values.to_vec()),
})
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn len(self) -> crate::expr::Expr<i32> {
crate::expr::Expr::from_node(crate::expr::node::ExprNode::ArrayLength {
column: self.column,
})
}
}
impl<M: Model, T> FieldRef<M, crate::Range<T>>
where
T: crate::range::RangeElement + IntoFilterValue,
{
fn range_predicate(self, op: crate::range::RangePredicateOp, value: FilterValue) -> Condition {
Condition::RangePredicate(crate::range::RangePredicateLeaf::new(
self.column,
op,
value,
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains(self, value: T) -> Condition {
Condition::RangePredicate(
crate::range::RangePredicateLeaf::new(
self.column,
crate::range::RangePredicateOp::Contains,
value.into_filter_value(),
)
.with_rhs_element_cast(T::sql_element_cast()),
)
}
fn range_rhs_predicate(
self,
op: crate::range::RangePredicateOp,
range: crate::Range<T>,
) -> Condition {
self.range_predicate(op, T::into_range_filter_value(range))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains_range(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::Contains, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contained_by(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::ContainedBy, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn overlaps(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::Overlaps, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_left_of(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::StrictlyLeftOf, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn strictly_right_of(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::StrictlyRightOf, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_right_of(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::NotExtendsRightOf, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn not_extends_left_of(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::NotExtendsLeftOf, range)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn adjacent_to(self, range: crate::Range<T>) -> Condition {
self.range_rhs_predicate(crate::range::RangePredicateOp::AdjacentTo, range)
}
}
impl<M: Model, T> FieldRef<M, Jsonb<T>> {
#[must_use = "JsonbPathRef is lazy — dropping one silently omits the filter"]
pub fn path<V>(self, dotted: &'static str) -> crate::jsonb::JsonbPathRef<M, V> {
crate::jsonb::JsonbPathRef::new(self.column, dotted)
}
#[must_use = "typed path handles are lazy — dropping one silently omits the filter"]
pub fn typed(self) -> T::Path<M>
where
T: crate::jsonb::JsonbSchema,
{
T::root_path::<M>(self.column)
}
}
impl<M: Model, T> FieldRef<M, Option<Jsonb<T>>> {
#[must_use = "JsonbPathRef is lazy — dropping one silently omits the filter"]
pub fn path<V>(self, dotted: &'static str) -> crate::jsonb::JsonbPathRef<M, V> {
crate::jsonb::JsonbPathRef::new(self.column, dotted)
}
#[must_use = "typed path handles are lazy — dropping one silently omits the filter"]
pub fn typed(self) -> T::Path<M>
where
T: crate::jsonb::JsonbSchema,
{
T::root_path::<M>(self.column)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, crate::geo::GeoPoint> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within_km(self, center: crate::geo::GeoPoint, km: f64) -> Condition {
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
Condition::Expr(crate::expr::Expr::from_node(ExprNode::Spatial(
SpatialExpr::Within {
field_column: self.column(),
center,
radius_meters: km * 1000.0,
},
)))
}
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn order_by_distance(self, center: crate::geo::GeoPoint) -> crate::query::order::OrderExpr {
let pk_column = M::descriptor().pk_column().unwrap_or(self.column());
crate::query::order::OrderExpr::spatial_distance_with_pk_tiebreak(
self.column(),
center,
pk_column,
)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, ::std::option::Option<crate::geo::GeoPoint>> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within_km(self, center: crate::geo::GeoPoint, km: f64) -> Condition {
let guard: FieldRef<M, ::std::option::Option<crate::geo::GeoPoint>> =
FieldRef::new(self.column);
let is_not_null = guard.is_not_null();
let inner: FieldRef<M, crate::geo::GeoPoint> = FieldRef::new(self.column);
Condition::and(is_not_null, inner.within_km(center, km))
}
#[must_use = "order expressions are inert until passed to `order_by`"]
pub fn order_by_distance(self, center: crate::geo::GeoPoint) -> crate::query::order::OrderExpr {
let inner: FieldRef<M, crate::geo::GeoPoint> = FieldRef::new(self.column);
inner.order_by_distance(center)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model, G: crate::geo::GeographyValue> FieldRef<M, G> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn contains<O: crate::geo::GeographyValue>(
self,
other: &O,
) -> crate::query::condition::Condition {
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
crate::query::condition::Condition::Expr(crate::expr::Expr::from_node(ExprNode::Spatial(
SpatialExpr::Contains {
field_column: self.column(),
other_ewkb: other.to_ewkb_bytes(),
},
)))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn intersects<O: crate::geo::GeographyValue>(
self,
other: &O,
) -> crate::query::condition::Condition {
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
crate::query::condition::Condition::Expr(crate::expr::Expr::from_node(ExprNode::Spatial(
SpatialExpr::Intersects {
field_column: self.column(),
other_ewkb: other.to_ewkb_bytes(),
},
)))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn touches<O: crate::geo::GeographyValue>(
self,
other: &O,
) -> crate::query::condition::Condition {
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
crate::query::condition::Condition::Expr(crate::expr::Expr::from_node(ExprNode::Spatial(
SpatialExpr::Touches {
field_column: self.column(),
other_ewkb: other.to_ewkb_bytes(),
},
)))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn within<O: crate::geo::GeographyValue>(
self,
other: &O,
) -> crate::query::condition::Condition {
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
crate::query::condition::Condition::Expr(crate::expr::Expr::from_node(ExprNode::Spatial(
SpatialExpr::WithinShape {
field_column: self.column(),
other_ewkb: other.to_ewkb_bytes(),
},
)))
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model, G: crate::geo::GeographyValue> FieldRef<M, G> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn bounded_by(
self,
min_lat: f64,
min_lon: f64,
max_lat: f64,
max_lon: f64,
) -> crate::expr::Expr<bool> {
crate::expr::Expr::from_node(crate::expr::node::ExprNode::Spatial(
crate::expr::spatial::SpatialExpr::BoundedBy {
field_column: self.column(),
min_lat,
min_lon,
max_lat,
max_lon,
},
))
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model, G: crate::geo::GeographyValue> FieldRef<M, G> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn convex_hull(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialConvexHull,
self.column(),
None,
)
}
#[cfg(feature = "spatial")]
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn centroid(self) -> crate::expr::AggregateExpr<crate::geo::GeoPoint> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialCentroid,
self.column(),
None,
)
}
#[cfg(feature = "spatial")]
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn collect(self) -> crate::expr::AggregateExpr<crate::geo::MultiPoint> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialCollect,
self.column(),
None,
)
}
#[cfg(feature = "spatial")]
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn extent(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialExtent,
self.column(),
None,
)
}
#[cfg(feature = "spatial")]
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn extent_3d(self) -> crate::expr::AggregateExpr<crate::geo::Polygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialExtent3D,
self.column(),
None,
)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, crate::geo::Polygon> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialUnion,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn polygon_agg(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialPolygonAgg,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_intersecting(self) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialClusterIntersecting,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_within(
self,
distance: f64,
) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialClusterWithin(distance),
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn mem_union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialMemUnion,
self.column(),
None,
)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, crate::geo::LineString> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn polygonize(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialPolygonize,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn line_agg(self) -> crate::expr::AggregateExpr<crate::geo::MultiLineString> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialLineAgg,
self.column(),
None,
)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, crate::geo::MultiPolygon> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialUnion,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn mem_union(self) -> crate::expr::AggregateExpr<crate::geo::MultiPolygon> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialMemUnion,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_intersecting(self) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialClusterIntersecting,
self.column(),
None,
)
}
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn cluster_within(
self,
distance: f64,
) -> crate::expr::AggregateExpr<Vec<crate::geo::MultiPolygon>> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialClusterWithin(distance),
self.column(),
None,
)
}
}
#[cfg(feature = "spatial")]
impl<M: crate::model::Model> FieldRef<M, crate::geo::GeoPoint> {
#[must_use = "aggregates are lazy — dropping one silently omits the column"]
pub fn make_line(self) -> crate::expr::AggregateExpr<crate::geo::LineString> {
crate::expr::AggregateExpr::unary_agg(
crate::expr::node::AggOp::SpatialMakeLine,
self.column(),
None,
)
}
#[must_use = "expressions are lazy — dropping one silently omits the predicate"]
pub fn distance_to(self, center: &crate::geo::GeoPoint) -> crate::expr::Expr<f64> {
crate::expr::Expr::from_node(crate::expr::node::ExprNode::Spatial(
crate::expr::spatial::SpatialExpr::Distance {
field_column: self.column(),
center: *center,
},
))
}
}
impl Condition {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn and_with(self, other: Condition) -> Condition {
Condition::and(self, other)
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn or_with(self, other: Condition) -> Condition {
Condition::or(self, other)
}
}
pub struct OptionalRelationRef<V> {
fk_column: &'static str,
peer_fields: V,
}
impl<V> OptionalRelationRef<V> {
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn map_filter<F>(self, f: F) -> Condition
where
F: FnOnce(V) -> Condition,
{
let inner = f(self.peer_fields);
let not_null = Condition::Leaf(Leaf::new(
self.fk_column,
LookupOp::IsNotNull,
FilterValue::Null,
));
Condition::and(not_null, inner)
}
#[must_use = "predicates are lazy — dropping one silently omits the filter"]
pub fn map_predicate<M, F, P>(self, f: F) -> crate::query::Q<M>
where
M: Model,
F: FnOnce(V) -> P,
P: crate::query::IntoQ<M>,
{
let inner = f(self.peer_fields).into_q();
let not_null = crate::query::Q::Condition(Condition::Leaf(Leaf::new(
self.fk_column,
LookupOp::IsNotNull,
FilterValue::Null,
)));
not_null & inner
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_none(self) -> Condition {
Condition::Leaf(Leaf::new(
self.fk_column,
LookupOp::IsNull,
FilterValue::Null,
))
}
#[must_use = "conditions are lazy — dropping one silently omits the filter"]
pub fn is_some(self) -> Condition {
Condition::Leaf(Leaf::new(
self.fk_column,
LookupOp::IsNotNull,
FilterValue::Null,
))
}
}
impl<V: Copy> Copy for OptionalRelationRef<V> {}
impl<V: Clone> Clone for OptionalRelationRef<V> {
fn clone(&self) -> Self {
Self {
fk_column: self.fk_column,
peer_fields: self.peer_fields.clone(),
}
}
}
impl<V: std::fmt::Debug> std::fmt::Debug for OptionalRelationRef<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OptionalRelationRef")
.field("fk_column", &self.fk_column)
.field("peer_fields", &self.peer_fields)
.finish()
}
}
#[doc(hidden)]
pub mod optional_relation_support {
use super::OptionalRelationRef;
use crate::ident::assert_plain_ident;
#[doc(hidden)]
pub fn __make_optional_relation_ref<V>(
fk_column: &'static str,
peer_fields: V,
) -> OptionalRelationRef<V> {
assert_plain_ident(fk_column, "optional_fk_column");
OptionalRelationRef {
fk_column,
peer_fields,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::__private::pg::SqlAccumulator;
use crate::expr::node::ExprNode;
use crate::query::condition::{Condition, LookupOp};
struct Fake;
impl crate::model::__sealed::Sealed for Fake {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for Fake {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fakes"
}
fn pk_value(&self) -> &i64 {
unimplemented!()
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), crate::DjogiError>> + Send + 'ctx
{
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send + 'ctx
{
async { unimplemented!() }
}
}
#[derive(Debug)]
struct FakeRow {
id: i64,
age: i64,
title: String,
maybe_age: Option<i64>,
duration: crate::Interval,
maybe_duration: Option<crate::Interval>,
span: crate::Range<i32>,
payload: Vec<u8>,
maybe_payload: Option<Vec<u8>>,
}
impl crate::model::__sealed::Sealed for FakeRow {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for FakeRow {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fake_rows"
}
fn pk_value(&self) -> &i64 {
&self.id
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), crate::DjogiError>> + Send + 'ctx
{
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<Self, crate::DjogiError>> + Send + 'ctx
{
async { unimplemented!() }
}
}
struct FakeRowPublic;
impl crate::visage_boundary::private::Sealed<FakeRow> for FakeRowPublic {}
impl crate::visage_boundary::DjogiVisageOf<FakeRow> for FakeRowPublic {}
impl crate::visage::private::Sealed for FakeRowPublic {}
impl crate::visage::DjogiVisage for FakeRowPublic {
type Model = FakeRow;
const SCOPE: &'static str = "public";
const COLUMNS: &'static [&'static str] = &["id"];
const PROJECTIONS: &'static [crate::__private::ProjectionEntry] = &[];
const PROJECTION_LIST: &'static str = "id";
}
fn test_visage_col<U>(name: &'static str) -> crate::query::VisageColumn<FakeRowPublic, U> {
crate::query::VisageColumn::__new_for_visage_column(
name,
crate::__private::visage_column_seal::TOKEN,
)
}
fn emit_expr_sql(expr: crate::expr::Expr<bool>) -> String {
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &expr.node, crate::query::SqlEmitContext::root())
.expect("emit");
acc.sql().trim().to_string()
}
#[test]
fn djogi_field_eq_returns_portable_sassi_leaf() {
let f =
djogi_field_macro_support::__make_djogi_field::<FakeRow, i64>("age", |row| &row.age);
let predicate = f.eq(42).into_inner();
match predicate {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "age");
assert_eq!(field.op(), sassi::LookupOp::Eq);
assert_eq!(field.value_as::<i64>(), Some(&42));
}
other => panic!("expected Field predicate, got {other:?}"),
}
}
#[test]
fn djogi_field_optional_eq_and_some_eq_use_planned_payloads() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
match f.eq(None).into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "maybe_age");
assert_eq!(field.op(), sassi::LookupOp::Eq);
assert_eq!(field.value_as::<Option<i64>>(), Some(&None));
}
other => panic!("expected optional Eq field predicate, got {other:?}"),
}
match f.some().eq(7).into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "maybe_age");
assert_eq!(field.op(), sassi::LookupOp::Eq);
assert_eq!(field.value_as::<i64>(), Some(&7));
}
other => panic!("expected present Eq field predicate, got {other:?}"),
}
}
#[test]
fn djogi_field_interval_equality_and_membership_are_sql_only_conditions() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, crate::Interval>(
"duration",
|row| &row.duration,
);
let one_month = crate::Interval::months_only(1);
let eq: Condition = f.eq(one_month);
let neq: Condition = f.neq(one_month);
let in_list: Condition = f.in_([one_month]);
let not_in_list: Condition = f.not_in([one_month]);
if let Condition::Leaf(leaf) = eq {
assert_eq!(leaf.column, "duration");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::Interval(v) if v == one_month));
} else {
panic!("expected Interval Eq to produce a SQL-only Condition leaf");
}
if let Condition::Leaf(leaf) = neq {
assert_eq!(leaf.op, LookupOp::Neq);
} else {
panic!("expected Interval Neq to produce a SQL-only Condition leaf");
}
if let Condition::Leaf(leaf) = in_list {
assert_eq!(leaf.op, LookupOp::In);
} else {
panic!("expected Interval IN to produce a SQL-only Condition leaf");
}
if let Condition::Leaf(leaf) = not_in_list {
assert_eq!(leaf.op, LookupOp::NotIn);
} else {
panic!("expected Interval NOT IN to produce a SQL-only Condition leaf");
}
}
#[test]
fn range_predicates_build_typed_range_condition_payloads() {
let field = FieldRef::<Fake, crate::Range<i32>>::new("span");
let probe = crate::Range::inclusive_exclusive(2_i32, 4_i32);
let contains_element = field.contains(3_i32);
if let Condition::RangePredicate(leaf) = contains_element {
assert_eq!(leaf.column(), "span");
assert_eq!(leaf.op(), crate::range::RangePredicateOp::Contains);
assert!(matches!(leaf.value(), FilterValue::I32(3)));
assert_eq!(leaf.rhs_element_cast(), Some("int4"));
} else {
panic!("expected contains(element) to produce a range predicate condition");
}
let overlaps = field.overlaps(probe);
if let Condition::RangePredicate(leaf) = overlaps {
assert_eq!(leaf.column(), "span");
assert_eq!(leaf.op(), crate::range::RangePredicateOp::Overlaps);
assert!(matches!(leaf.value(), FilterValue::RangeI32(range) if range == &probe));
assert_eq!(leaf.rhs_element_cast(), None);
} else {
panic!("expected overlaps(range) to produce a range predicate condition");
}
}
#[test]
fn explicit_pg_range_predicates_forward_from_root_field() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, crate::Range<i32>>(
"span",
|row| &row.span,
);
let probe = crate::Range::inclusive_exclusive(2_i32, 4_i32);
let adjacent = f.explicit_pg_predicate().adjacent_to(probe);
if let Condition::RangePredicate(leaf) = adjacent {
assert_eq!(leaf.column(), "span");
assert_eq!(leaf.op(), crate::range::RangePredicateOp::AdjacentTo);
assert!(matches!(leaf.value(), FilterValue::RangeI32(range) if range == &probe));
} else {
panic!("expected explicit range predicate to produce a range predicate condition");
}
}
#[test]
fn djogi_field_nullable_interval_eq_and_some_eq_are_sql_only_conditions() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<crate::Interval>>(
"maybe_duration",
|row| &row.maybe_duration,
);
let thirty_days = crate::Interval::days_only(30);
let nullable_eq: Condition = f.eq(thirty_days);
let present_eq: Condition = f.some().eq(thirty_days);
if let Condition::Leaf(leaf) = nullable_eq {
assert_eq!(leaf.column, "maybe_duration");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::Interval(v) if v == thirty_days));
} else {
panic!("expected nullable Interval Eq to produce a SQL-only Condition leaf");
}
if let Condition::Leaf(leaf) = present_eq {
assert_eq!(leaf.column, "maybe_duration");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::Interval(v) if v == thirty_days));
} else {
panic!("expected nullable Interval some().eq to produce a SQL-only Condition leaf");
}
let direct_empty_not_in: Condition = f.not_in(Vec::<crate::Interval>::new());
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_condition(&mut acc, &direct_empty_not_in, None).unwrap();
assert_eq!(
acc.sql(),
"TRUE",
"direct nullable Interval not_in([]) should keep the shared empty-list convention"
);
let present_empty_not_in: Condition = f.some().not_in(Vec::<crate::Interval>::new());
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_condition(&mut acc, &present_empty_not_in, None).unwrap();
assert_eq!(
acc.sql(),
"maybe_duration IS NOT NULL",
"present Interval not_in([]) must preserve the IS NOT NULL guard"
);
}
#[test]
fn djogi_field_bytea_eq_neq_in_not_in_are_sql_only_conditions() {
let f =
djogi_field_macro_support::__make_djogi_field::<FakeRow, Vec<u8>>("payload", |row| {
&row.payload
});
if let Condition::Leaf(leaf) = f.eq(vec![1, 2, 3]) {
assert_eq!(leaf.column, "payload");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::Bytea(ref b) if b == &[1, 2, 3]));
} else {
panic!("expected BYTEA eq Leaf");
}
if let Condition::Leaf(leaf) = f.neq(vec![9]) {
assert_eq!(leaf.op, LookupOp::Neq);
assert!(matches!(leaf.value, FilterValue::Bytea(ref b) if b == &[9]));
} else {
panic!("expected BYTEA neq Leaf");
}
if let Condition::Leaf(leaf) = f.in_([vec![1u8], vec![2u8]]) {
assert_eq!(leaf.op, LookupOp::In);
assert!(matches!(leaf.value, FilterValue::List(ref v) if v.len() == 2));
} else {
panic!("expected BYTEA in_ Leaf");
}
if let Condition::Leaf(leaf) = f.not_in([vec![1u8], vec![2u8]]) {
assert_eq!(leaf.op, LookupOp::NotIn);
assert!(matches!(leaf.value, FilterValue::List(ref v) if v.len() == 2));
} else {
panic!("expected BYTEA not_in Leaf");
}
}
#[test]
fn djogi_present_field_bytea_empty_not_in_falls_back_to_is_not_null() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<Vec<u8>>>(
"maybe_payload",
|row| &row.maybe_payload,
);
let empty: Condition = f.some().not_in(Vec::<Vec<u8>>::new());
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_condition(&mut acc, &empty, None).unwrap();
assert_eq!(
acc.sql(),
"maybe_payload IS NOT NULL",
"present BYTEA not_in([]) must preserve the IS NOT NULL guard"
);
}
#[test]
fn djogi_field_nullable_bytea_eq_is_sql_only_condition() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<Vec<u8>>>(
"maybe_payload",
|row| &row.maybe_payload,
);
if let Condition::Leaf(leaf) = f.eq(vec![7, 8]) {
assert_eq!(leaf.column, "maybe_payload");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::Bytea(ref b) if b == &[7, 8]));
} else {
panic!("expected nullable BYTEA eq Leaf");
}
}
#[test]
fn djogi_field_null_checks_stay_portable() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
match f.is_null().into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "maybe_age");
assert_eq!(field.op(), sassi::LookupOp::IsNull);
assert_eq!(field.value_as::<()>(), Some(&()));
}
other => panic!("expected IsNull field predicate, got {other:?}"),
}
}
#[test]
fn djogi_field_conflict_null_helpers_forward_to_sql_field_ref() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert!(matches!(
f.conflict_is_null().node,
ExprNode::IsNull(inner)
if matches!(*inner, ExprNode::Field { column } if column == "maybe_age")
));
assert!(matches!(
f.conflict_is_not_null().node,
ExprNode::IsNotNull(inner)
if matches!(*inner, ExprNode::Field { column } if column == "maybe_age")
));
}
#[test]
fn djogi_field_conflict_coalesce_excluded_forwards_to_sql_field_ref() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
match f.conflict_coalesce_excluded().node {
ExprNode::Coalesce(args) => {
assert_eq!(args.len(), 2);
assert!(matches!(args[0], ExprNode::Field { column } if column == "maybe_age"));
assert!(matches!(args[1], ExprNode::Excluded { column } if column == "maybe_age"));
}
other => panic!("expected Coalesce(Field, Excluded), got {other:?}"),
}
}
#[test]
fn field_ref_in_visage_emits_field_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("id").in_visage(subquery);
assert_eq!(emit_expr_sql(expr), "id IN (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_not_in_visage_emits_field_not_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("id").not_in_visage(subquery);
assert_eq!(emit_expr_sql(expr), "id NOT IN (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_gt_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").gt_any(subquery);
assert_eq!(emit_expr_sql(expr), "age > ANY (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_eq_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").eq_all(subquery);
assert_eq!(emit_expr_sql(expr), "age = ALL (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_gt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").gt_all(subquery);
assert_eq!(emit_expr_sql(expr), "age > ALL (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_gte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").gte_all(subquery);
assert_eq!(emit_expr_sql(expr), "age >= ALL (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_lt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").lt_all(subquery);
assert_eq!(emit_expr_sql(expr), "age < ALL (SELECT id FROM fake_rows)");
}
#[test]
fn field_ref_lte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, i64>::new("age").lte_all(subquery);
assert_eq!(emit_expr_sql(expr), "age <= ALL (SELECT id FROM fake_rows)");
}
#[test]
fn non_nullable_visage_blanket_domain_is_preserved() {
fn _assert_i64(
f: FieldRef<FakeRow, i64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i64>,
) {
let _ = f.in_visage(q);
}
fn _assert_string(
f: FieldRef<FakeRow, String>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, String>,
) {
let _ = f.gt_any(q);
}
fn _assert_i32(
f: FieldRef<FakeRow, i32>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i32>,
) {
let _ = f.lt_all(q);
}
fn _assert_field_ref_static_str(
f: FieldRef<FakeRow, &'static str>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, &'static str>,
) {
let _ = f.gt_any(q);
}
fn _assert_field_ref_u8(
f: FieldRef<FakeRow, u8>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u8>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_u16(
f: FieldRef<FakeRow, u16>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u16>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_u64(
f: FieldRef<FakeRow, u64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u64>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_f32(
f: FieldRef<FakeRow, f32>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, f32>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_f64(
f: FieldRef<FakeRow, f64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, f64>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_primitivedatetime(
f: FieldRef<FakeRow, time::PrimitiveDateTime>,
q: crate::query::visage_queryset::VisageSubquery<
FakeRowPublic,
time::PrimitiveDateTime,
>,
) {
let _ = f.gt_all(q);
}
fn _assert_field_ref_interval(
f: FieldRef<FakeRow, crate::Interval>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::Interval>,
) {
let _ = f.gt_all(q);
}
#[cfg(feature = "network")]
fn _assert_field_ref_ipaddr(
f: FieldRef<FakeRow, std::net::IpAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, std::net::IpAddr>,
) {
let _ = f.in_visage(q);
}
#[cfg(feature = "network")]
fn _assert_field_ref_cidraddr(
f: FieldRef<FakeRow, crate::CidrAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::CidrAddr>,
) {
let _ = f.in_visage(q);
}
#[cfg(feature = "network")]
fn _assert_field_ref_macaddr(
f: FieldRef<FakeRow, crate::MacAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::MacAddr>,
) {
let _ = f.in_visage(q);
}
let _ = (
_assert_i64,
_assert_string,
_assert_i32,
_assert_field_ref_static_str,
_assert_field_ref_u8,
_assert_field_ref_u16,
_assert_field_ref_u64,
_assert_field_ref_f32,
_assert_field_ref_f64,
_assert_field_ref_primitivedatetime,
_assert_field_ref_interval,
);
#[cfg(feature = "network")]
let _ = (
_assert_field_ref_ipaddr,
_assert_field_ref_cidraddr,
_assert_field_ref_macaddr,
);
}
#[test]
fn non_nullable_djogi_field_visage_domain_is_preserved() {
fn _assert_i8(
f: DjogiField<FakeRow, i8>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i8>,
) {
let _ = f.in_visage(q);
}
fn _assert_i16(
f: DjogiField<FakeRow, i16>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i16>,
) {
let _ = f.in_visage(q);
}
fn _assert_i32(
f: DjogiField<FakeRow, i32>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i32>,
) {
let _ = f.in_visage(q);
}
fn _assert_i64(
f: DjogiField<FakeRow, i64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, i64>,
) {
let _ = f.in_visage(q);
}
fn _assert_u8(
f: DjogiField<FakeRow, u8>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u8>,
) {
let _ = f.gt_all(q);
}
fn _assert_u16(
f: DjogiField<FakeRow, u16>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u16>,
) {
let _ = f.gt_all(q);
}
fn _assert_u32(
f: DjogiField<FakeRow, u32>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u32>,
) {
let _ = f.in_visage(q);
}
fn _assert_u64(
f: DjogiField<FakeRow, u64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, u64>,
) {
let _ = f.gt_all(q);
}
fn _assert_f32(
f: DjogiField<FakeRow, f32>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, f32>,
) {
let _ = f.gt_all(q);
}
fn _assert_f64(
f: DjogiField<FakeRow, f64>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, f64>,
) {
let _ = f.gt_all(q);
}
fn _assert_bool(
f: DjogiField<FakeRow, bool>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, bool>,
) {
let _ = f.in_visage(q);
}
fn _assert_string(
f: DjogiField<FakeRow, String>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, String>,
) {
let _ = f.gt_any(q);
}
fn _assert_static_str(
f: DjogiField<FakeRow, &'static str>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, &'static str>,
) {
let _ = f.gt_any(q);
}
fn _assert_decimal(
f: DjogiField<FakeRow, rust_decimal::Decimal>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, rust_decimal::Decimal>,
) {
let _ = f.in_visage(q);
}
fn _assert_offsetdatetime(
f: DjogiField<FakeRow, time::OffsetDateTime>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, time::OffsetDateTime>,
) {
let _ = f.gt_all(q);
}
fn _assert_date(
f: DjogiField<FakeRow, time::Date>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, time::Date>,
) {
let _ = f.gt_all(q);
}
fn _assert_primitivedatetime(
f: DjogiField<FakeRow, time::PrimitiveDateTime>,
q: crate::query::visage_queryset::VisageSubquery<
FakeRowPublic,
time::PrimitiveDateTime,
>,
) {
let _ = f.gt_all(q);
}
fn _assert_uuid(
f: DjogiField<FakeRow, uuid::Uuid>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, uuid::Uuid>,
) {
let _ = f.in_visage(q);
}
fn _assert_heerid(
f: DjogiField<FakeRow, crate::HeerId>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::HeerId>,
) {
let _ = f.in_visage(q);
}
fn _assert_ranjid(
f: DjogiField<FakeRow, crate::RanjId>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::RanjId>,
) {
let _ = f.in_visage(q);
}
fn _assert_heeriddesc(
f: DjogiField<FakeRow, crate::HeerIdDesc>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::HeerIdDesc>,
) {
let _ = f.in_visage(q);
}
fn _assert_ranjiddesc(
f: DjogiField<FakeRow, crate::RanjIdDesc>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::RanjIdDesc>,
) {
let _ = f.in_visage(q);
}
fn _assert_interval(
f: DjogiField<FakeRow, crate::Interval>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::Interval>,
) {
let _ = f.gt_all(q);
}
fn _assert_tracked(
f: DjogiField<FakeRow, crate::Tracked<i64>>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::Tracked<i64>>,
) {
let _ = f.in_visage(q);
}
#[cfg(feature = "network")]
fn _assert_ipaddr(
f: DjogiField<FakeRow, std::net::IpAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, std::net::IpAddr>,
) {
let _ = f.in_visage(q);
}
#[cfg(feature = "network")]
fn _assert_cidraddr(
f: DjogiField<FakeRow, crate::CidrAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::CidrAddr>,
) {
let _ = f.in_visage(q);
}
#[cfg(feature = "network")]
fn _assert_macaddr(
f: DjogiField<FakeRow, crate::MacAddr>,
q: crate::query::visage_queryset::VisageSubquery<FakeRowPublic, crate::MacAddr>,
) {
let _ = f.in_visage(q);
}
let _ = (
_assert_i8,
_assert_i16,
_assert_i32,
_assert_i64,
_assert_u8,
_assert_u16,
_assert_u32,
_assert_u64,
_assert_f32,
_assert_f64,
_assert_bool,
_assert_string,
_assert_static_str,
_assert_decimal,
_assert_offsetdatetime,
_assert_date,
_assert_primitivedatetime,
_assert_uuid,
_assert_heerid,
_assert_ranjid,
_assert_heeriddesc,
_assert_ranjiddesc,
_assert_interval,
_assert_tracked,
);
#[cfg(feature = "network")]
let _ = (_assert_ipaddr, _assert_cidraddr, _assert_macaddr);
}
#[test]
fn djogi_field_option_in_visage_emits_field_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
let expr = f.in_visage(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age IN (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_not_in_visage_emits_field_not_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
let expr = f.not_in_visage(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age NOT IN (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_gt_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.gt_any(subquery)),
"maybe_age > ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_gte_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.gte_any(subquery)),
"maybe_age >= ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_lt_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.lt_any(subquery)),
"maybe_age < ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_lte_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.lte_any(subquery)),
"maybe_age <= ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_gt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.gt_all(subquery)),
"maybe_age > ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_gte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.gte_all(subquery)),
"maybe_age >= ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_lt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.lt_all(subquery)),
"maybe_age < ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_option_lte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
assert_eq!(
emit_expr_sql(f.lte_all(subquery)),
"maybe_age <= ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_in_visage_emits_field_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").in_visage(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age IN (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_not_in_visage_emits_field_not_in_select_col() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").not_in_visage(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age NOT IN (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_gt_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").gt_any(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age > ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_gte_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").gte_any(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age >= ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_lt_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").lt_any(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age < ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_lte_any_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").lte_any(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age <= ANY (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_gt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").gt_all(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age > ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_gte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").gte_all(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age >= ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_lt_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").lt_all(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age < ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn field_ref_option_lte_all_visage_emits_quantified_subquery() {
let subquery =
crate::query::VisageQuerySet::<FakeRowPublic>::new_for_visage("fake_rows", "id")
.selecting(test_visage_col::<i64>("id"))
.expect("clean queryset");
let expr = FieldRef::<FakeRow, Option<i64>>::new("maybe_age").lte_all(subquery);
assert_eq!(
emit_expr_sql(expr),
"maybe_age <= ALL (SELECT id FROM fake_rows)"
);
}
#[test]
fn djogi_field_optional_ordering_routes_through_present_field_payloads() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
for (predicate, expected_op) in [
(f.gt(7_i64).into_inner(), sassi::LookupOp::Gt),
(f.gte(7_i64).into_inner(), sassi::LookupOp::Gte),
(f.lt(7_i64).into_inner(), sassi::LookupOp::Lt),
(f.lte(7_i64).into_inner(), sassi::LookupOp::Lte),
] {
match predicate {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "maybe_age");
assert_eq!(field.op(), expected_op);
assert_eq!(
field.value_as::<i64>(),
Some(&7),
"ordering arm payload must be inner U, not Option<U>",
);
assert!(
field.value_as::<Option<i64>>().is_none(),
"Option<U> downcast must NOT match — that would route to a NULL-aware arm by mistake",
);
}
other => panic!("expected Field predicate, got {other:?}"),
}
}
}
#[test]
fn djogi_field_optional_between_routes_through_present_field_pair() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
match f.between(10_i64, 20_i64).into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "maybe_age");
assert_eq!(field.op(), sassi::LookupOp::Between);
assert_eq!(field.value_as::<(i64, i64)>(), Some(&(10_i64, 20_i64)));
assert!(field.value_as::<(Option<i64>, Option<i64>)>().is_none());
}
other => panic!("expected Between field predicate, got {other:?}"),
}
}
#[test]
fn djogi_field_optional_string_accepts_borrowed_str_for_eq() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<String>>(
"tagline",
|_row| {
static NONE: Option<String> = None;
&NONE
},
);
match f.eq("hello").into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "tagline");
assert_eq!(field.op(), sassi::LookupOp::Eq);
assert_eq!(
field.value_as::<Option<String>>(),
Some(&Some("hello".to_owned())),
);
}
other => panic!("expected Eq field predicate, got {other:?}"),
}
}
#[test]
fn djogi_field_string_contains_routes_to_portable_case_contract() {
let f = djogi_field_macro_support::__make_djogi_field::<FakeRow, String>("title", |row| {
&row.title
});
match f.contains("Rust").into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "title");
assert_eq!(field.op(), sassi::LookupOp::IContains);
assert_eq!(field.value_as::<String>(), Some(&"Rust".to_string()));
}
other => panic!("expected IContains field predicate, got {other:?}"),
}
match f.contains_case_sensitive("Rust").into_inner() {
sassi::BasicPredicate::Field(field) => {
assert_eq!(field.field_name(), "title");
assert_eq!(field.op(), sassi::LookupOp::Contains);
assert_eq!(field.value_as::<String>(), Some(&"Rust".to_string()));
}
other => panic!("expected Contains field predicate, got {other:?}"),
}
}
#[test]
fn field_ref_eq_emits_leaf_with_eq_op() {
let f: FieldRef<Fake, i64> = FieldRef::new("age");
let c = f.eq(42i64);
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.column, "age");
assert_eq!(leaf.op, LookupOp::Eq);
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_is_copy() {
let f: FieldRef<Fake, String> = FieldRef::new("title");
let g = f;
let h = f;
let _ = g.eq("a");
let _ = h.neq("b");
}
#[test]
fn field_ref_option_scalar_accepts_inner_value_lookup() {
let f: FieldRef<Fake, Option<i16>> = FieldRef::new("estimated_birth_year");
let c = f.lte(1990_i16);
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.column, "estimated_birth_year");
assert_eq!(leaf.op, LookupOp::Lte);
assert!(matches!(leaf.value, FilterValue::I16(1990)));
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_tracked_string_accepts_inner_str_lookup() {
let f: FieldRef<Fake, Tracked<String>> = FieldRef::new("label");
let c = f.eq("alpha");
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.column, "label");
assert_eq!(leaf.op, LookupOp::Eq);
assert!(matches!(leaf.value, FilterValue::String(ref s) if s == "alpha"));
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_gte_emits_leaf_with_gte_op() {
let f: FieldRef<Fake, i64> = FieldRef::new("view_count");
let c = f.gte(100i64);
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.column, "view_count");
assert_eq!(leaf.op, LookupOp::Gte);
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_between_emits_pair_filter_value() {
let f: FieldRef<Fake, i64> = FieldRef::new("age");
let c = f.between(10i64, 20i64);
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.op, LookupOp::Between);
assert!(matches!(leaf.value, FilterValue::Pair(_, _)));
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_in_list_emits_list_filter_value() {
let f: FieldRef<Fake, i64> = FieldRef::new("id");
let c = f.in_list(vec![1i64, 2, 3]);
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.op, LookupOp::In);
if let FilterValue::List(items) = &leaf.value {
assert_eq!(items.len(), 3);
} else {
panic!("expected FilterValue::List");
}
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_is_null_emits_null_filter_value() {
let f: FieldRef<Fake, String> = FieldRef::new("deleted_at");
let c = f.is_null();
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.op, LookupOp::IsNull);
assert!(matches!(leaf.value, FilterValue::Null));
} else {
panic!("expected Leaf");
}
}
#[test]
fn field_ref_contains_string_only() {
let f: FieldRef<Fake, String> = FieldRef::new("title");
let c = f.contains("hello");
if let Condition::Leaf(leaf) = c {
assert_eq!(leaf.op, LookupOp::IContains);
} else {
panic!("expected Leaf");
}
}
#[test]
fn condition_and_with_chains_fluently() {
let f: FieldRef<Fake, i64> = FieldRef::new("a");
let g: FieldRef<Fake, i64> = FieldRef::new("b");
let combined = f.eq(1i64).and_with(g.eq(2i64));
if let Condition::And(parts) = combined {
assert_eq!(parts.len(), 2);
} else {
panic!("expected And, got {combined:?}");
}
}
#[test]
fn condition_or_with_chains_fluently() {
let f: FieldRef<Fake, i64> = FieldRef::new("a");
let g: FieldRef<Fake, i64> = FieldRef::new("b");
let combined = f.eq(1i64).or_with(g.eq(2i64));
if let Condition::Or(parts) = combined {
assert_eq!(parts.len(), 2);
} else {
panic!("expected Or, got {combined:?}");
}
}
#[test]
fn djogi_field_json_object_agg_accepts_djogi_field_argument() {
let f_id: DjogiField<FakeRow, i64> =
djogi_field_macro_support::__make_djogi_field::<FakeRow, i64>("id", |row| &row.id);
let f_name: DjogiField<FakeRow, String> = djogi_field_macro_support::__make_djogi_field::<
FakeRow,
String,
>("title", |row| &row.title);
let _: crate::expr::AggregateExpr<serde_json::Value> = f_id.json_object_agg(f_name);
}
#[test]
fn djogi_field_jsonb_object_agg_accepts_djogi_field_argument() {
let f_id: DjogiField<FakeRow, i64> =
djogi_field_macro_support::__make_djogi_field::<FakeRow, i64>("id", |row| &row.id);
let f_name: DjogiField<FakeRow, Option<i64>> =
djogi_field_macro_support::__make_djogi_field::<FakeRow, Option<i64>>(
"maybe_age",
|row| &row.maybe_age,
);
let _: crate::expr::AggregateExpr<serde_json::Value> = f_id.jsonb_object_agg(f_name);
}
}
#[cfg(all(test, feature = "spatial"))]
mod spatial_field_tests {
use super::*;
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
use crate::geo::{GeoPoint, LineString, MultiPoint, MultiPolygon, Polygon};
use crate::query::condition::Condition;
use std::future::Future;
struct Fake;
impl crate::model::__sealed::Sealed for Fake {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for Fake {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fakes"
}
fn pk_value(&self) -> &i64 {
unimplemented!()
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
}
fn make_polygon() -> Polygon {
let ring = [
GeoPoint::new(0.0, 0.0).unwrap(),
GeoPoint::new(1.0, 0.0).unwrap(),
GeoPoint::new(1.0, 1.0).unwrap(),
GeoPoint::new(0.0, 1.0).unwrap(),
GeoPoint::new(0.0, 0.0).unwrap(), ];
Polygon::closed(&ring).unwrap()
}
fn unwrap_spatial(cond: Condition) -> SpatialExpr {
if let Condition::Expr(expr) = cond
&& let ExprNode::Spatial(s) = expr.node
{
return s;
}
panic!("expected Condition::Expr(ExprNode::Spatial(...))");
}
#[test]
fn contains_method_dispatch_produces_contains_variant() {
let poly = make_polygon();
let field: FieldRef<Fake, Polygon> = FieldRef::new("area");
let cond = field.contains(&poly);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::Contains {
field_column: "area",
..
}
),
"expected Contains variant, got {s:?}"
);
}
#[test]
fn contains_method_dispatch_ewkb_is_bound() {
use crate::pg::accumulator::SqlAccumulator;
let poly = make_polygon();
let field: FieldRef<Fake, Polygon> = FieldRef::new("area");
let cond = field.contains(&poly);
let s = unwrap_spatial(cond);
let mut acc = SqlAccumulator::new("");
s.emit(&mut acc);
assert_eq!(acc.bind_count(), 1, "EWKB must flow through push_bind");
assert!(acc.sql().contains("$1"), "expected $1 placeholder");
}
#[test]
fn intersects_method_dispatch_produces_intersects_variant() {
let pts = [
GeoPoint::new(0.0, 0.0).unwrap(),
GeoPoint::new(1.0, 1.0).unwrap(),
];
let line = LineString::new(&pts).unwrap();
let field: FieldRef<Fake, LineString> = FieldRef::new("route");
let cond = field.intersects(&line);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::Intersects {
field_column: "route",
..
}
),
"expected Intersects variant, got {s:?}"
);
}
#[test]
fn intersects_cross_geometry_polygon_field_with_linestring_arg() {
let pts = [
GeoPoint::new(0.0, 0.0).unwrap(),
GeoPoint::new(2.0, 2.0).unwrap(),
];
let line = LineString::new(&pts).unwrap();
let field: FieldRef<Fake, Polygon> = FieldRef::new("area");
let cond = field.intersects(&line);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::Intersects {
field_column: "area",
..
}
),
"cross-geometry intersects failed: {s:?}"
);
}
#[test]
fn touches_method_dispatch_produces_touches_variant() {
let poly = make_polygon();
let field: FieldRef<Fake, Polygon> = FieldRef::new("boundary");
let cond = field.touches(&poly);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::Touches {
field_column: "boundary",
..
}
),
"expected Touches variant, got {s:?}"
);
}
#[test]
fn touches_method_dispatch_ewkb_is_bound() {
use crate::pg::accumulator::SqlAccumulator;
let poly = make_polygon();
let field: FieldRef<Fake, Polygon> = FieldRef::new("boundary");
let cond = field.touches(&poly);
let s = unwrap_spatial(cond);
let mut acc = SqlAccumulator::new("");
s.emit(&mut acc);
assert_eq!(acc.bind_count(), 1);
assert!(acc.sql().contains("$1"));
}
#[test]
fn within_method_dispatch_produces_within_shape_variant() {
let poly = make_polygon();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("drop_off");
let cond = field.within(&poly);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::WithinShape {
field_column: "drop_off",
..
}
),
"expected WithinShape variant, got {s:?}"
);
}
#[test]
fn within_method_dispatch_ewkb_is_bound() {
use crate::pg::accumulator::SqlAccumulator;
let poly = make_polygon();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("drop_off");
let cond = field.within(&poly);
let s = unwrap_spatial(cond);
let mut acc = SqlAccumulator::new("");
s.emit(&mut acc);
assert_eq!(acc.bind_count(), 1);
assert!(acc.sql().contains("$1"));
}
#[test]
fn within_cross_geometry_multipolygon_field() {
let poly = make_polygon();
let mpoly = MultiPolygon::new(vec![poly]).unwrap();
let field: FieldRef<Fake, MultiPolygon> = FieldRef::new("coverage");
let cond = field.within(&mpoly);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::WithinShape {
field_column: "coverage",
..
}
),
"cross-geometry within failed: {s:?}"
);
}
#[test]
fn contains_cross_geometry_geopoint_field_multipoint_arg() {
let pts = [GeoPoint::new(0.0, 0.0).unwrap()];
let mpt = MultiPoint::new(&pts).unwrap();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("loc");
let cond = field.intersects(&mpt);
let s = unwrap_spatial(cond);
assert!(
matches!(
s,
SpatialExpr::Intersects {
field_column: "loc",
..
}
),
"expected Intersects, got {s:?}"
);
}
}
#[cfg(all(test, feature = "spatial"))]
mod bbox_tests {
use super::*;
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
use crate::geo::{GeoPoint, MultiPolygon, Polygon};
use crate::pg::accumulator::SqlAccumulator;
use std::future::Future;
struct Fake;
impl crate::model::__sealed::Sealed for Fake {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for Fake {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fakes"
}
fn pk_value(&self) -> &i64 {
unimplemented!()
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
}
fn unwrap_spatial_from_expr(expr: crate::expr::Expr<bool>) -> SpatialExpr {
if let ExprNode::Spatial(s) = expr.node {
return s;
}
panic!("expected ExprNode::Spatial(...)");
}
fn make_polygon() -> Polygon {
let ring = [
GeoPoint::new(0.0, 0.0).unwrap(),
GeoPoint::new(1.0, 0.0).unwrap(),
GeoPoint::new(1.0, 1.0).unwrap(),
GeoPoint::new(0.0, 1.0).unwrap(),
GeoPoint::new(0.0, 0.0).unwrap(),
];
Polygon::closed(&ring).unwrap()
}
#[test]
fn bounded_by_emits_st_makeenvelope_in_xy_order() {
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let expr = field.bounded_by(37.0, -123.0, 38.0, -122.0);
let s = unwrap_spatial_from_expr(expr);
let mut acc = SqlAccumulator::new("");
s.emit(&mut acc);
let sql = acc.sql();
assert!(
sql.contains("ST_MakeEnvelope("),
"expected ST_MakeEnvelope; got: {sql}"
);
assert!(
sql.contains("::geography &&"),
"expected ::geography &&; got: {sql}"
);
assert!(
sql.contains("location"),
"expected column 'location' after &&; got: {sql}"
);
assert_eq!(
acc.bind_count(),
4,
"expected 4 binds; got {}",
acc.bind_count()
);
}
#[test]
fn bounded_by_emits_all_four_coords_as_binds() {
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("loc");
let expr = field.bounded_by(55.5555, 66.6666, 77.7777, 88.8888);
let s = unwrap_spatial_from_expr(expr);
let mut acc = SqlAccumulator::new("");
s.emit(&mut acc);
let sql = acc.sql();
assert!(!sql.contains("55.5555"), "min_lat leaked; got: {sql}");
assert!(!sql.contains("66.6666"), "min_lon leaked; got: {sql}");
assert!(!sql.contains("77.7777"), "max_lat leaked; got: {sql}");
assert!(!sql.contains("88.8888"), "max_lon leaked; got: {sql}");
assert_eq!(acc.bind_count(), 4);
}
#[test]
fn bounded_by_works_on_polygon_and_multipolygon_fieldrefs() {
let poly_field: FieldRef<Fake, Polygon> = FieldRef::new("area");
let expr_poly = poly_field.bounded_by(37.0, -123.0, 38.0, -122.0);
let s_poly = unwrap_spatial_from_expr(expr_poly);
assert!(
matches!(
s_poly,
SpatialExpr::BoundedBy {
field_column: "area",
..
}
),
"expected BoundedBy on Polygon field; got {s_poly:?}"
);
let mpoly = make_polygon();
let mp_field: FieldRef<Fake, MultiPolygon> = FieldRef::new("coverage");
let _ = mpoly; let expr_mp = mp_field.bounded_by(0.0, 0.0, 1.0, 1.0);
let s_mp = unwrap_spatial_from_expr(expr_mp);
assert!(
matches!(
s_mp,
SpatialExpr::BoundedBy {
field_column: "coverage",
..
}
),
"expected BoundedBy on MultiPolygon field; got {s_mp:?}"
);
}
}
#[cfg(all(test, feature = "spatial"))]
mod distance_tests {
use super::*;
use crate::expr::node::ExprNode;
use crate::expr::spatial::SpatialExpr;
use crate::geo::{GeoPoint, Polygon};
use crate::pg::accumulator::SqlAccumulator;
use std::future::Future;
fn make_polygon() -> Polygon {
let ring = [
GeoPoint::new(0.0, 0.0).unwrap(),
GeoPoint::new(1.0, 0.0).unwrap(),
GeoPoint::new(1.0, 1.0).unwrap(),
GeoPoint::new(0.0, 1.0).unwrap(),
GeoPoint::new(0.0, 0.0).unwrap(),
];
Polygon::closed(&ring).unwrap()
}
struct Fake;
impl crate::model::__sealed::Sealed for Fake {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for Fake {
type Pk = i64;
type Fields = ();
fn table_name() -> &'static str {
"fakes"
}
fn pk_value(&self) -> &i64 {
unimplemented!()
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unimplemented!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: i64,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl Future<Output = Result<(), crate::DjogiError>> + Send {
async { unimplemented!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl Future<Output = Result<Self, crate::DjogiError>> + Send + 'ctx {
async { unimplemented!() }
}
}
#[test]
fn distance_to_emits_st_distance() {
let center = GeoPoint::new(37.7749, -122.4194).unwrap();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("loc");
let expr: crate::expr::Expr<f64> = field.distance_to(¢er);
if let ExprNode::Spatial(SpatialExpr::Distance {
field_column,
center: c,
}) = expr.node
{
assert_eq!(field_column, "loc");
assert!((c.lat - 37.7749).abs() < 1e-10, "lat mismatch: {}", c.lat);
assert!(
(c.lon - (-122.4194)).abs() < 1e-10,
"lon mismatch: {}",
c.lon
);
let mut acc = SqlAccumulator::new("");
SpatialExpr::Distance {
field_column,
center: c,
}
.emit(&mut acc);
let sql = acc.sql();
assert!(
sql.contains("ST_Distance"),
"expected ST_Distance; got: {sql}"
);
assert!(sql.contains("loc"), "expected column; got: {sql}");
assert!(
sql.contains("::geography"),
"expected ::geography; got: {sql}"
);
assert_eq!(acc.bind_count(), 2, "expected 2 binds (lon, lat)");
} else {
panic!("expected Distance spatial expr");
}
}
#[test]
fn distance_to_composes_with_filter_lt() {
let center = GeoPoint::new(48.8566, 2.3522).unwrap();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("position");
let predicate: crate::expr::Expr<bool> = field.distance_to(¢er).lt(1000.0f64);
if let ExprNode::Cmp { lhs, .. } = predicate.node
&& let ExprNode::Spatial(SpatialExpr::Distance { field_column, .. }) = *lhs
{
assert_eq!(field_column, "position");
return;
}
panic!("expected Cmp {{ lhs: Spatial(Distance {{..}}) }}");
}
#[test]
fn distance_to_produces_f64_expr() {
let center = GeoPoint::new(0.0, 0.0).unwrap();
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("loc");
let _expr: crate::expr::Expr<f64> = field.distance_to(¢er);
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_on_geopoint_field_produces_aggregate_polygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::Polygon as PolygonTy;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg: AggregateExpr<PolygonTy> = field.convex_hull();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialConvexHull));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "location");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialConvexHull)");
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_dispatches_from_polygon_field_too() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::Polygon as PolygonTy;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<PolygonTy> = field.convex_hull();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialConvexHull));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "territory");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ConvexHull AggOp on Polygon field");
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_emits_st_convexhull_st_collect_with_geography_cast() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.convex_hull();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_ConvexHull(ST_Collect(location::geometry))::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_distinct_lands_inside_st_collect() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.convex_hull().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_ConvexHull(ST_Collect(DISTINCT location::geometry))::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_filter_attaches_to_inner_st_collect() {
use crate::expr::Expr;
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let confidence: FieldRef<Fake, f64> = FieldRef::new("confidence");
let agg = loc
.convex_hull()
.filter(confidence.as_expr().gt(Expr::literal(0.5_f64)));
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("ST_ConvexHull(ST_Collect("),
"must open ST_ConvexHull(ST_Collect(...; got: {sql}"
);
assert!(
sql.contains(" FILTER (WHERE confidence > "),
"FILTER clause must be present; got: {sql}"
);
assert!(
sql.ends_with(")::geography"),
"must end with )::geography; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_on_geopoint_field_produces_aggregate_geopoint() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg: AggregateExpr<GeoPoint> = field.centroid();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialCentroid));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "location");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialCentroid)");
}
#[cfg(feature = "spatial")]
#[test]
fn collect_on_geopoint_field_produces_aggregate_multipoint() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::MultiPoint;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg: AggregateExpr<MultiPoint> = field.collect();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialCollect));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "location");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialCollect)");
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_emits_st_centroid_st_collect_with_geography_cast() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.centroid();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Centroid(ST_Collect(location::geometry))::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn collect_emits_st_collect_with_geography_cast() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.collect();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_Collect(location::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_with_distinct_emits_distinct_inside_st_collect() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.centroid().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Centroid(ST_Collect(DISTINCT location::geometry))::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_with_filter_attaches_to_inner_st_collect() {
use crate::expr::Expr;
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let confidence: FieldRef<Fake, f64> = FieldRef::new("confidence");
let agg = loc
.centroid()
.filter(confidence.as_expr().gt(Expr::literal(0.5_f64)));
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("ST_Centroid(ST_Collect(location::geometry)"),
"must start with ST_Centroid(ST_Collect(...), got: {sql}"
);
assert!(
sql.contains(" FILTER (WHERE confidence > "),
"FILTER clause must be present, got: {sql}"
);
assert!(
sql.ends_with(")::geography"),
"must end with )::geography (cast outside ST_Centroid), got: {sql}"
);
let filter_idx = sql.find(" FILTER (WHERE").unwrap();
let cast_idx = sql.rfind("::geography").unwrap();
assert!(
filter_idx < cast_idx,
"FILTER must precede ::geography cast for valid Postgres syntax; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_with_over_in_annotate_path_places_over_on_inner_collect() {
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = loc.centroid();
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_aggregate_with_window_and_cast(&mut acc, &agg.node)
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert_eq!(
sql, "ST_Centroid(ST_Collect(location::geometry) OVER ())::geography",
"OVER must attach to the inner ST_Collect aggregate, inside the ST_Centroid \
scalar wrapper; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn collect_with_over_in_annotate_path_places_over_inside_geography_cast() {
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = loc.collect();
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_aggregate_with_window_and_cast(&mut acc, &agg.node)
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert_eq!(
sql, "(ST_Collect(location::geometry) OVER ())::geography",
"OVER must fall inside the ::geography cast; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn collect_with_filter_and_over_attaches_modifiers_to_aggregate_call() {
use crate::expr::Expr;
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let confidence: FieldRef<Fake, f64> = FieldRef::new("confidence");
let agg = loc
.collect()
.filter(confidence.as_expr().gt(Expr::literal(0.5_f64)));
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_aggregate_with_window_and_cast(&mut acc, &agg.node)
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("(ST_Collect(location::geometry) FILTER (WHERE confidence > "),
"expected single outer paren before ST_Collect (no nested filter parens); got: {sql}"
);
assert!(sql.contains(" OVER ()"), "OVER must be present; got: {sql}");
assert!(
sql.ends_with(")::geography"),
"must end with )::geography (cast outside (AGG FILTER OVER)); got: {sql}"
);
assert!(
!sql.starts_with("(("),
"must not start with `((` (anti-regression); got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn centroid_with_filter_and_over_places_both_inside_inner_collect() {
use crate::expr::Expr;
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let confidence: FieldRef<Fake, f64> = FieldRef::new("confidence");
let agg = loc
.centroid()
.filter(confidence.as_expr().gt(Expr::literal(0.5_f64)));
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_aggregate_with_window_and_cast(&mut acc, &agg.node)
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("ST_Centroid(ST_Collect("),
"must open with ST_Centroid(ST_Collect(...; got: {sql}"
);
assert!(
sql.contains(" FILTER (WHERE confidence > "),
"FILTER clause must be present; got: {sql}"
);
assert!(
sql.contains(" OVER ()"),
"OVER clause must be present; got: {sql}"
);
assert!(
sql.ends_with(")::geography"),
"must end with )::geography (cast outside wrapper); got: {sql}"
);
let filter_idx = sql.find(" FILTER (").unwrap();
let over_idx = sql.find(" OVER (").unwrap();
let cast_idx = sql.rfind("::geography").unwrap();
assert!(
filter_idx < over_idx && over_idx < cast_idx,
"modifier order must be FILTER < OVER < ::geography; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn convex_hull_with_over_places_over_on_inner_collect() {
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = loc.convex_hull();
let mut acc = SqlAccumulator::new("");
crate::query::sql::emit_aggregate_with_window_and_cast(&mut acc, &agg.node)
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert_eq!(
sql, "ST_ConvexHull(ST_Collect(location::geometry) OVER ())::geography",
"OVER must attach to the inner ST_Collect aggregate, inside the ST_ConvexHull \
scalar wrapper; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn union_on_polygon_field_produces_aggregate_multipolygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{MultiPolygon, Polygon as PolygonTy};
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<MultiPolygon> = field.union();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialUnion));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "territory");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialUnion)");
}
#[cfg(feature = "spatial")]
#[test]
fn union_on_multipolygon_field_also_dispatches() {
use crate::expr::AggregateExpr;
use crate::geo::MultiPolygon;
let field: FieldRef<Fake, MultiPolygon> = FieldRef::new("region");
let _agg: AggregateExpr<MultiPolygon> = field.union();
}
#[cfg(feature = "spatial")]
#[test]
fn union_emits_st_union_with_geography_cast() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.union();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_Union(territory::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn union_with_distinct_emits_distinct_inside_st_union() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.union().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Union(DISTINCT territory::geometry)::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn extent_on_geopoint_field_produces_aggregate_polygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::Polygon as PolygonTy;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg: AggregateExpr<PolygonTy> = field.extent();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialExtent));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "location");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialExtent)");
}
#[cfg(feature = "spatial")]
#[test]
fn extent_emits_box2d_geometry_geography_cast_chain() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.extent();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Extent(location::geometry)::geometry::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn extent_3d_on_geopoint_field_produces_aggregate_polygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::Polygon as PolygonTy;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg: AggregateExpr<PolygonTy> = field.extent_3d();
if let ExprNode::Aggregate { op, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialExtent3D));
return;
}
panic!("expected ExprNode::Aggregate(SpatialExtent3D)");
}
#[cfg(feature = "spatial")]
#[test]
fn extent_3d_emits_st_3dextent_with_two_step_cast_chain() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let agg = field.extent_3d();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_3DExtent(location::geometry)::geometry::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn extent_with_filter_attaches_to_inner_aggregate_before_cast() {
use crate::expr::Expr;
use crate::pg::accumulator::SqlAccumulator;
let loc: FieldRef<Fake, GeoPoint> = FieldRef::new("location");
let confidence: FieldRef<Fake, f64> = FieldRef::new("confidence");
let agg = loc
.extent()
.filter(confidence.as_expr().gt(Expr::literal(0.5_f64)));
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("(ST_Extent(location::geometry)"),
"must start with (ST_Extent for FILTER attachment, got: {sql}"
);
assert!(
sql.contains(" FILTER (WHERE confidence > "),
"FILTER clause must be present, got: {sql}"
);
assert!(
sql.ends_with(")::geometry::geography"),
"cast chain must close after FILTER, got: {sql}"
);
let filter_idx = sql.find(" FILTER (WHERE").unwrap();
let cast_idx = sql.rfind("::geometry::geography").unwrap();
assert!(
filter_idx < cast_idx,
"FILTER must precede the ::geometry::geography cast chain; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn make_line_on_geopoint_field_produces_aggregate_linestring() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::LineString;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("position");
let agg: AggregateExpr<LineString> = field.make_line();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialMakeLine));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "position");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialMakeLine)");
}
#[cfg(feature = "spatial")]
#[test]
fn make_line_emits_st_makeline_with_geography_cast() {
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, GeoPoint> = FieldRef::new("position");
let agg = field.make_line();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_MakeLine(position::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn make_line_with_order_by_lands_inside_st_makeline_parens() {
use crate::pg::accumulator::SqlAccumulator;
let position: FieldRef<Fake, GeoPoint> = FieldRef::new("position");
let recorded_at: FieldRef<Fake, i64> = FieldRef::new("recorded_at");
let agg = position.make_line().order_by(recorded_at.asc());
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_MakeLine(position::geometry ORDER BY recorded_at ASC)::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn polygon_agg_on_polygon_field_produces_aggregate_multipolygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{MultiPolygon, Polygon as PolygonTy};
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<MultiPolygon> = field.polygon_agg();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialPolygonAgg));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "territory");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialPolygonAgg)");
}
#[cfg(feature = "spatial")]
#[test]
fn polygon_agg_emits_st_collect_portable_fallback() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.polygon_agg();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_Collect(territory::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn polygon_agg_with_distinct_emits_distinct_inside_st_collect() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.polygon_agg().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Collect(DISTINCT territory::geometry)::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_intersecting_on_polygon_field_produces_aggregate_vec_multipolygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{MultiPolygon, Polygon as PolygonTy};
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<Vec<MultiPolygon>> = field.cluster_intersecting();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialClusterIntersecting));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "territory");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialClusterIntersecting)");
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_intersecting_emits_st_clusterintersecting_with_geography_array_cast() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.cluster_intersecting();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_ClusterIntersecting(territory::geometry)::geography[]"
);
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_within_carries_distance_inline_on_aggop() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{MultiPolygon, Polygon as PolygonTy};
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<Vec<MultiPolygon>> = field.cluster_within(1_000.0);
if let ExprNode::Aggregate { op, .. } = agg.node {
if let AggOp::SpatialClusterWithin(distance) = op {
assert!(
(distance - 1_000.0).abs() < f64::EPSILON,
"distance must round-trip; got {distance}"
);
return;
}
panic!("expected AggOp::SpatialClusterWithin variant");
}
panic!("expected ExprNode::Aggregate");
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_within_emits_st_clusterwithin_with_distance_bound() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.cluster_within(500.0);
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_ClusterWithin(territory::geometry, $1)::geography[]"
);
assert_eq!(acc.bind_count(), 1, "expected 1 bind for the distance");
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_intersecting_on_multipolygon_field_also_dispatches() {
use crate::expr::AggregateExpr;
use crate::geo::MultiPolygon;
let field: FieldRef<Fake, MultiPolygon> = FieldRef::new("region");
let _agg: AggregateExpr<Vec<MultiPolygon>> = field.cluster_intersecting();
}
#[cfg(feature = "spatial")]
#[test]
fn cluster_within_on_multipolygon_field_also_dispatches() {
use crate::expr::AggregateExpr;
use crate::geo::MultiPolygon;
let field: FieldRef<Fake, MultiPolygon> = FieldRef::new("region");
let _agg: AggregateExpr<Vec<MultiPolygon>> = field.cluster_within(2_000.0);
}
#[cfg(feature = "spatial")]
#[test]
fn mem_union_on_polygon_field_produces_aggregate_multipolygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{MultiPolygon, Polygon as PolygonTy};
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg: AggregateExpr<MultiPolygon> = field.mem_union();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialMemUnion));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "territory");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialMemUnion)");
}
#[cfg(feature = "spatial")]
#[test]
fn mem_union_emits_st_memunion_with_geography_cast() {
use crate::geo::Polygon as PolygonTy;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, PolygonTy> = FieldRef::new("territory");
let agg = field.mem_union();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_MemUnion(territory::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn mem_union_on_multipolygon_field_also_dispatches() {
use crate::expr::AggregateExpr;
use crate::geo::MultiPolygon;
let field: FieldRef<Fake, MultiPolygon> = FieldRef::new("region");
let _agg: AggregateExpr<MultiPolygon> = field.mem_union();
}
#[cfg(feature = "spatial")]
#[test]
fn polygonize_on_linestring_field_produces_aggregate_multipolygon() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{LineString, MultiPolygon};
let field: FieldRef<Fake, LineString> = FieldRef::new("edge");
let agg: AggregateExpr<MultiPolygon> = field.polygonize();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialPolygonize));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "edge");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialPolygonize)");
}
#[cfg(feature = "spatial")]
#[test]
fn polygonize_emits_st_polygonize_with_geography_cast() {
use crate::geo::LineString;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, LineString> = FieldRef::new("edge");
let agg = field.polygonize();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_Polygonize(edge::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn polygonize_with_distinct_emits_distinct_inside_st_polygonize() {
use crate::geo::LineString;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, LineString> = FieldRef::new("edge");
let agg = field.polygonize().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(
acc.sql(),
"ST_Polygonize(DISTINCT edge::geometry)::geography"
);
}
#[cfg(feature = "spatial")]
#[test]
fn line_agg_on_linestring_field_produces_aggregate_multilinestring() {
use crate::expr::AggregateExpr;
use crate::expr::node::AggOp;
use crate::geo::{LineString, MultiLineString};
let field: FieldRef<Fake, LineString> = FieldRef::new("path");
let agg: AggregateExpr<MultiLineString> = field.line_agg();
if let ExprNode::Aggregate { op, arg, .. } = agg.node {
assert!(matches!(op, AggOp::SpatialLineAgg));
if let ExprNode::Field { column } = *arg {
assert_eq!(column, "path");
return;
}
panic!("expected Aggregate.arg to wrap the column");
}
panic!("expected ExprNode::Aggregate(SpatialLineAgg)");
}
#[cfg(feature = "spatial")]
#[test]
fn line_agg_emits_st_lineagg_with_geography_cast() {
use crate::geo::LineString;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, LineString> = FieldRef::new("path");
let agg = field.line_agg();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_LineAgg(path::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn line_agg_with_distinct_emits_distinct_inside_st_lineagg() {
use crate::geo::LineString;
use crate::pg::accumulator::SqlAccumulator;
let field: FieldRef<Fake, LineString> = FieldRef::new("path");
let agg = field.line_agg().distinct();
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
assert_eq!(acc.sql(), "ST_LineAgg(DISTINCT path::geometry)::geography");
}
#[cfg(feature = "spatial")]
#[test]
fn line_agg_with_filter_attaches_before_cast() {
use crate::expr::Expr;
use crate::geo::LineString;
use crate::pg::accumulator::SqlAccumulator;
let path: FieldRef<Fake, LineString> = FieldRef::new("path");
let len_m: FieldRef<Fake, f64> = FieldRef::new("length_m");
let agg = path
.line_agg()
.filter(len_m.as_expr().gt(Expr::literal(100.0_f64)));
let mut acc = SqlAccumulator::new("");
crate::expr::sql::emit_expr(&mut acc, &agg.node, crate::query::SqlEmitContext::root())
.expect("aggregate emission");
let sql = acc.sql().to_string();
assert!(
sql.starts_with("(ST_LineAgg(path::geometry)"),
"must start with (ST_LineAgg for FILTER attachment, got: {sql}"
);
assert!(
sql.contains(" FILTER (WHERE length_m > "),
"FILTER clause must be present, got: {sql}"
);
assert!(
sql.ends_with(")::geography"),
"::geography cast must close after FILTER, got: {sql}"
);
let filter_idx = sql.find(" FILTER (WHERE").unwrap();
let cast_idx = sql.rfind("::geography").unwrap();
assert!(
filter_idx < cast_idx,
"FILTER must precede ::geography cast for valid Postgres syntax; got: {sql}"
);
}
#[cfg(feature = "spatial")]
#[test]
fn area_of_produces_f64_expr_with_area_node() {
let poly = make_polygon();
let expr: crate::expr::Expr<f64> = crate::expr::Expr::area_of(&poly);
if let ExprNode::Spatial(SpatialExpr::Area { geom_ewkb }) = expr.node {
assert!(!geom_ewkb.is_empty(), "Area must capture EWKB bytes");
return;
}
panic!("expected ExprNode::Spatial(SpatialExpr::Area {{..}})");
}
#[cfg(feature = "spatial")]
#[test]
fn area_of_intersection_produces_f64_expr_with_fused_node() {
let a = make_polygon();
let b = make_polygon();
let expr: crate::expr::Expr<f64> = crate::expr::Expr::area_of_intersection(&a, &b);
if let ExprNode::Spatial(SpatialExpr::AreaOfIntersection { a_ewkb, b_ewkb }) = expr.node {
assert!(!a_ewkb.is_empty(), "a EWKB captured");
assert!(!b_ewkb.is_empty(), "b EWKB captured");
return;
}
panic!("expected ExprNode::Spatial(SpatialExpr::AreaOfIntersection {{..}})");
}
#[cfg(feature = "spatial")]
#[test]
fn area_of_intersection_divides_by_area_of_for_overlap_pct() {
use crate::expr::Expr;
let a = make_polygon();
let b = make_polygon();
let ratio: Expr<f64> = Expr::area_of_intersection(&a, &b) / Expr::area_of(&a);
if let ExprNode::Div(lhs, rhs) = ratio.node
&& matches!(
*lhs,
ExprNode::Spatial(SpatialExpr::AreaOfIntersection { .. })
)
&& matches!(*rhs, ExprNode::Spatial(SpatialExpr::Area { .. }))
{
return;
}
panic!("expected Div(Spatial(AreaOfIntersection), Spatial(Area))");
}
#[test]
fn into_filter_value_i8_widens_to_i16() {
match (-1i8).into_filter_value() {
FilterValue::I16(v) => assert_eq!(v, -1),
other => panic!("expected I16, got {other:?}"),
}
match i8::MAX.into_filter_value() {
FilterValue::I16(v) => assert_eq!(v, 127),
other => panic!("expected I16, got {other:?}"),
}
}
#[test]
fn into_filter_value_u8_widens_to_i16() {
match 0u8.into_filter_value() {
FilterValue::I16(v) => assert_eq!(v, 0),
other => panic!("expected I16, got {other:?}"),
}
match u8::MAX.into_filter_value() {
FilterValue::I16(v) => assert_eq!(v, 255),
other => panic!("expected I16, got {other:?}"),
}
}
#[test]
fn into_filter_value_u16_widens_to_i32() {
match 0u16.into_filter_value() {
FilterValue::I32(v) => assert_eq!(v, 0),
other => panic!("expected I32, got {other:?}"),
}
match u16::MAX.into_filter_value() {
FilterValue::I32(v) => assert_eq!(v, 65_535),
other => panic!("expected I32, got {other:?}"),
}
}
#[test]
fn into_filter_value_u32_widens_to_i64() {
match 0u32.into_filter_value() {
FilterValue::I64(v) => assert_eq!(v, 0),
other => panic!("expected I64, got {other:?}"),
}
match u32::MAX.into_filter_value() {
FilterValue::I64(v) => assert_eq!(v, 4_294_967_295),
other => panic!("expected I64, got {other:?}"),
}
}
#[test]
fn into_filter_value_u64_widens_to_decimal() {
match 0u64.into_filter_value() {
FilterValue::Decimal(v) => assert_eq!(v, rust_decimal::Decimal::from(0u64)),
other => panic!("expected Decimal, got {other:?}"),
}
match u64::MAX.into_filter_value() {
FilterValue::Decimal(v) => assert_eq!(v, rust_decimal::Decimal::from(u64::MAX)),
other => panic!("expected Decimal, got {other:?}"),
}
}
}