Skip to main content

rainier_orm/
row.rs

1//! Row decoding (`Row` + `FromColumn`) and value encoding (`ToColumn`).
2//!
3//! An [`Executor`](crate::Executor) yields opaque `Box<dyn Row>` values; the
4//! generated `Entity::from_row` reads each field through [`FromColumn`], which
5//! dispatches to the typed getter on [`Row`]. Encoding for inserts/updates
6//! goes the other way through [`ToColumn`], which lowers a field to a
7//! `sea_query::Value`. Supporting a new field type is one `SqlType` impl plus
8//! — if it isn't already `Into<sea_query::Value>` and readable from a getter —
9//! a `FromColumn` impl.
10
11use crate::{ColumnType, Error, Result, SqlType};
12use chrono::{DateTime, NaiveDate, Utc};
13use sea_query::Value;
14
15/// A single result row, addressed by column name. Every getter returns
16/// `Option` so a NULL is distinguishable from a missing/zero value; the
17/// non-`Option` `FromColumn` impls turn a `None` into a decode error.
18///
19/// Implementors back this with whatever their driver hands back — a
20/// `sea_orm::QueryResult`, a parsed D1 JSON object, etc.
21pub trait Row {
22    fn get_bool(&self, col: &str) -> Result<Option<bool>>;
23    fn get_i32(&self, col: &str) -> Result<Option<i32>>;
24    fn get_i64(&self, col: &str) -> Result<Option<i64>>;
25    fn get_u32(&self, col: &str) -> Result<Option<u32>>;
26    fn get_u64(&self, col: &str) -> Result<Option<u64>>;
27    fn get_f64(&self, col: &str) -> Result<Option<f64>>;
28    fn get_string(&self, col: &str) -> Result<Option<String>>;
29    fn get_bytes(&self, col: &str) -> Result<Option<Vec<u8>>>;
30    fn get_datetime(&self, col: &str) -> Result<Option<DateTime<Utc>>>;
31    fn get_naive_date(&self, col: &str) -> Result<Option<NaiveDate>>;
32}
33
34/// Decodes one column of a [`Row`] into a Rust value. The generated decoder
35/// calls `T::from_column(row, "col")?` per field. The bare `T` impls treat a
36/// NULL as a decode error; the blanket `Option<T>` impl below turns it into
37/// `None`.
38pub trait FromColumn: Sized {
39    fn from_column(row: &dyn Row, col: &str) -> Result<Self>;
40}
41
42macro_rules! from_column {
43    ($t:ty, $getter:ident) => {
44        impl FromColumn for $t {
45            fn from_column(row: &dyn Row, col: &str) -> Result<Self> {
46                row.$getter(col)?.ok_or_else(|| Error::msg(format!("column `{col}` was NULL")))
47            }
48        }
49    };
50}
51
52from_column!(bool, get_bool);
53from_column!(i32, get_i32);
54from_column!(i64, get_i64);
55from_column!(u32, get_u32);
56from_column!(u64, get_u64);
57from_column!(f64, get_f64);
58from_column!(String, get_string);
59from_column!(Vec<u8>, get_bytes);
60from_column!(DateTime<Utc>, get_datetime);
61from_column!(NaiveDate, get_naive_date);
62
63/// Blanket nullability: any decodable, typed column is decodable as `Option`.
64/// Living in this crate (not a per-type macro) keeps it coherent — a custom
65/// type gets `Option` support for free from its bare `FromColumn` + `SqlType`,
66/// without a downstream `impl … for Option<Custom>` (which the orphan rule
67/// forbids). Presence is probed through the getter matching the type's
68/// [`ColumnType`]; only a present cell is decoded.
69impl<T: FromColumn + SqlType> FromColumn for Option<T> {
70    fn from_column(row: &dyn Row, col: &str) -> Result<Self> {
71        let present = match T::COLUMN_TYPE {
72            ColumnType::Bool => row.get_bool(col)?.is_some(),
73            ColumnType::Int => row.get_i32(col)?.is_some(),
74            ColumnType::BigInt => row.get_i64(col)?.is_some(),
75            ColumnType::Uint => row.get_u32(col)?.is_some(),
76            ColumnType::BigUint => row.get_u64(col)?.is_some(),
77            ColumnType::Double => row.get_f64(col)?.is_some(),
78            ColumnType::Text => row.get_string(col)?.is_some(),
79            ColumnType::Binary => row.get_bytes(col)?.is_some(),
80            ColumnType::Timestamp => row.get_datetime(col)?.is_some(),
81            ColumnType::Date => row.get_naive_date(col)?.is_some(),
82        };
83        if present {
84            Ok(Some(T::from_column(row, col)?))
85        } else {
86            Ok(None)
87        }
88    }
89}
90
91/// Lowers a field value to a `sea_query::Value` for binding.
92///
93/// Impl'd explicitly (not via a blanket `impl<T: Into<Value>>`) for each
94/// supported type and its `Option`. The blanket form is tempting but is a
95/// coherence trap: it claims *every* `T`, so a downstream crate could never add
96/// `ToColumn` for its own enum/newtype. Keeping these concrete leaves the trait
97/// open — a custom column type adds one `ToColumn` impl (see
98/// [`impl_string_column!`](crate::impl_string_column) for the string-enum case).
99pub trait ToColumn {
100    fn to_value(&self) -> Value;
101}
102
103macro_rules! to_column {
104    ($t:ty) => {
105        impl ToColumn for $t {
106            fn to_value(&self) -> Value {
107                self.clone().into()
108            }
109        }
110    };
111}
112
113to_column!(bool);
114to_column!(i32);
115to_column!(i64);
116to_column!(u32);
117to_column!(u64);
118to_column!(f64);
119to_column!(String);
120to_column!(Vec<u8>);
121to_column!(DateTime<Utc>);
122to_column!(NaiveDate);
123
124/// Blanket nullability for binding, mirroring the `FromColumn` side: `None`
125/// binds as a *typed* NULL chosen from the inner type's [`ColumnType`] (some
126/// drivers need the type even for NULL), `Some` delegates. Coherent in-crate,
127/// so custom types are nullable without a downstream `Option` impl.
128impl<T: ToColumn + SqlType> ToColumn for Option<T> {
129    fn to_value(&self) -> Value {
130        match self {
131            Some(v) => v.to_value(),
132            None => null_value(T::COLUMN_TYPE),
133        }
134    }
135}
136
137/// The typed NULL for a column type.
138fn null_value(ty: ColumnType) -> Value {
139    match ty {
140        ColumnType::Bool => Value::Bool(None),
141        ColumnType::Int => Value::Int(None),
142        ColumnType::BigInt => Value::BigInt(None),
143        ColumnType::Uint => Value::Unsigned(None),
144        ColumnType::BigUint => Value::BigUnsigned(None),
145        ColumnType::Double => Value::Double(None),
146        ColumnType::Text => Value::String(None),
147        ColumnType::Binary => Value::Bytes(None),
148        ColumnType::Timestamp => Value::ChronoDateTimeUtc(None),
149        ColumnType::Date => Value::ChronoDate(None),
150    }
151}