-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
442 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
/// Map a SQL text value to/from a Rust type using [`Display`] and [`FromStr`]. | ||
/// | ||
/// This can be useful for types that do not have a direct SQL equivalent, or are simply not | ||
/// supported by SQLx for one reason or another. | ||
/// | ||
/// For strongly typed databases like Postgres, this will report the value's type as `TEXT`. | ||
/// Explicit conversion may be necessary on the SQL side depending on the desired type. | ||
/// | ||
/// [`Display`]: std::fmt::Display | ||
/// [`FromStr`]: std::str::FromStr | ||
/// | ||
/// ### Panics | ||
/// | ||
/// You should only use this adapter with `Display` implementations that are infallible, | ||
/// otherwise you may encounter panics when attempting to bind a value. | ||
/// | ||
/// This is because the design of the `Encode` trait assumes encoding is infallible, so there is no | ||
/// way to bubble up the error. | ||
/// | ||
/// Fortunately, most `Display` implementations are infallible by convention anyway | ||
/// (the standard `ToString` trait also assumes this), but you may still want to audit | ||
/// the source code for any types you intend to use with this adapter, just to be safe. | ||
/// | ||
/// ### Example: `SocketAddr` | ||
/// | ||
/// MySQL and SQLite do not have a native SQL equivalent for `SocketAddr`, so if you want to | ||
/// store and retrieve instances of it, it makes sense to map it to `TEXT`: | ||
/// | ||
/// ```rust,no_run | ||
/// # use sqlx::types::{time, uuid}; | ||
/// | ||
/// use std::net::SocketAddr; | ||
/// | ||
/// use sqlx::Connection; | ||
/// use sqlx::mysql::MySqlConnection; | ||
/// use sqlx::types::Text; | ||
/// | ||
/// use uuid::Uuid; | ||
/// use time::OffsetDateTime; | ||
/// | ||
/// #[derive(sqlx::FromRow, Debug)] | ||
/// struct Login { | ||
/// user_id: Uuid, | ||
/// socket_addr: Text<SocketAddr>, | ||
/// login_at: OffsetDateTime | ||
/// } | ||
/// | ||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> { | ||
/// | ||
/// let mut conn: MySqlConnection = MySqlConnection::connect("<DATABASE URL>").await?; | ||
/// | ||
/// let user_id: Uuid = "e9a72cdc-d907-48d6-a488-c64a91fd063c".parse().unwrap(); | ||
/// let socket_addr: SocketAddr = "198.51.100.47:31790".parse().unwrap(); | ||
/// | ||
/// // CREATE TABLE user_login(user_id VARCHAR(36), socket_addr TEXT, login_at TIMESTAMP); | ||
/// sqlx::query("INSERT INTO user_login(user_id, socket_addr, login_at) VALUES (?, ?, NOW())") | ||
/// .bind(user_id) | ||
/// .bind(Text(socket_addr)) | ||
/// .execute(&mut conn) | ||
/// .await?; | ||
/// | ||
/// let logins: Vec<Login> = sqlx::query_as("SELECT * FROM user_login") | ||
/// .fetch_all(&mut conn) | ||
/// .await?; | ||
/// | ||
/// println!("Logins for user ID {user_id}: {logins:?}"); | ||
/// | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] | ||
pub struct Text<T>(pub T); | ||
|
||
impl<T> Text<T> { | ||
/// Extract the inner value. | ||
pub fn into_inner(self) -> T { | ||
self.0 | ||
} | ||
} | ||
|
||
impl<T> Deref for Text<T> { | ||
type Target = T; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<T> DerefMut for Text<T> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
&mut self.0 | ||
} | ||
} | ||
|
||
/* We shouldn't use blanket impls so individual drivers can provide specialized ones. | ||
impl<T, DB> Type<DB> for Text<T> | ||
where | ||
String: Type<DB>, | ||
DB: Database, | ||
{ | ||
fn type_info() -> DB::TypeInfo { | ||
String::type_info() | ||
} | ||
fn compatible(ty: &DB::TypeInfo) -> bool { | ||
String::compatible(ty) | ||
} | ||
} | ||
impl<'q, T, DB> Encode<'q, DB> for Text<T> | ||
where | ||
T: Display, | ||
String: Encode<'q, DB>, | ||
DB: Database, | ||
{ | ||
fn encode_by_ref(&self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull { | ||
self.0.to_string().encode(buf) | ||
} | ||
} | ||
impl<'r, T, DB> Decode<'r, DB> for Text<T> | ||
where | ||
T: FromStr, | ||
BoxDynError: From<<T as FromStr>::Err>, | ||
&'r str: Decode<'r, DB>, | ||
DB: Database, | ||
{ | ||
fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> { | ||
Ok(Text(<&'r str as Decode<'r, DB>>::decode(value)?.parse()?)) | ||
} | ||
} | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,7 @@ mod bytes; | |
mod float; | ||
mod int; | ||
mod str; | ||
mod text; | ||
mod uint; | ||
|
||
#[cfg(feature = "json")] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; | ||
use sqlx_core::decode::Decode; | ||
use sqlx_core::encode::{Encode, IsNull}; | ||
use sqlx_core::error::BoxDynError; | ||
use sqlx_core::types::{Text, Type}; | ||
use std::fmt::Display; | ||
use std::str::FromStr; | ||
|
||
impl<T> Type<MySql> for Text<T> { | ||
fn type_info() -> MySqlTypeInfo { | ||
<String as Type<MySql>>::type_info() | ||
} | ||
|
||
fn compatible(ty: &MySqlTypeInfo) -> bool { | ||
<String as Type<MySql>>::compatible(ty) | ||
} | ||
} | ||
|
||
impl<'q, T> Encode<'q, MySql> for Text<T> | ||
where | ||
T: Display, | ||
{ | ||
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull { | ||
// We can't really do the trick like with Postgres where we reserve the space for the | ||
// length up-front and then overwrite it later, because MySQL appears to enforce that | ||
// length-encoded integers use the smallest encoding for the value: | ||
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html#sect_protocol_basic_dt_int_le | ||
// | ||
// So we'd have to reserve space for the max-width encoding, format into the buffer, | ||
// then figure out how many bytes our length-encoded integer needs to be and move the | ||
// value bytes down to use up the empty space. | ||
// | ||
// Copying from a completely separate buffer instead is easier. It may or may not be faster | ||
// or slower depending on a ton of different variables, but I don't currently have the time | ||
// to implement both approaches and compare their performance. | ||
Encode::<MySql>::encode(self.0.to_string(), buf) | ||
} | ||
} | ||
|
||
impl<'r, T> Decode<'r, MySql> for Text<T> | ||
where | ||
T: FromStr, | ||
BoxDynError: From<<T as FromStr>::Err>, | ||
{ | ||
fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> { | ||
let s: &str = Decode::<MySql>::decode(value)?; | ||
Ok(Self(s.parse()?)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -193,6 +193,7 @@ mod oid; | |
mod range; | ||
mod record; | ||
mod str; | ||
mod text; | ||
mod tuple; | ||
mod void; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
use crate::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres}; | ||
use sqlx_core::decode::Decode; | ||
use sqlx_core::encode::{Encode, IsNull}; | ||
use sqlx_core::error::BoxDynError; | ||
use sqlx_core::types::{Text, Type}; | ||
use std::fmt::Display; | ||
use std::str::FromStr; | ||
|
||
use std::io::Write; | ||
|
||
impl<T> Type<Postgres> for Text<T> { | ||
fn type_info() -> PgTypeInfo { | ||
<String as Type<Postgres>>::type_info() | ||
} | ||
|
||
fn compatible(ty: &PgTypeInfo) -> bool { | ||
<String as Type<Postgres>>::compatible(ty) | ||
} | ||
} | ||
|
||
impl<'q, T> Encode<'q, Postgres> for Text<T> | ||
where | ||
T: Display, | ||
{ | ||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { | ||
// Unfortunately, our API design doesn't give us a way to bubble up the error here. | ||
// | ||
// Fortunately, writing to `Vec<u8>` is infallible so the only possible source of | ||
// errors is from the implementation of `Display::fmt()` itself, | ||
// where the onus is on the user. | ||
// | ||
// The blanket impl of `ToString` also panics if there's an error, so this is not | ||
// unprecedented. | ||
// | ||
// However, the panic should be documented anyway. | ||
write!(**buf, "{}", self.0).expect("unexpected error from `Display::fmt()`"); | ||
IsNull::No | ||
} | ||
} | ||
|
||
impl<'r, T> Decode<'r, Postgres> for Text<T> | ||
where | ||
T: FromStr, | ||
BoxDynError: From<<T as FromStr>::Err>, | ||
{ | ||
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { | ||
let s: &str = Decode::<Postgres>::decode(value)?; | ||
Ok(Self(s.parse()?)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; | ||
use sqlx_core::decode::Decode; | ||
use sqlx_core::encode::{Encode, IsNull}; | ||
use sqlx_core::error::BoxDynError; | ||
use sqlx_core::types::{Text, Type}; | ||
use std::fmt::Display; | ||
use std::str::FromStr; | ||
|
||
impl<T> Type<Sqlite> for Text<T> { | ||
fn type_info() -> SqliteTypeInfo { | ||
<String as Type<Sqlite>>::type_info() | ||
} | ||
|
||
fn compatible(ty: &SqliteTypeInfo) -> bool { | ||
<String as Type<Sqlite>>::compatible(ty) | ||
} | ||
} | ||
|
||
impl<'q, T> Encode<'q, Sqlite> for Text<T> | ||
where | ||
T: Display, | ||
{ | ||
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull { | ||
Encode::<Sqlite>::encode(self.0.to_string(), buf) | ||
} | ||
} | ||
|
||
impl<'r, T> Decode<'r, Sqlite> for Text<T> | ||
where | ||
T: FromStr, | ||
BoxDynError: From<<T as FromStr>::Err>, | ||
{ | ||
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> { | ||
let s: &str = Decode::<Sqlite>::decode(value)?; | ||
Ok(Self(s.parse()?)) | ||
} | ||
} |
Oops, something went wrong.