-
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
7 changed files
with
291 additions
and
7 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use std::fmt::{Debug, Display}; | ||
use std::ops::{Deref, DerefMut}; | ||
use std::str::FromStr; | ||
|
||
use crate::database::{Database, HasArguments, HasValueRef}; | ||
use crate::decode::Decode; | ||
use crate::encode::{Encode, IsNull}; | ||
use crate::error::BoxDynError; | ||
use crate::types::Type; | ||
|
||
/// 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`. | ||
/// | ||
/// ### 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 uuid::Uuid; | ||
/// | ||
/// use time::OffsetDateTime; | ||
/// | ||
/// use std::net::SocketAddr; | ||
/// | ||
/// use sqlx::Connection; | ||
/// use sqlx::mysql::MySqlConnection; | ||
/// use sqlx::types::Text; | ||
/// | ||
/// #[derive(sqlx::FromRow, Debug)] | ||
/// struct Login { | ||
/// user_id: Uuid, | ||
/// socket_addr: Text<SocketAddr>, | ||
/// login_at: OffsetDateTime | ||
/// } | ||
/// | ||
/// # async fn main() -> 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 | ||
} | ||
} | ||
|
||
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
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
Oops, something went wrong.