feat(mysql): add support for &str and &[u8]

This commit is contained in:
Ryan Leckey 2021-02-23 01:51:21 -08:00
parent d2da565d0b
commit 22960f1610
No known key found for this signature in database
GPG Key ID: F8AA68C235AB08C9
6 changed files with 178 additions and 40 deletions

View File

@ -31,6 +31,16 @@ pub trait Type<Db: Database> {
}
}
impl<Db: Database, T: Type<Db>> Type<Db> 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<Db: Database>: Type<Db> + Encode<Db> {
/// Returns the canonical SQL type identifier for this Rust type.

View File

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

View File

@ -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()
}
}

View File

@ -29,6 +29,7 @@
mod bool;
mod str;
mod uint;
mod bytes;
// TODO: mod decimal;
// TODO: mod int;

View File

@ -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<MySql> for &'_ [u8] {
fn type_id() -> MySqlTypeId {
MySqlTypeId::BLOB
}
fn compatible(ty: &MySqlTypeInfo) -> bool {
matches!(ty.id(), MySqlTypeId::BLOB | MySqlTypeId::BINARY | MySqlTypeId::VARBINARY)
}
}
impl Encode<MySql> 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<Self> {
value.as_bytes()
}
}
impl Type<MySql> for Vec<u8> {
fn type_id() -> MySqlTypeId {
<&[u8] as Type<MySql>>::type_id()
}
fn compatible(ty: &MySqlTypeInfo) -> bool {
<&[u8] as Type<MySql>>::compatible(ty)
}
}
impl Encode<MySql> for Vec<u8> {
fn encode(&self, ty: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> {
<&[u8] as Encode<MySql>>::encode(&self.as_slice(), ty, out)
}
}
impl<'r> Decode<'r, MySql> for Vec<u8> {
fn decode(value: MySqlRawValue<'r>) -> decode::Result<Self> {
value.as_bytes().map(ToOwned::to_owned)
}
}

View File

@ -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<MySql> for &'_ str {
fn type_id() -> MySqlTypeId {
MySqlTypeId::TEXT
}
// TODO: accepts(ty)
// TODO: compatible(ty)
impl Encode<MySql> 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<MySql> for String {
impl Encode<MySql> 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<MySql> for String {
fn type_id() -> MySqlTypeId {
<&str as Type<MySql>>::type_id()
}
fn compatible(ty: &MySqlTypeInfo) -> bool {
<&str as Type<MySql>>::compatible(ty)
}
}
impl Encode<MySql> for String {
fn encode(&self, ty: &MySqlTypeInfo, out: &mut MySqlOutput<'_>) -> encode::Result<()> {
<&str as Encode<MySql>>::encode(&self.as_str(), ty, out)
}
}
impl<'r> Decode<'r, MySql> for String {
fn decode(value: MySqlRawValue<'r>) -> decode::Result<Self> {
value.as_str().map(str::to_owned)