Skip to content

Commit 40f7b38

Browse files
committed
feat(error): add MismatchedTypeError for improved type mismatch handling
This commit introduces a new error type, MismatchedTypeError, to provide detailed information when a Rust type is incompatible with a SQL type. The error includes fields for the Rust type, its SQL representation, the actual SQL type, and an optional source error. Additionally, the mismatched_types function is updated to utilize this new error type, enhancing error reporting in type decoding scenarios. The Any module is also refactored to remove the obsolete error handling code.
1 parent 1cf7da5 commit 40f7b38

File tree

16 files changed

+169
-164
lines changed

16 files changed

+169
-164
lines changed

sqlx-core/src/any/error.rs

Lines changed: 0 additions & 16 deletions
This file was deleted.

sqlx-core/src/any/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ mod arguments;
1818
pub(crate) mod column;
1919
mod connection;
2020
mod database;
21-
mod error;
2221
mod kind;
2322
mod options;
2423
mod query_result;

sqlx-core/src/any/row.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use crate::any::error::mismatched_types;
21
use crate::any::{Any, AnyColumn, AnyColumnIndex};
32
use crate::column::ColumnIndex;
43
use crate::database::HasValueRef;
54
use crate::decode::Decode;
5+
use crate::error::mismatched_types;
66
use crate::error::Error;
77
use crate::row::Row;
88
use crate::type_info::TypeInfo;
@@ -91,7 +91,7 @@ impl Row for AnyRow {
9191
let ty = value.type_info();
9292

9393
if !value.is_null() && !ty.is_null() && !T::compatible(&ty) {
94-
Err(mismatched_types::<T>(&ty))
94+
Err(mismatched_types::<Self::Database, T>(&ty))
9595
} else {
9696
T::decode(value)
9797
}

sqlx-core/src/any/value.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use std::borrow::Cow;
22

3-
use crate::any::error::mismatched_types;
43
use crate::any::{Any, AnyTypeInfo};
54
use crate::database::HasValueRef;
65
use crate::decode::Decode;
7-
use crate::error::Error;
6+
use crate::error::mismatched_types;
87
use crate::type_info::TypeInfo;
98
use crate::types::Type;
109
use crate::value::{Value, ValueRef};
@@ -113,15 +112,18 @@ impl Value for AnyValue {
113112
}
114113
}
115114

116-
fn try_decode<'r, T>(&'r self) -> Result<T, Error>
115+
fn try_decode<'r, T>(&'r self) -> crate::error::Result<T>
117116
where
118117
T: Decode<'r, Self::Database> + Type<Self::Database>,
119118
{
120119
if !self.is_null() {
121120
let ty = self.type_info();
122121

123122
if !ty.is_null() && !T::compatible(&ty) {
124-
return Err(Error::Decode(mismatched_types::<T>(&ty)));
123+
return Err(crate::error::Error::Decode(mismatched_types::<
124+
Self::Database,
125+
T,
126+
>(&ty)));
125127
}
126128
}
127129

sqlx-core/src/error.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,43 @@ pub type BoxDynError = Box<dyn StdError + 'static + Send + Sync>;
2626
#[error("unexpected null; try decoding as an `Option`")]
2727
pub struct UnexpectedNullError;
2828

29+
/// Error indicating that a Rust type is not compatible with a SQL type.
30+
#[derive(thiserror::Error, Debug)]
31+
#[error("mismatched types; Rust type `{rust_type}` (as SQL type `{rust_sql_type}`) could not be decoded into SQL type `{sql_type}`")]
32+
pub struct MismatchedTypeError {
33+
/// The name of the Rust type.
34+
pub rust_type: String,
35+
/// The SQL type name that the Rust type would map to.
36+
pub rust_sql_type: String,
37+
/// The actual SQL type from the database.
38+
pub sql_type: String,
39+
/// Optional source error that caused the mismatch.
40+
#[source]
41+
pub source: Option<BoxDynError>,
42+
}
43+
44+
impl MismatchedTypeError {
45+
/// Create a new mismatched type error without a source.
46+
pub fn new<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> Self {
47+
Self {
48+
rust_type: type_name::<T>().to_string(),
49+
rust_sql_type: T::type_info().name().to_string(),
50+
sql_type: ty.name().to_string(),
51+
source: None,
52+
}
53+
}
54+
55+
/// Create a new mismatched type error with a source error.
56+
pub fn with_source<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo, source: BoxDynError) -> Self {
57+
Self {
58+
rust_type: type_name::<T>().to_string(),
59+
rust_sql_type: T::type_info().name().to_string(),
60+
sql_type: ty.name().to_string(),
61+
source: Some(source),
62+
}
63+
}
64+
}
65+
2966
/// Represents all the ways a method can fail within SQLx.
3067
#[derive(Debug, thiserror::Error)]
3168
#[non_exhaustive]
@@ -145,23 +182,17 @@ impl Error {
145182
}
146183

147184
pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
148-
// TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)`
149-
if T::compatible(ty) {
150-
format!(
151-
"mismatched types; Rust type `{}` (as SQL type `{}`) is compatible with SQL type `{}` but decoding failed",
152-
type_name::<T>(),
153-
T::type_info().name(),
154-
ty.name()
155-
)
156-
} else {
157-
format!(
158-
"mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`",
185+
Box::new(MismatchedTypeError {
186+
rust_type: format!(
187+
"{} ({}compatible with SQL type `{}`)",
159188
type_name::<T>(),
160-
T::type_info().name(),
161-
ty.name()
162-
)
163-
}
164-
.into()
189+
if T::compatible(ty) { "" } else { "in" },
190+
T::type_info().name()
191+
),
192+
rust_sql_type: T::type_info().name().to_string(),
193+
sql_type: ty.name().to_string(),
194+
source: None,
195+
})
165196
}
166197

167198
/// An error that was returned from the database.

sqlx-core/src/odbc/connection/odbc_bridge.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -459,17 +459,17 @@ where
459459
fn push_binary(
460460
cursor_row: &mut odbc_api::CursorRow<'_>,
461461
col_index: u16,
462-
vec: &mut Vec<Option<Vec<u8>>>,
462+
vec: &mut Vec<Vec<u8>>,
463463
nulls: &mut Vec<bool>,
464464
) {
465465
let mut buf = Vec::new();
466466
match cursor_row.get_text(col_index, &mut buf) {
467467
Ok(true) => {
468-
vec.push(Some(buf));
468+
vec.push(buf);
469469
nulls.push(false);
470470
}
471471
Ok(false) | Err(_) => {
472-
vec.push(None);
472+
vec.push(Vec::new());
473473
nulls.push(true);
474474
}
475475
}
@@ -478,21 +478,13 @@ where
478478
fn push_text(
479479
cursor_row: &mut odbc_api::CursorRow<'_>,
480480
col_index: u16,
481-
vec: &mut Vec<Option<String>>,
481+
vec: &mut Vec<String>,
482482
nulls: &mut Vec<bool>,
483483
) {
484-
let mut buf = Vec::new();
485-
match cursor_row.get_text(col_index, &mut buf) {
486-
Ok(true) => {
487-
let s = String::from_utf8_lossy(&buf).to_string();
488-
vec.push(Some(s));
489-
nulls.push(false);
490-
}
491-
Ok(false) | Err(_) => {
492-
vec.push(None);
493-
nulls.push(true);
494-
}
495-
}
484+
let mut buf = Vec::<u16>::new();
485+
let txt = cursor_row.get_wide_text(col_index, &mut buf);
486+
vec.push(String::from_utf16_lossy(&buf).to_string());
487+
nulls.push(!txt.unwrap_or(false));
496488
}
497489

498490
fn push_from_cursor_row(

sqlx-core/src/odbc/row.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ mod tests {
113113
nulls: vec![false],
114114
}),
115115
Arc::new(ColumnData {
116-
values: OdbcValueVec::Text(vec![Some("test".to_string())]),
116+
values: OdbcValueVec::Text(vec!["test".to_string()]),
117117
type_info: OdbcTypeInfo::new(DataType::Varchar { length: None }),
118118
nulls: vec![false],
119119
}),

sqlx-core/src/odbc/types/bool.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ mod tests {
115115
}
116116

117117
fn create_test_value_text(text: &'static str, data_type: DataType) -> OdbcValueRef<'static> {
118-
make_ref(OdbcValueVec::Text(vec![Some(text.to_string())]), data_type)
118+
make_ref(OdbcValueVec::Text(vec![text.to_string()]), data_type)
119119
}
120120

121121
fn create_test_value_int(value: i64, data_type: DataType) -> OdbcValueRef<'static> {
@@ -316,7 +316,7 @@ mod tests {
316316
#[test]
317317
fn test_bool_decode_error_handling() {
318318
let column = ColumnData {
319-
values: OdbcValueVec::Text(vec![Some("not_a_bool".to_string())]),
319+
values: OdbcValueVec::Text(vec!["not_a_bool".to_string()]),
320320
type_info: OdbcTypeInfo::BIT,
321321
nulls: vec![false],
322322
};

sqlx-core/src/odbc/types/bytes.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ mod tests {
9696
}
9797

9898
fn create_test_value_text(text: &'static str, data_type: DataType) -> OdbcValueRef<'static> {
99-
make_ref(OdbcValueVec::Text(vec![Some(text.to_string())]), data_type)
99+
make_ref(OdbcValueVec::Text(vec![text.to_string()]), data_type)
100100
}
101101

102102
fn create_test_value_blob(data: &'static [u8], data_type: DataType) -> OdbcValueRef<'static> {
103-
make_ref(OdbcValueVec::Binary(vec![Some(data.to_vec())]), data_type)
103+
make_ref(OdbcValueVec::Binary(vec![data.to_vec()]), data_type)
104104
}
105105

106106
#[test]
@@ -196,7 +196,7 @@ mod tests {
196196
#[test]
197197
fn test_decode_error_handling() {
198198
let column = ColumnData {
199-
values: OdbcValueVec::Text(vec![Some("not_bytes".to_string())]),
199+
values: OdbcValueVec::Text(vec!["not_bytes".to_string()]),
200200
type_info: OdbcTypeInfo::varbinary(None),
201201
nulls: vec![false],
202202
};

sqlx-core/src/odbc/types/chrono.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ mod tests {
489489
}
490490

491491
fn create_test_value_text(text: &'static str, data_type: DataType) -> OdbcValueRef<'static> {
492-
make_ref(OdbcValueVec::Text(vec![Some(text.to_string())]), data_type)
492+
make_ref(OdbcValueVec::Text(vec![text.to_string()]), data_type)
493493
}
494494

495495
fn create_test_value_int(value: i64, data_type: DataType) -> OdbcValueRef<'static> {
@@ -501,7 +501,7 @@ mod tests {
501501
}
502502

503503
fn create_test_value_blob(data: &'static [u8], data_type: DataType) -> OdbcValueRef<'static> {
504-
make_ref(OdbcValueVec::Binary(vec![Some(data.to_vec())]), data_type)
504+
make_ref(OdbcValueVec::Binary(vec![data.to_vec()]), data_type)
505505
}
506506

507507
#[test]
@@ -636,7 +636,7 @@ mod tests {
636636

637637
// From empty
638638
let column = ColumnData {
639-
values: OdbcValueVec::Text(vec![None]),
639+
values: OdbcValueVec::Text(vec![String::new()]),
640640
type_info: OdbcTypeInfo::new(DataType::Date),
641641
nulls: vec![true],
642642
};

0 commit comments

Comments
 (0)