sqlx_core/
type_checking.rs1use 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#[derive(PartialEq, Eq)]
11pub enum ParamChecking {
12 Weak,
14 Strong,
16}
17
18pub trait TypeChecking: Database {
22 const PARAM_CHECKING: ParamChecking;
24
25 fn param_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
30
31 fn return_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
35
36 fn get_feature_gate(info: &Self::TypeInfo) -> Option<&'static str>;
39
40 fn fmt_value_debug(value: &<Self as Database>::Value) -> FmtValue<'_, Self>;
44}
45
46pub 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 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 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}