zino_orm/
decode.rs

1use super::{DatabaseDriver, DatabaseRow};
2use sqlx::{Database, Decode, Row, ValueRef};
3use zino_core::{Decimal, Uuid, error::Error, warn};
4
5/// Decodes a single value as `T` for the field in a row.
6#[inline]
7pub fn decode<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<T, Error>
8where
9    T: Decode<'r, DatabaseDriver>,
10{
11    row.try_get_unchecked(field)
12        .map_err(|err| warn!("fail to decode the `{}` field: {}", field, err))
13}
14
15/// Decodes a single value as `T` for the field in a row,
16/// returning `None` if it was not found.
17#[inline]
18pub fn decode_optional<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<Option<T>, Error>
19where
20    T: Decode<'r, DatabaseDriver>,
21{
22    match row.try_get_raw(field) {
23        Ok(value) => {
24            if value.is_null() {
25                Ok(None)
26            } else {
27                let value = decode_raw(field, value)?;
28                Ok(Some(value))
29            }
30        }
31        Err(err) => {
32            if let sqlx::Error::ColumnNotFound(_) = err {
33                Ok(None)
34            } else {
35                Err(warn!("fail to get the `{}` field: {}", field, err))
36            }
37        }
38    }
39}
40
41/// Decodes a single value as `Decimal` for the field in a row.
42#[cfg(any(
43    feature = "orm-mariadb",
44    feature = "orm-mysql",
45    feature = "orm-postgres",
46    feature = "orm-tidb"
47))]
48#[inline]
49pub fn decode_decimal(row: &DatabaseRow, field: &str) -> Result<Decimal, Error> {
50    match row.try_get_raw(field) {
51        Ok(value) => {
52            if value.is_null() {
53                warn!("decode the NULL as a zero value");
54                Ok(Decimal::ZERO)
55            } else {
56                let value = decode_raw(field, value)?;
57                Ok(value)
58            }
59        }
60        Err(err) => {
61            if let sqlx::Error::ColumnNotFound(_) = err {
62                Ok(Decimal::ZERO)
63            } else {
64                Err(warn!("fail to get the `{}` field: {}", field, err))
65            }
66        }
67    }
68}
69
70/// Decodes a single value as `Decimal` for the field in a row.
71#[cfg(not(any(
72    feature = "orm-mariadb",
73    feature = "orm-mysql",
74    feature = "orm-postgres",
75    feature = "orm-tidb"
76)))]
77#[inline]
78pub fn decode_decimal(row: &DatabaseRow, field: &str) -> Result<Decimal, Error> {
79    let Some(value) = decode_optional::<String>(row, field)? else {
80        return Ok(Decimal::ZERO);
81    };
82    value
83        .parse()
84        .map_err(|err| warn!("fail to decode the `{}` field: {}", field, err))
85}
86
87/// Decodes a single value as `Uuid` for the field in a row.
88#[cfg(feature = "orm-postgres")]
89#[inline]
90pub fn decode_uuid(row: &DatabaseRow, field: &str) -> Result<Uuid, Error> {
91    match row.try_get_raw(field) {
92        Ok(value) => {
93            if value.is_null() {
94                Ok(Uuid::nil())
95            } else {
96                let id = decode_raw(field, value)?;
97                Ok(id)
98            }
99        }
100        Err(err) => {
101            if let sqlx::Error::ColumnNotFound(_) = err {
102                Ok(Uuid::nil())
103            } else {
104                Err(warn!("fail to get the `{}` field: {}", field, err))
105            }
106        }
107    }
108}
109
110/// Decodes a single value as `Uuid` for the field in a row.
111#[cfg(not(feature = "orm-postgres"))]
112#[inline]
113pub fn decode_uuid(row: &DatabaseRow, field: &str) -> Result<Uuid, Error> {
114    let Some(value) = decode_optional::<String>(row, field)? else {
115        return Ok(Uuid::nil());
116    };
117    value
118        .parse()
119        .map_err(|err| warn!("fail to decode the `{}` field: {}", field, err))
120}
121
122/// Decodes a single value as `Vec<T>` for the field in a row.
123#[cfg(feature = "orm-postgres")]
124#[inline]
125pub fn decode_array<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<Vec<T>, Error>
126where
127    T: for<'a> Decode<'a, DatabaseDriver> + sqlx::Type<DatabaseDriver>,
128{
129    match row.try_get_raw(field) {
130        Ok(value) => {
131            if value.is_null() {
132                warn!("decode the NULL as an empty array");
133                Ok(Vec::new())
134            } else {
135                let vec = decode_raw(field, value)?;
136                Ok(vec)
137            }
138        }
139        Err(err) => {
140            if let sqlx::Error::ColumnNotFound(_) = err {
141                Ok(Vec::new())
142            } else {
143                Err(warn!("fail to get the `{}` field: {}", field, err))
144            }
145        }
146    }
147}
148
149/// Decodes a single value as `Vec<T>` for the field in a row.
150#[cfg(feature = "orm-mariadb")]
151#[inline]
152pub fn decode_array<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<Vec<T>, Error>
153where
154    T: Decode<'r, DatabaseDriver> + serde::de::DeserializeOwned,
155{
156    let Some(value) = decode_optional::<String>(row, field)? else {
157        return Ok(Vec::new());
158    };
159    if value.starts_with('[') && value.ends_with(']') {
160        serde_json::from_str(&value)
161            .map_err(|err| warn!("fail to decode the `{}` field: {}", field, err))
162    } else {
163        zino_core::bail!("invalid array data for the `{}` field", field);
164    }
165}
166
167/// Decodes a single value as `Vec<T>` for the field in a row.
168#[cfg(not(any(feature = "orm-mariadb", feature = "orm-postgres")))]
169#[inline]
170pub fn decode_array<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<Vec<T>, Error>
171where
172    T: Decode<'r, DatabaseDriver> + std::str::FromStr,
173    <T as std::str::FromStr>::Err: std::error::Error + Send + 'static,
174{
175    use zino_core::{JsonValue, extension::JsonValueExt};
176
177    let Some(value) = decode_optional::<JsonValue>(row, field)? else {
178        return Ok(Vec::new());
179    };
180    if let Some(result) = value.parse_array() {
181        result.map_err(|err| warn!("fail to decode the `{}` field: {}", field, err))
182    } else {
183        Ok(Vec::new())
184    }
185}
186
187/// Decodes a raw value at the index.
188#[inline]
189pub(super) fn decode_raw<'r, T>(
190    field: &str,
191    value: <DatabaseDriver as Database>::ValueRef<'r>,
192) -> Result<T, sqlx::Error>
193where
194    T: Decode<'r, DatabaseDriver>,
195{
196    T::decode(value).map_err(|source| {
197        tracing::error!("fail to decode the `{}` field", field);
198        sqlx::Error::ColumnDecode {
199            index: field.to_owned(),
200            source,
201        }
202    })
203}