sqlx_core/
type_checking.rs

1use crate::database::Database;
2use crate::decode::Decode;
3use crate::type_info::TypeInfo;
4use crate::value::Value;
5use std::any::Any;
6use std::fmt;
7use std::fmt::{Debug, Formatter};
8
9/// The type of query parameter checking done by a SQL database.
10#[derive(PartialEq, Eq)]
11pub enum ParamChecking {
12    /// Parameter checking is weak or nonexistent (uses coercion or allows mismatches).
13    Weak,
14    /// Parameter checking is strong (types must match exactly).
15    Strong,
16}
17
18/// Type-checking extensions for the `Database` trait.
19///
20/// Mostly supporting code for the macros, and for `Debug` impls.
21pub trait TypeChecking: Database {
22    /// Describes how the database in question typechecks query parameters.
23    const PARAM_CHECKING: ParamChecking;
24
25    /// Get the full path of the Rust type that corresponds to the given `TypeInfo`, if applicable.
26    ///
27    /// If the type has a borrowed equivalent suitable for query parameters,
28    /// this is that borrowed type.
29    fn param_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
30
31    /// Get the full path of the Rust type that corresponds to the given `TypeInfo`, if applicable.
32    ///
33    /// Always returns the owned version of the type, suitable for decoding from `Row`.
34    fn return_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
35
36    /// Get the name of the Cargo feature gate that must be enabled to process the given `TypeInfo`,
37    /// if applicable.
38    fn get_feature_gate(info: &Self::TypeInfo) -> Option<&'static str>;
39
40    /// If `value` is a well-known type, decode and format it using `Debug`.
41    ///
42    /// If `value` is not a well-known type or could not be decoded, the reason is printed instead.
43    fn fmt_value_debug(value: &<Self as Database>::Value) -> FmtValue<'_, Self>;
44}
45
46/// An adapter for [`Value`] which attempts to decode the value and format it when printed using [`Debug`].
47pub struct FmtValue<'v, DB>
48where
49    DB: Database,
50{
51    value: &'v <DB as Database>::Value,
52    fmt: fn(&'v <DB as Database>::Value, &mut Formatter<'_>) -> fmt::Result,
53}
54
55impl<'v, DB> FmtValue<'v, DB>
56where
57    DB: Database,
58{
59    // This API can't take `ValueRef` directly as it would need to pass it to `Decode` by-value,
60    // which means taking ownership of it. We cannot rely on a `Clone` impl because `SqliteValueRef` doesn't have one.
61    /// When printed with [`Debug`], attempt to decode `value` as the given type `T` and format it using [`Debug`].
62    ///
63    /// If `value` could not be decoded as `T`, the reason is printed instead.
64    pub fn debug<T>(value: &'v <DB as Database>::Value) -> Self
65    where
66        T: Decode<'v, DB> + Debug + Any,
67    {
68        Self {
69            value,
70            fmt: |value, f| {
71                let info = value.type_info();
72
73                match T::decode(value.as_ref()) {
74                    Ok(value) => Debug::fmt(&value, f),
75                    Err(e) => {
76                        if e.is::<crate::error::UnexpectedNullError>() {
77                            f.write_str("NULL")
78                        } else {
79                            f.write_fmt(format_args!(
80                                "(error decoding SQL type {} as {}: {e:?})",
81                                info.name(),
82                                std::any::type_name::<T>()
83                            ))
84                        }
85                    }
86                }
87            },
88        }
89    }
90
91    /// If the type to be decoded is not known or not supported, print the SQL type instead,
92    /// as well as any applicable SQLx feature that needs to be enabled.
93    pub fn unknown(value: &'v <DB as Database>::Value) -> Self
94    where
95        DB: TypeChecking,
96    {
97        Self {
98            value,
99            fmt: |value, f| {
100                let info = value.type_info();
101
102                if let Some(feature_gate) = <DB as TypeChecking>::get_feature_gate(&info) {
103                    return f.write_fmt(format_args!(
104                        "(unknown SQL type {}: SQLx feature {feature_gate} not enabled)",
105                        info.name()
106                    ));
107                }
108
109                f.write_fmt(format_args!("(unknown SQL type {})", info.name()))
110            },
111        }
112    }
113}
114
115impl<'v, DB> Debug for FmtValue<'v, DB>
116where
117    DB: Database,
118{
119    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
120        (self.fmt)(self.value, f)
121    }
122}
123
124#[doc(hidden)]
125#[macro_export]
126macro_rules! select_input_type {
127    ($ty:ty, $input:ty) => {
128        stringify!($input)
129    };
130    ($ty:ty) => {
131        stringify!($ty)
132    };
133}
134
135#[macro_export]
136macro_rules! impl_type_checking {
137    (
138        $database:path {
139            $($(#[$meta:meta])? $ty:ty $(| $input:ty)?),*$(,)?
140        },
141        ParamChecking::$param_checking:ident,
142        feature-types: $ty_info:ident => $get_gate:expr,
143    ) => {
144        impl $crate::type_checking::TypeChecking for $database {
145            const PARAM_CHECKING: $crate::type_checking::ParamChecking = $crate::type_checking::ParamChecking::$param_checking;
146
147            fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
148                match () {
149                    $(
150                        $(#[$meta])?
151                        _ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some($crate::select_input_type!($ty $(, $input)?)),
152                    )*
153                    $(
154                        $(#[$meta])?
155                        _ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some($crate::select_input_type!($ty $(, $input)?)),
156                    )*
157                    _ => None
158                }
159            }
160
161            fn return_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
162                match () {
163                    $(
164                        $(#[$meta])?
165                        _ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some(stringify!($ty)),
166                    )*
167                    $(
168                        $(#[$meta])?
169                        _ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some(stringify!($ty)),
170                    )*
171                    _ => None
172                }
173            }
174
175            fn get_feature_gate($ty_info: &Self::TypeInfo) -> Option<&'static str> {
176                $get_gate
177            }
178
179            fn fmt_value_debug(value: &Self::Value) -> $crate::type_checking::FmtValue<Self> {
180                use $crate::value::Value;
181
182                let info = value.type_info();
183
184                match () {
185                    $(
186                        $(#[$meta])?
187                        _ if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) => $crate::type_checking::FmtValue::debug::<$ty>(value),
188                    )*
189                    _ => $crate::type_checking::FmtValue::unknown(value),
190                }
191            }
192        }
193    };
194}