sqlx_d1_core/
types.rs

1//! ref: <https://github.com/launchbadge/sqlx/blob/277dd36c7868acb10eae20f50418e273b71c8499/sqlx-sqlite/src/types/chrono.rs>
2
3use crate::{D1, type_info::D1TypeInfo, value::D1Value, error::D1Error};
4use sqlx_core::encode::{IsNull, Encode};
5use sqlx_core::decode::Decode;
6use sqlx_core::types::Type;
7use worker::{serde_wasm_bindgen, wasm_bindgen::JsValue};
8
9
10//////////////////////////////////////////////////////////////////////////////////
11/// compile-time compatibility check support for `sqlx::query_*!` macro's internal
12//////////////////////////////////////////////////////////////////////////////////
13
14#[doc(hidden)]
15pub trait Compatible<X: TypeChecker>: Sized {
16    fn then(self) -> Self {self}
17}
18impl<X: TypeChecker, C: Compatible<X>> Compatible<Option<X>> for Option<C> {}
19
20#[doc(hidden)]
21pub trait TypeChecker {
22    const TYPE_INFO: D1TypeInfo;
23}
24impl<X: TypeChecker> TypeChecker for Option<X> {
25    const TYPE_INFO: D1TypeInfo = X::TYPE_INFO;
26}
27const _: () = {
28    impl TypeChecker for bool {
29        const TYPE_INFO: D1TypeInfo = D1TypeInfo::boolean();
30    }
31    impl TypeChecker for i64 {
32        const TYPE_INFO: D1TypeInfo = D1TypeInfo::integer();
33    }
34    impl TypeChecker for f64 {
35        const TYPE_INFO: D1TypeInfo = D1TypeInfo::real();
36    }
37    impl TypeChecker for String {
38        const TYPE_INFO: D1TypeInfo = D1TypeInfo::text();
39    }
40    impl TypeChecker for Vec<u8> {
41        const TYPE_INFO: D1TypeInfo = D1TypeInfo::blob();
42    }
43    #[cfg(feature = "chrono")]
44    impl TypeChecker for sqlx_core::types::chrono::NaiveDate {
45        const TYPE_INFO: D1TypeInfo = D1TypeInfo::date();
46    }
47    #[cfg(feature = "chrono")]
48    impl TypeChecker for sqlx_core::types::chrono::NaiveTime {
49        const TYPE_INFO: D1TypeInfo = D1TypeInfo::time();
50    }
51    #[cfg(feature = "chrono")]
52    impl TypeChecker for sqlx_core::types::chrono::NaiveDateTime {
53        const TYPE_INFO: D1TypeInfo = D1TypeInfo::datetime();
54    }
55};
56
57/* ref: <https://github.com/launchbadge/sqlx/blob/277dd36c7868acb10eae20f50418e273b71c8499/sqlx-sqlite/src/type_checking.rs> */
58sqlx_core::impl_type_checking! {
59    crate::D1 {
60        // BOOLEAN,
61        bool,
62        // INTEGER,
63        i64,
64        // REAL,
65        f64,
66        // TEXT,
67        String,
68        // BLOB,
69        Vec<u8>,
70        // DATE,
71        #[cfg(feature = "chrono")]
72        sqlx_core::types::chrono::NaiveDate,
73        // TIME,
74        #[cfg(feature = "chrono")]
75        sqlx_core::types::chrono::NaiveTime,
76        // DATETIME,
77        #[cfg(feature = "chrono")]
78        sqlx_core::types::chrono::NaiveDateTime,
79    },
80    ParamChecking::Weak,
81    feature-types: _info => None,
82}
83
84
85///////////////////////////////////////////////////////////////////////////////////
86/// `Type`, `Encode`, `Decode` implementations for specific types
87///////////////////////////////////////////////////////////////////////////////////
88
89impl<'q, E: Encode<'q, D1>> Encode<'q, D1> for Option<E> {
90    fn encode_by_ref(
91        &self,
92        buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
93    ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
94        match self {
95            Some(e) => {
96                <E as Encode<'q, D1>>::encode_by_ref(e, buf)
97            }
98            None => {
99                buf.push(D1Value::null());
100                Ok(IsNull::Yes)
101            }
102        }
103    }
104}
105
106macro_rules! serialize {
107    ($q:lifetime) => {
108        fn encode_by_ref(
109            &self,
110            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<$q>,
111        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
112            buf.push(D1Value::from(serde_wasm_bindgen::to_value(self).map_err(D1Error::from_rust)?));
113            Ok(IsNull::No)            
114        }
115    };
116}
117
118macro_rules! deserialize {
119    () => {
120        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
121            Ok(serde_wasm_bindgen::from_value(value.into()).map_err(D1Error::from_rust)?)
122        }
123    };
124}
125
126macro_rules! serde_wasm_bindgen {
127    ($T:ty where $type_cheker:ty) => {
128        impl Compatible<$type_cheker> for $T {}
129
130        impl Type<D1> for $T {
131            fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
132                <$type_cheker as TypeChecker>::TYPE_INFO
133            }
134        }
135
136        impl<'q> Encode<'q, D1> for $T {
137            serialize!('q);
138        }
139
140        impl Decode<'_, D1> for $T {
141            deserialize!();
142        }
143    };
144}
145
146impl Type<D1> for [u8] {
147    fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
148        D1TypeInfo::blob()
149    }
150}
151impl<'q> Encode<'q, D1> for &'q [u8] {
152    serialize!('q);
153}
154
155serde_wasm_bindgen!(Vec<u8> where Vec<u8>);
156serde_wasm_bindgen!(Box<[u8]> where Vec<u8>);
157
158serde_wasm_bindgen!(f32 where f64);
159serde_wasm_bindgen!(f64 where f64);
160
161serde_wasm_bindgen!(i8 where i64);
162serde_wasm_bindgen!(i16 where i64);
163serde_wasm_bindgen!(i32 where i64);
164serde_wasm_bindgen!(i64 where i64);
165serde_wasm_bindgen!(isize where i64);
166
167serde_wasm_bindgen!(u8 where i64);
168serde_wasm_bindgen!(u16 where i64);
169serde_wasm_bindgen!(u32 where i64);
170serde_wasm_bindgen!(u64 where i64);
171serde_wasm_bindgen!(usize where i64);
172
173impl Type<D1> for str {
174    fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
175        D1TypeInfo::blob()
176    }
177}
178impl<'q> Encode<'q, D1> for &'q str {
179    serialize!('q);
180}
181
182serde_wasm_bindgen!(Box<str> where String);
183serde_wasm_bindgen!(String where String);
184serde_wasm_bindgen!(std::borrow::Cow<'_, str> where String);
185
186/// specialized conversion: true <-> 1 / false <-> 0
187const _: (/* bool */) = {
188    impl Compatible<bool> for bool {}
189
190    impl Type<D1> for bool {
191        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
192            D1TypeInfo::boolean()
193        }
194    }
195
196    impl<'q> Encode<'q, D1> for bool {
197        fn encode_by_ref(
198            &self,
199            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
200        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
201            buf.push(D1Value::from(JsValue::from_f64(if *self {1.} else {0.})));
202            Ok(IsNull::No)
203        }
204    }
205
206    impl Decode<'_, D1> for bool {
207        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
208            Ok((&*value).as_f64().is_some_and(|n| n != 0.))
209        }
210    }
211};
212
213/// ref: <https://github.com/launchbadge/sqlx/blob/d4ae6ffd882ed2de1695c652888d809bc068554e/sqlx-sqlite/src/types/text.rs>
214const _: (/* generics text */) = {
215    use sqlx_core::types::Text;
216
217    impl<C: TypeChecker, T> Compatible<C> for Text<T>
218    where
219        String: Compatible<C>,
220    {}
221
222    impl<T> Type<D1> for Text<T> {
223        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
224            <String as Type<D1>>::type_info()
225        }
226    }
227
228    impl<'q, T> Encode<'q, D1> for Text<T>
229    where
230        T: std::fmt::Display,
231    {
232        fn encode_by_ref(
233            &self,
234            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
235        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
236            <String as Encode<'q, D1>>::encode(self.0.to_string(), buf)
237        }
238    }
239
240    impl<T> Decode<'_, D1> for Text<T>
241    where
242        T: std::str::FromStr,
243        sqlx_core::error::BoxDynError: From<<T as std::str::FromStr>::Err>,
244    {
245        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
246            Ok(Self(<String as Decode<D1>>::decode(value)?.parse()?))
247        }
248    }
249};
250
251#[cfg(feature = "json")]
252/// ref: <https://github.com/launchbadge/sqlx/blob/d4ae6ffd882ed2de1695c652888d809bc068554e/sqlx-sqlite/src/types/json.rs>
253const _: (/* json */) = {
254    use sqlx_core::types::Json;
255
256    impl<C: TypeChecker, T> Compatible<C> for Json<T>
257    where
258        String: Compatible<C>,
259    {}
260
261    impl<T> Type<D1> for Json<T> {
262        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
263            <String as Type<D1>>::type_info()
264        }
265    }
266
267    impl<'q, T> Encode<'q, D1> for Json<T>
268    where
269        T: serde::Serialize,
270    {
271        fn encode_by_ref(
272            &self,
273            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
274        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
275            <String as Encode<'q, D1>>::encode(self.encode_to_string()?, buf)
276        }
277    }
278
279    impl<T> Decode<'_, D1> for Json<T>
280    where
281        T: for<'de> serde::Deserialize<'de>,
282    {
283        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
284            Self::decode_from_string(&<String as Decode<D1>>::decode(value)?)
285        }
286    }
287};
288
289#[cfg(feature = "uuid")]
290/// ref: <https://github.com/launchbadge/sqlx/blob/d4ae6ffd882ed2de1695c652888d809bc068554e/sqlx-sqlite/src/types/uuid.rs>
291const _: (/* uuid */) = {
292    use sqlx_core::types::uuid::{Uuid, fmt::{Hyphenated, Simple}};
293
294    impl<C: TypeChecker> Compatible<C> for Uuid
295    where
296        Vec<u8>: Compatible<C>,
297    {}
298    impl Type<D1> for Uuid {
299        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
300            <Vec<u8> as Type<D1>>::type_info()
301        }
302    }
303    impl<'q> Encode<'q, D1> for Uuid {
304        fn encode_by_ref(
305            &self,
306            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
307        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
308            <Vec<u8> as Encode<'q, D1>>::encode(self.into_bytes().into(), buf)
309        }
310    }
311    impl Decode<'_, D1> for Uuid {
312        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
313            Ok(Uuid::from_slice(&<Vec<u8> as Decode<D1>>::decode(value)?).map_err(D1Error::from_rust)?)
314        }
315    }
316
317    impl<C: TypeChecker> Compatible<C> for Hyphenated
318    where
319        String: Compatible<C>,
320    {}
321    impl Type<D1> for Hyphenated {
322        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
323            <String as Type<D1>>::type_info()
324        }
325    }
326    impl<'q> Encode<'q, D1> for Hyphenated {
327        fn encode_by_ref(
328            &self,
329            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
330        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
331            <String as Encode<'q, D1>>::encode(self.to_string(), buf)            
332        }
333    }
334    impl Decode<'_, D1> for Hyphenated {
335        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
336            let uuid = Uuid::parse_str(&<String as Decode<D1>>::decode(value)?).map_err(D1Error::from_rust)?;
337            Ok(uuid.hyphenated())
338        }
339    }
340
341    impl<C: TypeChecker> Compatible<C> for Simple
342    where
343        String: Compatible<C>,
344    {}
345    impl Type<D1> for Simple {
346        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
347            <String as Type<D1>>::type_info()
348        }
349    }
350    impl<'q> Encode<'q, D1> for Simple {
351        fn encode_by_ref(
352            &self,
353            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
354        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
355            <String as Encode<'q, D1>>::encode(self.to_string(), buf)            
356        }
357    }
358    impl Decode<'_, D1> for Simple {
359        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
360            let uuid = Uuid::parse_str(&<String as Decode<D1>>::decode(value)?).map_err(D1Error::from_rust)?;
361            Ok(uuid.simple())
362        }
363    }
364};
365
366#[cfg(feature = "chrono")]
367/// ref: <https://github.com/launchbadge/sqlx/blob/277dd36c7868acb10eae20f50418e273b71c8499/sqlx-sqlite/src/types/chrono.rs>
368const _: (/* chrono */) = {
369    use sqlx_core::types::chrono::{
370        FixedOffset,
371        DateTime,
372        Local,
373        NaiveDate,
374        NaiveTime,
375        NaiveDateTime,
376        TimeZone,
377        Utc,
378    };
379
380    impl<C: TypeChecker, Tz: TimeZone> Compatible<C> for DateTime<Tz>
381    where
382        NaiveDateTime: Compatible<C>,
383    {}
384    impl<Tz: TimeZone> Type<D1> for DateTime<Tz> {
385        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
386            <NaiveDateTime as Type<D1>>::type_info()
387        }
388        fn compatible(ty: &<D1 as sqlx_core::database::Database>::TypeInfo) -> bool {
389            <NaiveDateTime as Type<D1>>::compatible(ty)
390        }
391    }
392    impl<Tz: TimeZone> Encode<'_, D1> for DateTime<Tz> {
393        fn encode_by_ref(
394            &self,
395            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'_>,
396        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
397            let mut rfc3339 = self.to_rfc3339();
398            if rfc3339.ends_with('Z') {let _ = rfc3339.pop().unwrap();}
399            <String as Encode<'_, D1>>::encode(rfc3339, buf)
400        }
401    }
402    impl Decode<'_, D1> for DateTime<Utc> {
403        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
404            let fixed_offset = <DateTime<FixedOffset> as Decode<'_, D1>>::decode(value)?;
405            Ok(Utc.from_utc_datetime(&fixed_offset.naive_utc()))
406        }
407    }
408    impl Decode<'_, D1> for DateTime<Local> {
409        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
410            let fixed_offset = <DateTime<FixedOffset> as Decode<'_, D1>>::decode(value)?;
411            Ok(Local.from_utc_datetime(&fixed_offset.naive_utc()))
412        }
413    }
414    impl Decode<'_, D1> for DateTime<FixedOffset> {
415        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
416            return decode_or_none(&value).ok_or_else(|| From::from(format!(
417                "expected datetime but got unparsable `{value:?}`"
418            )));
419
420            fn decode_or_none(
421                value: &<D1 as sqlx_core::database::Database>::ValueRef<'_>
422            ) -> Option<DateTime<FixedOffset>> {
423                use {sqlx_core::value::ValueRef, crate::type_info::D1Type::*};
424
425                macro_rules! return_some_if_ok {
426                    ($result:expr) => {
427                        if let Ok(it) = $result {
428                            return Some(it);
429                        }
430                    };
431                    ($result:expr => |$v:ident| $conv:expr) => {
432                        if let Ok(it) = $result {
433                            return Some((|$v| $conv)(it));
434                        }
435                    };
436                }
437                
438                match &**value.type_info() {
439                    Text => {
440                        let value = value.as_string()?;
441                        return_some_if_ok!(DateTime::parse_from_rfc3339(&value));
442                        for format in &[
443                            "%F %T%.f",
444                            "%F %R",
445                            "%F %RZ",
446                            "%F %R%:z",
447                            "%F %T%.fZ",
448                            "%F %T%.f%:z",
449                            "%FT%R",
450                            "%FT%RZ",
451                            "%FT%R%:z",
452                            "%FT%T%.f",
453                            "%FT%T%.fZ",
454                            "%FT%T%.f%:z",
455                        ] {
456                            return_some_if_ok!(DateTime::parse_from_str(&value, format));
457                            return_some_if_ok!(NaiveDateTime::parse_from_str(&value, format)
458                                => |it| FixedOffset::east_opt(0).unwrap().from_utc_datetime(&it));
459                        }
460                        None
461                    }
462                    Integer => {
463                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
464                        let value = value.as_f64()? as i64;
465                        FixedOffset::east_opt(0).unwrap().timestamp_opt(value, 0).single()
466                    }
467                    Real => {
468                        let value = value.as_f64()?;
469
470                        let epoch_in_julian_days = 2_440_587.5;
471                        let seconds_in_day = 86400.0;
472                        let timestamp = (value - epoch_in_julian_days) * seconds_in_day;
473                    
474                        if !timestamp.is_finite() {
475                            return None;
476                        }
477                    
478                        // We don't really have a choice but to do lossy casts for this conversion
479                        // We checked above if the value is infinite or NaN which could otherwise cause problems
480                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
481                        {
482                            let seconds = timestamp.trunc() as i64;
483                            let nanos = (timestamp.fract() * 1E9).abs() as u32;
484                            FixedOffset::east_opt(0).unwrap().timestamp_opt(seconds, nanos).single()
485                        }
486                    }
487                    _ => None
488                }
489            }
490        }
491    }
492
493    impl Compatible<NaiveDateTime> for NaiveDateTime {}
494    impl Compatible<String> for NaiveDateTime {}
495    impl Compatible<i64> for NaiveDateTime {}
496    impl Compatible<f64> for NaiveDateTime {}
497    impl Type<D1> for NaiveDateTime {
498        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
499            D1TypeInfo::datetime()
500        }
501        fn compatible(ty: &<D1 as sqlx_core::database::Database>::TypeInfo) -> bool {
502            use crate::type_info::D1Type::*;
503            matches!(**ty, Datetime | Text | Integer | Real)
504        }
505    }
506    impl Encode<'_, D1> for NaiveDateTime {
507        fn encode_by_ref(
508            &self,
509            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'_>,
510        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
511            <String as Encode<'_, D1>>::encode(self.format("%F %T%.f").to_string(), buf)
512        }
513    }
514    impl Decode<'_, D1> for NaiveDateTime {
515        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
516            Ok(<DateTime<FixedOffset> as Decode<'_, D1>>::decode(value)?.naive_local())
517        }
518    }
519
520    impl Compatible<NaiveDate> for NaiveDate {}
521    impl Compatible<String> for NaiveDate {}
522    impl Type<D1> for NaiveDate {
523        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
524            D1TypeInfo::date()
525        }
526        fn compatible(ty: &<D1 as sqlx_core::database::Database>::TypeInfo) -> bool {
527            use crate::type_info::D1Type::*;
528            matches!(**ty, Date | Text)
529        }
530    }
531    impl Encode<'_, D1> for NaiveDate {
532        fn encode_by_ref(
533            &self,
534            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'_>,
535        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
536            <String as Encode<'_, D1>>::encode(self.format("%F").to_string(), buf)
537        }
538    }
539    impl Decode<'_, D1> for NaiveDate {
540        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
541            let value = value.as_string().ok_or_else(|| format!("expected `chrono::NaiveDate` but got unparsable: {value:?}"))?;
542            Ok(NaiveDate::parse_from_str(&value, "%F")?)
543        }
544    }
545
546    impl Compatible<NaiveTime> for NaiveTime {}
547    impl Compatible<String> for NaiveTime {}
548    impl Type<D1> for NaiveTime {
549        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
550            D1TypeInfo::time()
551        }
552        fn compatible(ty: &<D1 as sqlx_core::database::Database>::TypeInfo) -> bool {
553            use crate::type_info::D1Type::*;
554            matches!(**ty, Time | Text)
555        }
556    }
557    impl Encode<'_, D1> for NaiveTime {
558        fn encode_by_ref(
559            &self,
560            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'_>,
561        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
562            <String as Encode<'_, D1>>::encode(self.format("%T%.f").to_string(), buf)
563        }
564    }
565    impl Decode<'_, D1> for NaiveTime {
566        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
567            let value = value.as_string().ok_or_else(|| format!("expected `chrono::NaiveDate` but got unparsable: {value:?}"))?;
568            for format in [
569                "%T.f",
570                "%T%.f",
571                "%R",
572                "%RZ",
573                "%T%.fZ",
574                "%R%:z",
575                "%T%.f%:z",
576            ] {
577                if let Ok(t) = NaiveTime::parse_from_str(&value, format) {
578                    return Ok(t);
579                }
580            }
581            Err(From::from(format!("invalid time: {value:?}")))
582        }
583    }
584};
585
586#[cfg(feature = "decimal")]
587const _: (/* decimal */) = {
588    use rust_decimal::Decimal;
589    use std::str::FromStr;
590
591    impl<C: TypeChecker> Compatible<C> for Decimal where String: Compatible<C> {}
592
593    impl Type<D1> for Decimal {
594        fn type_info() -> <D1 as sqlx_core::database::Database>::TypeInfo {
595            // Store Decimal as TEXT
596            <String as Type<D1>>::type_info()
597        }
598        fn compatible(ty: &<D1 as sqlx_core::database::Database>::TypeInfo) -> bool {
599            <String as Type<D1>>::compatible(ty)
600        }
601    }
602
603    impl<'q> Encode<'q, D1> for Decimal {
604        fn encode_by_ref(
605            &self,
606            buf: &mut <D1 as sqlx_core::database::Database>::ArgumentBuffer<'q>,
607        ) -> Result<IsNull, sqlx_core::error::BoxDynError> {
608            <String as Encode<'q, D1>>::encode(self.to_string(), buf)
609        }
610    }
611
612    impl Decode<'_, D1> for Decimal {
613        fn decode(value: <D1 as sqlx_core::database::Database>::ValueRef<'_>) -> Result<Self, sqlx_core::error::BoxDynError> {
614            let s = <String as Decode<D1>>::decode(value)?;
615            Ok(Decimal::from_str(&s)?)
616        }
617    }
618};