diff --git a/Cargo.toml b/Cargo.toml index e23fbdc1..634af794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,11 @@ name = "sqlite-describe" path = "tests/sqlite/describe.rs" required-features = [ "sqlite" ] +[[test]] +name = "sqlite-macros" +path = "tests/sqlite/macros.rs" +required-features = [ "sqlite", "macros" ] + # # MySQL # @@ -126,6 +131,11 @@ name = "mysql-describe" path = "tests/mysql/describe.rs" required-features = [ "mysql" ] +[[test]] +name = "mysql-macros" +path = "tests/mysql/macros.rs" +required-features = [ "mysql", "macros" ] + # # PostgreSQL # diff --git a/sqlx-core/src/mysql/connection/executor.rs b/sqlx-core/src/mysql/connection/executor.rs index b0042062..6530ab9a 100644 --- a/sqlx-core/src/mysql/connection/executor.rs +++ b/sqlx-core/src/mysql/connection/executor.rs @@ -275,9 +275,10 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection { for _ in 0..(ok.columns as usize) { let def: ColumnDefinition = self.stream.recv().await?; let ty = MySqlTypeInfo::from_column(&def); + let name = def.name()?; columns.push(Column { - name: def.name()?.to_owned(), + name: if name.is_empty() { def.alias()? } else { name }.to_owned(), type_info: ty, not_null: Some(def.flags.contains(ColumnFlags::NOT_NULL)), }) diff --git a/sqlx-core/src/mysql/type_info.rs b/sqlx-core/src/mysql/type_info.rs index a37e5bd9..75153e84 100644 --- a/sqlx-core/src/mysql/type_info.rs +++ b/sqlx-core/src/mysql/type_info.rs @@ -78,6 +78,18 @@ impl PartialEq for MySqlTypeInfo { == other.flags.contains(ColumnFlags::UNSIGNED); } + // for string types, check that our charset matches + ColumnType::VarChar + | ColumnType::Blob + | ColumnType::TinyBlob + | ColumnType::MediumBlob + | ColumnType::LongBlob + | ColumnType::String + | ColumnType::VarString + | ColumnType::Enum => { + return self.char_set == other.char_set; + } + _ => {} } diff --git a/sqlx-core/src/mysql/types/str.rs b/sqlx-core/src/mysql/types/str.rs index 86210901..17a6464e 100644 --- a/sqlx-core/src/mysql/types/str.rs +++ b/sqlx-core/src/mysql/types/str.rs @@ -36,7 +36,7 @@ impl<'r> Decode<'r, MySql> for &'r str { | ColumnType::String | ColumnType::VarString | ColumnType::Enum - ) + ) && ty.char_set == 224 } fn decode(value: MySqlValueRef<'r>) -> Result { diff --git a/sqlx-macros/src/database/mysql.rs b/sqlx-macros/src/database/mysql.rs index e6b04d48..43c5291e 100644 --- a/sqlx-macros/src/database/mysql.rs +++ b/sqlx-macros/src/database/mysql.rs @@ -13,6 +13,7 @@ impl_database_ext! { f32, f64, + // ordering is important here as otherwise we might infer strings to be binary // CHAR, VAR_CHAR, TEXT String, diff --git a/sqlx-macros/src/query/data.rs b/sqlx-macros/src/query/data.rs index 3f7e4183..9af35a48 100644 --- a/sqlx-macros/src/query/data.rs +++ b/sqlx-macros/src/query/data.rs @@ -10,6 +10,7 @@ use sqlx_core::executor::Executor; deserialize = "Describe: serde::de::DeserializeOwned" )) )] +#[derive(Debug)] pub struct QueryData { #[allow(dead_code)] pub(super) query: String, diff --git a/tests/mysql/macros.rs b/tests/mysql/macros.rs new file mode 100644 index 00000000..f949fb48 --- /dev/null +++ b/tests/mysql/macros.rs @@ -0,0 +1,94 @@ +use sqlx::MySql; +use sqlx_test::new; + +#[sqlx_macros::test] +async fn macro_select_from_cte() -> anyhow::Result<()> { + let mut conn = new::().await?; + let account = + sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name, cast(null as char) email) accounts") + .fetch_one(&mut conn) + .await?; + + assert_eq!(account.id, 1); + assert_eq!(account.name, "Herp Derpinson"); + // MySQL can tell us the nullability of expressions, ain't that cool + assert_eq!(account.email, None); + + Ok(()) +} + +#[sqlx_macros::test] +async fn macro_select_from_cte_bind() -> anyhow::Result<()> { + let mut conn = new::().await?; + let account = sqlx::query!( + "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?", + 1i32 + ) + .fetch_one(&mut conn) + .await?; + + println!("{:?}", account); + println!("{}: {}", account.id, account.name); + + Ok(()) +} + +#[derive(Debug)] +struct RawAccount { + r#type: i32, + name: Option, +} + +#[sqlx_macros::test] +async fn test_query_as_raw() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let account = sqlx::query_as!( + RawAccount, + "SELECT * from (select 1 as type, cast(null as char) as name) accounts" + ) + .fetch_one(&mut conn) + .await?; + + assert_eq!(account.name, None); + assert_eq!(account.r#type, 1); + + println!("{:?}", account); + + Ok(()) +} + +#[sqlx_macros::test] +async fn test_query_as_bool() -> anyhow::Result<()> { + let mut conn = new::().await?; + + struct Article { + id: i32, + deleted: bool, + } + + let article = sqlx::query_as_unchecked!( + Article, + "select * from (select 51 as id, true as deleted) articles" + ) + .fetch_one(&mut conn) + .await?; + + assert_eq!(51, article.id); + assert_eq!(true, article.deleted); + + Ok(()) +} + +#[sqlx_macros::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(()) +}