diff --git a/sqlx-core/src/type.rs b/sqlx-core/src/type.rs index 8ca5b384..4d670791 100644 --- a/sqlx-core/src/type.rs +++ b/sqlx-core/src/type.rs @@ -31,6 +31,16 @@ pub trait Type { } } +impl> Type for &'_ T { + fn type_id() -> Db::TypeId { + T::type_id() + } + + fn compatible(ty: &Db::TypeInfo) -> bool { + T::compatible(ty) + } +} + #[allow(clippy::module_name_repetitions)] pub trait TypeEncode: Type + Encode { /// Returns the canonical SQL type identifier for this Rust type. diff --git a/sqlx-mysql/src/type_id.rs b/sqlx-mysql/src/type_id.rs index 847f000e..c5344db1 100644 --- a/sqlx-mysql/src/type_id.rs +++ b/sqlx-mysql/src/type_id.rs @@ -57,30 +57,6 @@ impl MySqlTypeId { pub(crate) const fn is_unsigned(&self) -> bool { self.1 == UNSIGNED } - - /// Returns the name for this MySQL data type. - pub(crate) const fn name(&self) -> &'static str { - match *self { - Self::NULL => "NULL", - - Self::TINYINT => "TINYINT", - Self::SMALLINT => "SMALLINT", - Self::MEDIUMINT => "MEDIUMINT", - Self::INT => "INT", - Self::BIGINT => "BIGINT", - - Self::TINYINT_UNSIGNED => "TINYINT UNSIGNED", - Self::SMALLINT_UNSIGNED => "SMALLINT UNSIGNED", - Self::MEDIUMINT_UNSIGNED => "MEDIUMINT UNSIGNED", - Self::INT_UNSIGNED => "INT UNSIGNED", - Self::BIGINT_UNSIGNED => "BIGINT UNSIGNED", - - Self::FLOAT => "FLOAT", - Self::DOUBLE => "DOUBLE", - - _ => "", - } - } } // https://dev.mysql.com/doc/refman/8.0/en/data-types.html @@ -199,4 +175,40 @@ impl MySqlTypeId { /// used to send a SQL `NULL` without knowing the SQL type. /// pub const NULL: Self = Self(6, 0); + + /// A fixed-length string that is always right-padded with spaces + /// to the specified length when stored. + /// + pub const CHAR: Self = Self(254, 0); + + /// A fixed-length binary string that is always right-padded with zeroes + /// to the specified length when stored. + /// + /// The type identifier for `BINARY` is the same as `CHAR`. At the type + /// level, they are identical. At the column, the presence of a binary (`_bin`) + /// collation determines if the type stores binary data. + /// + pub const BINARY: Self = Self(254, 0); + + /// A variable-length string. + pub const VARCHAR: Self = Self(253, 0); + + /// A variable-length binary string. + /// + /// The type identifier for `VARBINARY` is the same as `VARCHAR`. At the type + /// level, they are identical. At the column, the presence of a binary (`_bin`) + /// collation determines if the type stores binary data. + /// + pub const VARBINARY: Self = Self(253, 0); + + /// A variable-length string that is assumed to be stored by reference. + pub const TEXT: Self = Self(252, 0); + + /// A variable-length string that is assumed to be stored by reference. + /// + /// The type identifier for `BLOB` is the same as `TEXT`. At the type + /// level, they are identical. At the column, the presence of a binary (`_bin`) + /// collation determines if the type stores binary data. + /// + pub const BLOB: Self = Self(252, 0); } diff --git a/sqlx-mysql/src/type_info.rs b/sqlx-mysql/src/type_info.rs index a02cbff2..898777e0 100644 --- a/sqlx-mysql/src/type_info.rs +++ b/sqlx-mysql/src/type_info.rs @@ -1,6 +1,6 @@ use sqlx_core::TypeInfo; -use crate::protocol::ColumnDefinition; +use crate::protocol::{ColumnDefinition, ColumnFlags}; use crate::{MySql, MySqlTypeId}; /// Provides information about a MySQL type. @@ -13,6 +13,7 @@ use crate::{MySql, MySqlTypeId}; pub struct MySqlTypeInfo { id: MySqlTypeId, charset: u16, + has_binary_collation: bool, // [max_size] for integer types, this is (M) in BIT(M) or TINYINT(M) max_size: u32, @@ -20,15 +21,63 @@ pub struct MySqlTypeInfo { impl MySqlTypeInfo { pub(crate) const fn new(def: &ColumnDefinition) -> Self { - Self { id: MySqlTypeId::new(def), charset: def.charset, max_size: def.max_size } + Self { + id: MySqlTypeId::new(def), + charset: def.charset, + max_size: def.max_size, + has_binary_collation: def.flags.contains(ColumnFlags::BINARY_COLLATION), + } } } impl MySqlTypeInfo { /// Returns the unique identifier for this MySQL type. + #[must_use] pub const fn id(&self) -> MySqlTypeId { self.id } + + /// Returns `true` if this type has a binary collation. + #[must_use] + pub const fn has_binary_collation(&self) -> bool { + self.has_binary_collation + } + + /// Returns the name for this MySQL type. + #[must_use] + pub const fn name(&self) -> &'static str { + match self.id { + MySqlTypeId::NULL => "NULL", + + MySqlTypeId::TINYINT => "TINYINT", + MySqlTypeId::SMALLINT => "SMALLINT", + MySqlTypeId::MEDIUMINT => "MEDIUMINT", + MySqlTypeId::INT => "INT", + MySqlTypeId::BIGINT => "BIGINT", + + MySqlTypeId::TINYINT_UNSIGNED => "TINYINT UNSIGNED", + MySqlTypeId::SMALLINT_UNSIGNED => "SMALLINT UNSIGNED", + MySqlTypeId::MEDIUMINT_UNSIGNED => "MEDIUMINT UNSIGNED", + MySqlTypeId::INT_UNSIGNED => "INT UNSIGNED", + MySqlTypeId::BIGINT_UNSIGNED => "BIGINT UNSIGNED", + + MySqlTypeId::FLOAT => "FLOAT", + MySqlTypeId::DOUBLE => "DOUBLE", + + // note: VARBINARY, BINARY, and BLOB have the same type IDs as + // VARCHAR, CHAR, and TEXT; the only difference is the + // presence of a binary collation + MySqlTypeId::VARBINARY if self.has_binary_collation() => "VARBINARY", + MySqlTypeId::BINARY if self.has_binary_collation() => "BINARY", + MySqlTypeId::BLOB if self.has_binary_collation() => "BLOB", + + MySqlTypeId::VARCHAR => "VARCHAR", + MySqlTypeId::CHAR => "CHAR", + MySqlTypeId::TEXT => "TEXT", + + _ => "", + } + } } impl TypeInfo for MySqlTypeInfo { @@ -43,6 +92,6 @@ impl TypeInfo for MySqlTypeInfo { } fn name(&self) -> &str { - self.id.name() + self.name() } } diff --git a/sqlx-mysql/src/types.rs b/sqlx-mysql/src/types.rs index 2bd4947d..a8812297 100644 --- a/sqlx-mysql/src/types.rs +++ b/sqlx-mysql/src/types.rs @@ -29,6 +29,7 @@ mod bool; mod str; mod uint; +mod bytes; // TODO: mod decimal; // TODO: mod int; diff --git a/sqlx-mysql/src/types/bytes.rs b/sqlx-mysql/src/types/bytes.rs new file mode 100644 index 00000000..873a4c1e --- /dev/null +++ b/sqlx-mysql/src/types/bytes.rs @@ -0,0 +1,52 @@ +use sqlx_core::{decode, encode, Type}; +use sqlx_core::{Decode, Encode}; + +use crate::io::MySqlWriteExt; +use crate::type_info::MySqlTypeInfo; +use crate::{MySql, MySqlOutput, MySqlRawValue, MySqlTypeId}; + +impl Type for &'_ [u8] { + fn type_id() -> MySqlTypeId { + MySqlTypeId::BLOB + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + matches!(ty.id(), MySqlTypeId::BLOB | MySqlTypeId::BINARY | MySqlTypeId::VARBINARY) + } +} + +impl Encode for &'_ [u8] { + fn encode(&self, _: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> { + out.buffer().write_bytes_lenenc(self); + + Ok(()) + } +} + +impl<'r> Decode<'r, MySql> for &'r [u8] { + fn decode(value: MySqlRawValue<'r>) -> decode::Result { + value.as_bytes() + } +} + +impl Type for Vec { + fn type_id() -> MySqlTypeId { + <&[u8] as Type>::type_id() + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + <&[u8] as Type>::compatible(ty) + } +} + +impl Encode for Vec { + fn encode(&self, ty: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> { + <&[u8] as Encode>::encode(&self.as_slice(), ty, out) + } +} + +impl<'r> Decode<'r, MySql> for Vec { + fn decode(value: MySqlRawValue<'r>) -> decode::Result { + value.as_bytes().map(ToOwned::to_owned) + } +} diff --git a/sqlx-mysql/src/types/str.rs b/sqlx-mysql/src/types/str.rs index a090729a..7f2d2e2a 100644 --- a/sqlx-mysql/src/types/str.rs +++ b/sqlx-mysql/src/types/str.rs @@ -1,25 +1,23 @@ -use sqlx_core::{decode, encode}; +use sqlx_core::{decode, encode, Type}; use sqlx_core::{Decode, Encode}; +use crate::io::MySqlWriteExt; use crate::type_info::MySqlTypeInfo; -use crate::{MySql, MySqlOutput, MySqlRawValue}; +use crate::{MySql, MySqlOutput, MySqlRawValue, MySqlTypeId}; -// https://dev.mysql.com/doc/internals/en/binary-protocol-value.html#packet-ProtocolBinary +impl Type for &'_ str { + fn type_id() -> MySqlTypeId { + MySqlTypeId::TEXT + } -// TODO: accepts(ty) -// TODO: compatible(ty) - -impl Encode for str { - fn encode(&self, _: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> { - todo!("encode: &str"); - - Ok(()) + fn compatible(ty: &MySqlTypeInfo) -> bool { + matches!(ty.id(), MySqlTypeId::TEXT | MySqlTypeId::CHAR | MySqlTypeId::VARCHAR) } } -impl Encode for String { +impl Encode for &'_ str { fn encode(&self, _: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> { - todo!("encode: String"); + out.buffer().write_bytes_lenenc(self.as_bytes()); Ok(()) } @@ -31,6 +29,22 @@ impl<'r> Decode<'r, MySql> for &'r str { } } +impl Type for String { + fn type_id() -> MySqlTypeId { + <&str as Type>::type_id() + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + <&str as Type>::compatible(ty) + } +} + +impl Encode for String { + fn encode(&self, ty: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> { + <&str as Encode>::encode(&self.as_str(), ty, out) + } +} + impl<'r> Decode<'r, MySql> for String { fn decode(value: MySqlRawValue<'r>) -> decode::Result { value.as_str().map(str::to_owned)