diff --git a/sqlx-core/src/mysql/types/mod.rs b/sqlx-core/src/mysql/types/mod.rs index fbde8156..ceb8bfe2 100644 --- a/sqlx-core/src/mysql/types/mod.rs +++ b/sqlx-core/src/mysql/types/mod.rs @@ -95,6 +95,16 @@ impl MySqlTypeInfo { } } + #[doc(hidden)] + pub const fn r#enum() -> Self { + Self { + id: TypeId::ENUM, + is_unsigned: false, + is_binary: false, + char_set: 0, + } + } + pub(crate) fn from_nullable_column_def(def: &ColumnDefinition) -> Self { Self { id: def.type_id, @@ -157,6 +167,51 @@ impl Display for MySqlTypeInfo { } } +impl PartialEq for MySqlTypeInfo { + fn eq(&self, other: &MySqlTypeInfo) -> bool { + match self.id { + TypeId::VAR_CHAR + | TypeId::TEXT + | TypeId::CHAR + | TypeId::TINY_BLOB + | TypeId::MEDIUM_BLOB + | TypeId::LONG_BLOB + | TypeId::ENUM + if (self.is_binary == other.is_binary) + && match other.id { + TypeId::VAR_CHAR + | TypeId::TEXT + | TypeId::CHAR + | TypeId::TINY_BLOB + | TypeId::MEDIUM_BLOB + | TypeId::LONG_BLOB + | TypeId::ENUM => true, + + _ => false, + } => + { + return true; + } + + _ => {} + } + + if self.id.0 != other.id.0 { + return false; + } + + match self.id { + TypeId::TINY_INT | TypeId::SMALL_INT | TypeId::INT | TypeId::BIG_INT => { + return self.is_unsigned == other.is_unsigned; + } + + _ => {} + } + + true + } +} + impl TypeInfo for MySqlTypeInfo { fn compatible(&self, other: &Self) -> bool { // NOTE: MySQL is weakly typed so much of this may be surprising to a Rust developer. @@ -190,19 +245,45 @@ impl TypeInfo for MySqlTypeInfo { | TypeId::TINY_BLOB | TypeId::MEDIUM_BLOB | TypeId::LONG_BLOB - | TypeId::ENUM - if (self.is_binary == other.is_binary) - && match other.id { - TypeId::VAR_CHAR - | TypeId::TEXT - | TypeId::CHAR - | TypeId::TINY_BLOB - | TypeId::MEDIUM_BLOB - | TypeId::LONG_BLOB - | TypeId::ENUM => true, + if match other.id { + TypeId::VAR_CHAR + | TypeId::TEXT + | TypeId::CHAR + | TypeId::TINY_BLOB + | TypeId::MEDIUM_BLOB + | TypeId::LONG_BLOB => true, - _ => false, - } => + _ => false, + } => + { + true + } + + // Enums are considered compatible with other text/binary types + TypeId::ENUM + if match other.id { + TypeId::VAR_CHAR + | TypeId::TEXT + | TypeId::CHAR + | TypeId::TINY_BLOB + | TypeId::MEDIUM_BLOB + | TypeId::LONG_BLOB + | TypeId::ENUM => true, + + _ => false, + } => + { + true + } + + TypeId::VAR_CHAR + | TypeId::TEXT + | TypeId::CHAR + | TypeId::TINY_BLOB + | TypeId::MEDIUM_BLOB + | TypeId::LONG_BLOB + | TypeId::ENUM + if other.id == TypeId::ENUM => { true } @@ -227,8 +308,7 @@ impl TypeInfo for MySqlTypeInfo { true } - // Fallback to equality of only [id] and [is_unsigned] - _ => self.id.0 == other.id.0 && self.is_unsigned == other.is_unsigned, + _ => self.eq(other), } } } diff --git a/sqlx-core/src/types.rs b/sqlx-core/src/types.rs index 4b3ba67b..fcccf4a5 100644 --- a/sqlx-core/src/types.rs +++ b/sqlx-core/src/types.rs @@ -46,7 +46,7 @@ pub mod ipnetwork { #[derive(Debug, PartialEq)] pub struct Json(pub T); -pub trait TypeInfo: Debug + Display + Clone { +pub trait TypeInfo: PartialEq + Debug + Display + Clone { /// Compares type information to determine if `other` is compatible at the Rust level /// with `self`. fn compatible(&self, other: &Self) -> bool; diff --git a/sqlx-macros/src/database/mod.rs b/sqlx-macros/src/database/mod.rs index 87f3f030..10d56c60 100644 --- a/sqlx-macros/src/database/mod.rs +++ b/sqlx-macros/src/database/mod.rs @@ -45,7 +45,10 @@ macro_rules! impl_database_ext { fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> { match () { $( - // `if` statements cannot have attributes but these can + $(#[$meta])? + _ if <$ty as sqlx::types::Type<$database>>::type_info() == *info => Some(input_ty!($ty $(, $input)?)), + )* + $( $(#[$meta])? _ if sqlx::types::TypeInfo::compatible(&<$ty as sqlx::types::Type<$database>>::type_info(), &info) => Some(input_ty!($ty $(, $input)?)), )* @@ -55,6 +58,10 @@ macro_rules! impl_database_ext { fn return_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> { match () { + $( + $(#[$meta])? + _ if <$ty as sqlx::types::Type<$database>>::type_info() == *info => return Some(stringify!($ty)), + )* $( $(#[$meta])? _ if sqlx::types::TypeInfo::compatible(&<$ty as sqlx::types::Type<$database>>::type_info(), &info) => return Some(stringify!($ty)), diff --git a/sqlx-macros/src/derives/decode.rs b/sqlx-macros/src/derives/decode.rs index 88f1706f..dbdc6235 100644 --- a/sqlx-macros/src/derives/decode.rs +++ b/sqlx-macros/src/derives/decode.rs @@ -71,7 +71,7 @@ fn expand_derive_decode_transparent( let tts = quote!( impl #impl_generics sqlx::decode::Decode<'de, DB> for #ident #ty_generics #where_clause { - fn decode(value: >::RawValue) -> sqlx::Result { + fn decode(value: >::RawValue) -> sqlx::Result { <#ty as sqlx::decode::Decode<'de, DB>>::decode(value).map(Self) } } @@ -100,7 +100,7 @@ fn expand_derive_decode_weak_enum( Ok(quote!( impl<'de, DB: sqlx::Database> sqlx::decode::Decode<'de, DB> for #ident where #repr: sqlx::decode::Decode<'de, DB> { - fn decode(value: >::RawValue) -> sqlx::Result { + fn decode(value: >::RawValue) -> sqlx::Result { let value = <#repr as sqlx::decode::Decode<'de, DB>>::decode(value)?; match value { @@ -140,7 +140,7 @@ fn expand_derive_decode_strong_enum( Ok(quote!( impl<'de, DB: sqlx::Database> sqlx::decode::Decode<'de, DB> for #ident where &'de str: sqlx::decode::Decode<'de, DB> { - fn decode(value: >::RawValue) -> sqlx::Result { + fn decode(value: >::RawValue) -> sqlx::Result { let value = <&'de str as sqlx::decode::Decode<'de, DB>>::decode(value)?; match value { #(#value_arms)* @@ -195,7 +195,7 @@ fn expand_derive_decode_struct( tts.extend(quote!( impl #impl_generics sqlx::decode::Decode<'de, sqlx::Postgres> for #ident #ty_generics #where_clause { - fn decode(value: >::RawValue) -> sqlx::Result { + fn decode(value: >::RawValue) -> sqlx::Result { let mut decoder = sqlx::postgres::types::raw::PgRecordDecoder::new(value)?; #(#reads)* diff --git a/sqlx-macros/src/derives/type.rs b/sqlx-macros/src/derives/type.rs index a92e485f..07b12cbc 100644 --- a/sqlx-macros/src/derives/type.rs +++ b/sqlx-macros/src/derives/type.rs @@ -110,9 +110,7 @@ fn expand_derive_has_sql_type_strong_enum( tts.extend(quote!( impl sqlx::Type< sqlx::MySql > for #ident { fn type_info() -> sqlx::mysql::MySqlTypeInfo { - // This is really fine, MySQL is loosely typed and - // we don't nede to be specific here - >::type_info() + sqlx::mysql::MySqlTypeInfo::r#enum() } } )); diff --git a/src/lib.rs b/src/lib.rs index 328a4793..46a6ba88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub use sqlx_core::arguments; pub use sqlx_core::connection::{Connect, Connection}; -pub use sqlx_core::cursor::Cursor; +pub use sqlx_core::cursor::{self, Cursor}; pub use sqlx_core::database::{self, Database}; pub use sqlx_core::executor::{self, Execute, Executor}; pub use sqlx_core::pool::{self, Pool}; @@ -10,6 +10,7 @@ pub use sqlx_core::query::{self, query, Query}; pub use sqlx_core::query_as::{query_as, QueryAs}; pub use sqlx_core::row::{self, FromRow, Row}; pub use sqlx_core::transaction::Transaction; +pub use sqlx_core::value; #[doc(hidden)] pub use sqlx_core::describe; diff --git a/tests/mysql-macros.rs b/tests/mysql-macros.rs index 6921a242..6013b0c3 100644 --- a/tests/mysql-macros.rs +++ b/tests/mysql-macros.rs @@ -58,3 +58,17 @@ async fn test_query_as_raw() -> anyhow::Result<()> { Ok(()) } + +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] +async fn test_query_bytes() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let rec = sqlx::query!("SELECT X'01AF' as _1") + .fetch_one(&mut conn) + .await?; + + assert_eq!(rec._1, &[0x01_u8, 0xAF_u8]); + + Ok(()) +}