Skip to content

Commit 3bec3f0

Browse files
committed
refactor: lift type mappings into driver crates
Motivated by #2917
1 parent 936744d commit 3bec3f0

File tree

12 files changed

+308
-150
lines changed

12 files changed

+308
-150
lines changed

sqlx-core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub mod raw_sql;
7979
pub mod row;
8080
pub mod rt;
8181
pub mod sync;
82+
pub mod type_checking;
8283
pub mod type_info;
8384
pub mod value;
8485

sqlx-core/src/type_checking.rs

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use crate::database::Database;
2+
use crate::decode::Decode;
3+
use crate::type_info::TypeInfo;
4+
use crate::value::Value;
5+
use std::any::Any;
6+
use std::fmt;
7+
use std::fmt::{Debug, Formatter};
8+
9+
/// The type of query parameter checking done by a SQL database.
10+
#[derive(PartialEq, Eq)]
11+
pub 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.
21+
pub 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`].
47+
pub struct FmtValue<'v, DB>
48+
where
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+
55+
impl<'v, DB> FmtValue<'v, DB>
56+
where
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) => f.write_fmt(format_args!(
76+
"(error decoding SQL type {} as {}: {e:?})",
77+
info.name(),
78+
std::any::type_name::<T>()
79+
)),
80+
}
81+
},
82+
}
83+
}
84+
85+
/// If the type to be decoded is not known or not supported, print the SQL type instead,
86+
/// as well as any applicable SQLx feature that needs to be enabled.
87+
pub fn unknown(value: &'v <DB as Database>::Value) -> Self
88+
where
89+
DB: TypeChecking,
90+
{
91+
Self {
92+
value,
93+
fmt: |value, f| {
94+
let info = value.type_info();
95+
96+
if let Some(feature_gate) = <DB as TypeChecking>::get_feature_gate(&info) {
97+
return f.write_fmt(format_args!(
98+
"(unknown SQL type {}: SQLx feature {feature_gate} not enabled)",
99+
info.name()
100+
));
101+
}
102+
103+
f.write_fmt(format_args!("(unknown SQL type {})", info.name()))
104+
},
105+
}
106+
}
107+
}
108+
109+
impl<'v, DB> Debug for FmtValue<'v, DB>
110+
where
111+
DB: Database,
112+
{
113+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114+
(self.fmt)(&self.value, f)
115+
}
116+
}
117+
118+
#[doc(hidden)]
119+
#[macro_export]
120+
macro_rules! select_input_type {
121+
($ty:ty, $input:ty) => {
122+
stringify!($input)
123+
};
124+
($ty:ty) => {
125+
stringify!($ty)
126+
};
127+
}
128+
129+
#[macro_export]
130+
macro_rules! impl_type_checking {
131+
(
132+
$database:path {
133+
$($(#[$meta:meta])? $ty:ty $(| $input:ty)?),*$(,)?
134+
},
135+
ParamChecking::$param_checking:ident,
136+
feature-types: $ty_info:ident => $get_gate:expr,
137+
) => {
138+
impl $crate::type_checking::TypeChecking for $database {
139+
const PARAM_CHECKING: $crate::type_checking::ParamChecking = $crate::type_checking::ParamChecking::$param_checking;
140+
141+
fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
142+
match () {
143+
$(
144+
$(#[$meta])?
145+
_ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some($crate::select_input_type!($ty $(, $input)?)),
146+
)*
147+
$(
148+
$(#[$meta])?
149+
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some(select_input_type!($ty $(, $input)?)),
150+
)*
151+
_ => None
152+
}
153+
}
154+
155+
fn return_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
156+
match () {
157+
$(
158+
$(#[$meta])?
159+
_ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some(stringify!($ty)),
160+
)*
161+
$(
162+
$(#[$meta])?
163+
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some(stringify!($ty)),
164+
)*
165+
_ => None
166+
}
167+
}
168+
169+
fn get_feature_gate($ty_info: &Self::TypeInfo) -> Option<&'static str> {
170+
$get_gate
171+
}
172+
173+
fn fmt_value_debug(value: &Self::Value) -> $crate::type_checking::FmtValue<Self> {
174+
use $crate::value::Value;
175+
176+
let info = value.type_info();
177+
178+
match () {
179+
$(
180+
$(#[$meta])?
181+
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) => $crate::type_checking::FmtValue::debug::<$ty>(value),
182+
)*
183+
_ => $crate::type_checking::FmtValue::unknown(value),
184+
}
185+
}
186+
}
187+
};
188+
}
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
macro_rules! impl_database_ext {
2+
(
3+
$database:path,
4+
row: $row:path,
5+
$(describe-blocking: $describe:path,)?
6+
) => {
7+
impl $crate::database::DatabaseExt for $database {
8+
const DATABASE_PATH: &'static str = stringify!($database);
9+
const ROW_PATH: &'static str = stringify!($row);
10+
impl_describe_blocking!($database, $($describe)?);
11+
}
12+
}
13+
}
14+
15+
macro_rules! impl_describe_blocking {
16+
($database:path $(,)?) => {
17+
fn describe_blocking(
18+
query: &str,
19+
database_url: &str,
20+
) -> sqlx_core::Result<sqlx_core::describe::Describe<Self>> {
21+
use $crate::database::CachingDescribeBlocking;
22+
23+
// This can't be a provided method because the `static` can't reference `Self`.
24+
static CACHE: CachingDescribeBlocking<$database> = CachingDescribeBlocking::new();
25+
26+
CACHE.describe(query, database_url)
27+
}
28+
};
29+
($database:path, $describe:path) => {
30+
fn describe_blocking(
31+
query: &str,
32+
database_url: &str,
33+
) -> sqlx_core::Result<sqlx_core::describe::Describe<Self>> {
34+
$describe(query, database_url)
35+
}
36+
};
37+
}
38+
39+
// The paths below will also be emitted from the macros, so they need to match the final facade.
40+
mod sqlx {
41+
#[cfg(feature = "mysql")]
42+
pub use sqlx_mysql as mysql;
43+
44+
#[cfg(feature = "postgres")]
45+
pub use sqlx_postgres as postgres;
46+
47+
#[cfg(feature = "sqlite")]
48+
pub use sqlx_sqlite as sqlite;
49+
}
50+
51+
// NOTE: type mappings have been moved to `src/type_checking.rs` in their respective driver crates.
52+
#[cfg(feature = "mysql")]
53+
impl_database_ext! {
54+
sqlx::mysql::MySql,
55+
row: sqlx::mysql::MySqlRow,
56+
}
57+
58+
#[cfg(feature = "postgres")]
59+
impl_database_ext! {
60+
sqlx::postgres::Postgres,
61+
row: sqlx::postgres::PgRow,
62+
}
63+
64+
#[cfg(feature = "sqlite")]
65+
impl_database_ext! {
66+
sqlx::sqlite::Sqlite,
67+
row: sqlx::sqlite::SqliteRow,
68+
// Since proc-macros don't benefit from async, we can make a describe call directly
69+
// which also ensures that the database is closed afterwards, regardless of errors.
70+
describe-blocking: sqlx_sqlite::describe_blocking,
71+
}

0 commit comments

Comments
 (0)