mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-31 15:39:34 +00:00
Merge pull request #134 from launchbadge/derives
Add derives to support custom types
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::types::TypeInfo;
|
||||
/// A database driver.
|
||||
///
|
||||
/// This trait encapsulates a complete driver implementation to a specific
|
||||
/// database (e.g., MySQL, Postgres).
|
||||
/// database (e.g., **MySQL**, **Postgres**).
|
||||
pub trait Database
|
||||
where
|
||||
Self: Sized + Send + 'static,
|
||||
@@ -29,21 +29,54 @@ where
|
||||
/// The Rust type of table identifiers for this database.
|
||||
type TableId: Display + Clone;
|
||||
|
||||
type RawBuffer;
|
||||
/// The Rust type used as the buffer when encoding arguments.
|
||||
///
|
||||
/// For example, **Postgres** and **MySQL** use `Vec<u8>`; however, **SQLite** uses `Vec<SqliteArgumentValue>`.
|
||||
type RawBuffer: Default;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with a `RawValue` of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8
|
||||
pub trait HasRawValue<'c> {
|
||||
/// The Rust type used to hold a not-yet-decoded value that has just been
|
||||
/// received from the database.
|
||||
///
|
||||
/// For example, **Postgres** and **MySQL** use `&'c [u8]`; however, **SQLite** uses `SqliteValue<'c>`.
|
||||
type RawValue;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with a [`Cursor`] of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8
|
||||
pub trait HasCursor<'c, 'q> {
|
||||
type Database: Database;
|
||||
|
||||
/// The concrete `Cursor` implementation for this database.
|
||||
type Cursor: Cursor<'c, 'q, Database = Self::Database>;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with a [`Row`] of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8
|
||||
pub trait HasRow<'c> {
|
||||
type Database: Database;
|
||||
|
||||
/// The concrete `Row` implementation for this database.
|
||||
type Row: Row<'c, Database = Self::Database>;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ impl<'c> Row<'c> {
|
||||
| TypeId::LONG_BLOB
|
||||
| TypeId::CHAR
|
||||
| TypeId::TEXT
|
||||
| TypeId::ENUM
|
||||
| TypeId::VAR_CHAR => {
|
||||
let (len_size, len) = get_lenenc(&buffer[index..]);
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ type_id_consts! {
|
||||
pub const VAR_CHAR: TypeId = TypeId(253); // or VAR_BINARY
|
||||
pub const TEXT: TypeId = TypeId(252); // or BLOB
|
||||
|
||||
// Enum
|
||||
pub const ENUM: TypeId = TypeId(247);
|
||||
|
||||
// More Bytes
|
||||
pub const TINY_BLOB: TypeId = TypeId(249);
|
||||
pub const MEDIUM_BLOB: TypeId = TypeId(250);
|
||||
|
||||
@@ -85,6 +85,7 @@ impl TypeInfo for MySqlTypeInfo {
|
||||
| TypeId::TINY_BLOB
|
||||
| TypeId::MEDIUM_BLOB
|
||||
| TypeId::LONG_BLOB
|
||||
| TypeId::ENUM
|
||||
if (self.is_binary == other.is_binary)
|
||||
&& match other.id {
|
||||
TypeId::VAR_CHAR
|
||||
@@ -92,7 +93,8 @@ impl TypeInfo for MySqlTypeInfo {
|
||||
| TypeId::CHAR
|
||||
| TypeId::TINY_BLOB
|
||||
| TypeId::MEDIUM_BLOB
|
||||
| TypeId::LONG_BLOB => true,
|
||||
| TypeId::LONG_BLOB
|
||||
| TypeId::ENUM => true,
|
||||
|
||||
_ => false,
|
||||
} =>
|
||||
|
||||
@@ -21,7 +21,7 @@ mod row;
|
||||
mod sasl;
|
||||
mod stream;
|
||||
mod tls;
|
||||
mod types;
|
||||
pub mod types;
|
||||
|
||||
/// An alias for [`Pool`][crate::Pool], specialized for **Postgres**.
|
||||
pub type PgPool = crate::pool::Pool<PgConnection>;
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::row::{ColumnIndex, Row};
|
||||
|
||||
/// A value from Postgres. This may be in a BINARY or TEXT format depending
|
||||
/// on the data type and if the query was prepared or not.
|
||||
#[derive(Debug)]
|
||||
pub enum PgValue<'c> {
|
||||
Binary(&'c [u8]),
|
||||
Text(&'c str),
|
||||
|
||||
@@ -11,8 +11,11 @@ mod bool;
|
||||
mod bytes;
|
||||
mod float;
|
||||
mod int;
|
||||
mod record;
|
||||
mod str;
|
||||
|
||||
pub use self::record::{PgRecordDecoder, PgRecordEncoder};
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
|
||||
@@ -57,6 +60,10 @@ impl PgTypeInfo {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn oid(&self) -> u32 {
|
||||
self.id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PgTypeInfo {
|
||||
|
||||
309
sqlx-core/src/postgres/types/record.rs
Normal file
309
sqlx-core/src/postgres/types/record.rs
Normal file
@@ -0,0 +1,309 @@
|
||||
use crate::decode::Decode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
use crate::io::Buf;
|
||||
use crate::postgres::protocol::TypeId;
|
||||
use crate::postgres::{PgTypeInfo, PgValue, Postgres};
|
||||
use crate::types::Type;
|
||||
use byteorder::BigEndian;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct PgRecordEncoder<'a> {
|
||||
buf: &'a mut Vec<u8>,
|
||||
beg: usize,
|
||||
num: u32,
|
||||
}
|
||||
|
||||
impl<'a> PgRecordEncoder<'a> {
|
||||
pub fn new(buf: &'a mut Vec<u8>) -> Self {
|
||||
// reserve space for a field count
|
||||
buf.extend_from_slice(&(0_u32).to_be_bytes());
|
||||
|
||||
Self {
|
||||
beg: buf.len(),
|
||||
buf,
|
||||
num: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
// replaces zeros with actual length
|
||||
self.buf[self.beg - 4..self.beg].copy_from_slice(&self.num.to_be_bytes());
|
||||
}
|
||||
|
||||
pub fn encode<T>(&mut self, value: T) -> &mut Self
|
||||
where
|
||||
T: Type<Postgres> + Encode<Postgres>,
|
||||
{
|
||||
// write oid
|
||||
let info = T::type_info();
|
||||
self.buf.extend(&info.oid().to_be_bytes());
|
||||
|
||||
// write zeros for length
|
||||
self.buf.extend(&[0; 4]);
|
||||
|
||||
let start = self.buf.len();
|
||||
if let IsNull::Yes = value.encode_nullable(self.buf) {
|
||||
// replaces zeros with actual length
|
||||
self.buf[start - 4..start].copy_from_slice(&(-1_i32).to_be_bytes());
|
||||
} else {
|
||||
let end = self.buf.len();
|
||||
let size = end - start;
|
||||
|
||||
// replaces zeros with actual length
|
||||
self.buf[start - 4..start].copy_from_slice(&(size as u32).to_be_bytes());
|
||||
}
|
||||
|
||||
// keep track of count
|
||||
self.num += 1;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PgRecordDecoder<'de> {
|
||||
value: PgValue<'de>,
|
||||
}
|
||||
|
||||
impl<'de> PgRecordDecoder<'de> {
|
||||
pub fn new(value: Option<PgValue<'de>>) -> crate::Result<Self> {
|
||||
let mut value: PgValue = value.try_into()?;
|
||||
|
||||
match value {
|
||||
PgValue::Binary(ref mut buf) => {
|
||||
let _expected_len = buf.get_u32::<BigEndian>()?;
|
||||
}
|
||||
|
||||
PgValue::Text(ref mut s) => {
|
||||
// remove outer ( ... )
|
||||
*s = &s[1..(s.len() - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { value })
|
||||
}
|
||||
|
||||
pub fn decode<T>(&mut self) -> crate::Result<T>
|
||||
where
|
||||
T: Decode<'de, Postgres>,
|
||||
{
|
||||
match self.value {
|
||||
PgValue::Binary(ref mut buf) => {
|
||||
// TODO: We should fail if this type is not _compatible_; but
|
||||
// I want to make sure we handle this _and_ the outer level
|
||||
// type mismatch errors at the same time
|
||||
let _oid = buf.get_u32::<BigEndian>()?;
|
||||
let len = buf.get_i32::<BigEndian>()? as isize;
|
||||
|
||||
let value = if len < 0 {
|
||||
T::decode(None)?
|
||||
} else {
|
||||
let value_buf = &buf[..(len as usize)];
|
||||
*buf = &buf[(len as usize)..];
|
||||
|
||||
T::decode(Some(PgValue::Binary(value_buf)))?
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
PgValue::Text(ref mut s) => {
|
||||
let mut in_quotes = false;
|
||||
let mut in_escape = false;
|
||||
let mut is_quoted = false;
|
||||
let mut prev_ch = '\0';
|
||||
let mut eos = false;
|
||||
let mut prev_index = 0;
|
||||
let mut value = String::new();
|
||||
|
||||
let index = 'outer: loop {
|
||||
let mut iter = s.char_indices();
|
||||
while let Some((index, ch)) = iter.next() {
|
||||
match ch {
|
||||
',' if !in_quotes => {
|
||||
break 'outer Some(prev_index);
|
||||
}
|
||||
|
||||
',' if prev_ch == '\0' => {
|
||||
break 'outer None;
|
||||
}
|
||||
|
||||
'"' if prev_ch == '"' && index != 1 => {
|
||||
// Quotes are escaped with another quote
|
||||
in_quotes = false;
|
||||
value.push('"');
|
||||
}
|
||||
|
||||
'"' if in_quotes => {
|
||||
in_quotes = false;
|
||||
}
|
||||
|
||||
'\'' if in_escape => {
|
||||
in_escape = false;
|
||||
value.push('\'');
|
||||
}
|
||||
|
||||
'"' if in_escape => {
|
||||
in_escape = false;
|
||||
value.push('"');
|
||||
}
|
||||
|
||||
'\\' if in_escape => {
|
||||
in_escape = false;
|
||||
value.push('\\');
|
||||
}
|
||||
|
||||
'\\' => {
|
||||
in_escape = true;
|
||||
}
|
||||
|
||||
'"' => {
|
||||
is_quoted = true;
|
||||
in_quotes = true;
|
||||
}
|
||||
|
||||
ch => {
|
||||
value.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
prev_index = index;
|
||||
prev_ch = ch;
|
||||
}
|
||||
|
||||
eos = true;
|
||||
|
||||
break 'outer if prev_ch == '\0' {
|
||||
// NULL values have zero characters
|
||||
// Empty strings are ""
|
||||
None
|
||||
} else {
|
||||
Some(prev_index)
|
||||
};
|
||||
};
|
||||
|
||||
let value = index.map(|index| {
|
||||
let mut s = &s[..=index];
|
||||
|
||||
if is_quoted {
|
||||
s = &s[1..s.len() - 1];
|
||||
}
|
||||
|
||||
PgValue::Text(s)
|
||||
});
|
||||
|
||||
let value = T::decode(value)?;
|
||||
|
||||
if !eos {
|
||||
*s = &s[index.unwrap_or(0) + 2..];
|
||||
} else {
|
||||
*s = "";
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_pg_record_for_tuple {
|
||||
($( $idx:ident : $T:ident ),+) => {
|
||||
impl<$($T,)+> Type<Postgres> for ($($T,)+) {
|
||||
#[inline]
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo {
|
||||
id: TypeId(2249),
|
||||
name: Some("RECORD".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, $($T,)+> Decode<'de, Postgres> for ($($T,)+)
|
||||
where
|
||||
$($T: crate::types::Type<Postgres>,)+
|
||||
$($T: crate::decode::Decode<'de, Postgres>,)+
|
||||
{
|
||||
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> {
|
||||
let mut decoder = PgRecordDecoder::new(value)?;
|
||||
|
||||
$(let $idx: $T = decoder.decode()?;)+
|
||||
|
||||
Ok(($($idx,)+))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2, _3: T3);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2, _3: T3, _4: T4);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2, _3: T3, _4: T4, _5: T5);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6);
|
||||
|
||||
impl_pg_record_for_tuple!(_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7);
|
||||
|
||||
impl_pg_record_for_tuple!(
|
||||
_1: T1,
|
||||
_2: T2,
|
||||
_3: T3,
|
||||
_4: T4,
|
||||
_5: T5,
|
||||
_6: T6,
|
||||
_7: T7,
|
||||
_8: T8
|
||||
);
|
||||
|
||||
impl_pg_record_for_tuple!(
|
||||
_1: T1,
|
||||
_2: T2,
|
||||
_3: T3,
|
||||
_4: T4,
|
||||
_5: T5,
|
||||
_6: T6,
|
||||
_7: T7,
|
||||
_8: T8,
|
||||
_9: T9
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_encode_field() {
|
||||
let value = "Foo Bar";
|
||||
let mut raw_encoded = Vec::new();
|
||||
<&str as Encode<Postgres>>::encode(&value, &mut raw_encoded);
|
||||
let mut field_encoded = Vec::new();
|
||||
|
||||
let mut encoder = PgRecordEncoder::new(&mut field_encoded);
|
||||
encoder.encode(&value);
|
||||
|
||||
// check oid
|
||||
let oid = <&str as Type<Postgres>>::type_info().oid();
|
||||
let field_encoded_oid = u32::from_be_bytes(field_encoded[4..8].try_into().unwrap());
|
||||
assert_eq!(oid, field_encoded_oid);
|
||||
|
||||
// check length
|
||||
let field_encoded_length = u32::from_be_bytes(field_encoded[8..12].try_into().unwrap());
|
||||
assert_eq!(raw_encoded.len(), field_encoded_length as usize);
|
||||
|
||||
// check data
|
||||
assert_eq!(raw_encoded, &field_encoded[12..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_field() {
|
||||
let value = "Foo Bar".to_string();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut encoder = PgRecordEncoder::new(&mut buf);
|
||||
encoder.encode(&value);
|
||||
|
||||
let mut buf = buf.as_slice();
|
||||
let mut decoder = PgRecordDecoder::new(Some(PgValue::Binary(buf))).unwrap();
|
||||
|
||||
let value_decoded: String = decoder.decode().unwrap();
|
||||
assert_eq!(value_decoded, value);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use libsqlite3_sys::{
|
||||
};
|
||||
|
||||
use crate::arguments::Arguments;
|
||||
use crate::encode::Encode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
use crate::sqlite::statement::Statement;
|
||||
use crate::sqlite::Sqlite;
|
||||
use crate::sqlite::SqliteError;
|
||||
@@ -63,7 +63,9 @@ impl Arguments for SqliteArguments {
|
||||
where
|
||||
T: Encode<Self::Database> + Type<Self::Database>,
|
||||
{
|
||||
value.encode(&mut self.values);
|
||||
if let IsNull::Yes = value.encode_nullable(&mut self.values) {
|
||||
self.values.push(SqliteArgumentValue::Null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user