mysql: tweak type equivalence rules to try and support both rust best practices but still be compatible with the loose types of mysql

This commit is contained in:
Ryan Leckey 2020-03-25 03:56:39 -07:00
parent 50928b06b8
commit ad2cf1676f
7 changed files with 124 additions and 24 deletions

View File

@ -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<MySqlTypeInfo> 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),
}
}
}

View File

@ -46,7 +46,7 @@ pub mod ipnetwork {
#[derive(Debug, PartialEq)]
pub struct Json<T>(pub T);
pub trait TypeInfo: Debug + Display + Clone {
pub trait TypeInfo: PartialEq<Self> + Debug + Display + Clone {
/// Compares type information to determine if `other` is compatible at the Rust level
/// with `self`.
fn compatible(&self, other: &Self) -> bool;

View File

@ -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)),

View File

@ -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: <DB as sqlx::database::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
fn decode(value: <DB as sqlx::value::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
<#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: <DB as sqlx::database::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
fn decode(value: <DB as sqlx::value::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
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: <DB as sqlx::database::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
fn decode(value: <DB as sqlx::value::HasRawValue<'de>>::RawValue) -> sqlx::Result<DB, Self> {
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: <sqlx::Postgres as sqlx::database::HasRawValue<'de>>::RawValue) -> sqlx::Result<sqlx::Postgres, Self> {
fn decode(value: <sqlx::Postgres as sqlx::value::HasRawValue<'de>>::RawValue) -> sqlx::Result<sqlx::Postgres, Self> {
let mut decoder = sqlx::postgres::types::raw::PgRecordDecoder::new(value)?;
#(#reads)*

View File

@ -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
<str as sqlx::Type<sqlx::MySql>>::type_info()
sqlx::mysql::MySqlTypeInfo::r#enum()
}
}
));

View File

@ -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;

View File

@ -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::<MySql>().await?;
let rec = sqlx::query!("SELECT X'01AF' as _1")
.fetch_one(&mut conn)
.await?;
assert_eq!(rec._1, &[0x01_u8, 0xAF_u8]);
Ok(())
}