Generalize ToSql, AsSql, FromSql, and FromSql over Backend

This commit is contained in:
Ryan Leckey 2019-08-06 08:58:51 -07:00
parent 0b98527550
commit 1db85d1069
16 changed files with 437 additions and 149 deletions

8
src/backend.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::row::RawRow;
pub trait Backend {
type RawRow: RawRow;
/// The type used to represent metadata associated with a SQL type.
type TypeMetadata;
}

27
src/deserialize.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::{
backend::Backend,
types::{AsSql, SqlType},
};
// TODO: Allow from_sql to return an error (that can be unified)
// TODO: Consider using a RawValue wrapper type instead of exposing raw bytes (as different back-ends may want to expose different data here.. maybe?)
pub trait FromSql<B, ST>: AsSql<B>
where
B: Backend,
ST: SqlType<B>,
{
fn from_sql(raw: Option<&[u8]>) -> Self;
}
impl<B, ST, T> FromSql<B, ST> for Option<T>
where
B: Backend,
ST: SqlType<B>,
T: FromSql<B, ST>,
{
#[inline]
fn from_sql(raw: Option<&[u8]>) -> Self {
Some(T::from_sql(Some(raw?)))
}
}

View File

@ -22,5 +22,8 @@ pub mod postgres;
#[macro_use]
pub mod macros;
pub mod backend;
pub mod deserialize;
pub mod row;
pub mod serialize;
pub mod types;

View File

@ -1,6 +1,9 @@
use super::prepare::Prepare;
use crate::{
postgres::protocol::{self, DataRow, Message},
postgres::{
protocol::{self, DataRow, Message},
Postgres,
},
row::{FromRow, Row},
types::SqlType,
};
@ -12,19 +15,19 @@ impl<'a, 'b> Prepare<'a, 'b> {
#[inline]
pub async fn get<Record, T>(self) -> io::Result<T>
where
T: FromRow<Record>,
T: FromRow<Postgres, Record>,
{
Ok(T::from_row(self.get_raw().await?.unwrap()))
}
// TODO: Better name?
// TODO: Should this be public?
async fn get_raw(self) -> io::Result<Option<Row>> {
async fn get_raw(self) -> io::Result<Option<Row<Postgres>>> {
let conn = self.finish();
conn.flush().await?;
let mut row: Option<Row> = None;
let mut row: Option<Row<Postgres>> = None;
while let Some(message) = conn.receive().await? {
match message {
@ -37,7 +40,7 @@ impl<'a, 'b> Prepare<'a, 'b> {
Message::DataRow(body) => {
// note: because we used `EXECUTE 1` this will only execute once
row = Some(Row(body));
row = Some(Row::<Postgres>(body));
}
Message::CommandComplete(_) => {}

View File

@ -1,7 +1,11 @@
use super::Connection;
use crate::{
postgres::protocol::{self, BindValues},
types::{SqlType, ToSql, ToSqlAs},
postgres::{
protocol::{self, BindValues},
Postgres,
},
serialize::ToSql,
types::{AsSql, SqlType},
};
pub struct Prepare<'a, 'b> {
@ -23,16 +27,16 @@ pub fn prepare<'a, 'b>(connection: &'a mut Connection, query: &'b str) -> Prepar
impl<'a, 'b> Prepare<'a, 'b> {
#[inline]
pub fn bind<T: ToSql>(mut self, value: T) -> Self
pub fn bind<T: AsSql<Postgres>>(mut self, value: T) -> Self
where
T: ToSqlAs<<T as ToSql>::Type>,
T: ToSql<Postgres, <T as AsSql<Postgres>>::Type>,
{
self.bind.add(value);
self
}
#[inline]
pub fn bind_as<ST: SqlType, T: ToSqlAs<ST>>(mut self, value: T) -> Self {
pub fn bind_as<ST: SqlType<Postgres>, T: ToSql<Postgres, ST>>(mut self, value: T) -> Self {
self.bind.add_as::<ST, T>(value);
self
}

View File

@ -1,6 +1,9 @@
use super::prepare::Prepare;
use crate::{
postgres::protocol::{self, DataRow, Message},
postgres::{
protocol::{self, DataRow, Message},
Postgres,
},
row::{FromRow, Row},
};
use futures::{stream, Stream, TryStreamExt};
@ -12,14 +15,14 @@ impl<'a, 'b> Prepare<'a, 'b> {
self,
) -> impl Stream<Item = Result<T, io::Error>> + 'a + Unpin
where
T: FromRow<Record>,
T: FromRow<Postgres, Record>,
{
self.select_raw().map_ok(T::from_row)
}
// TODO: Better name?
// TODO: Should this be public?
fn select_raw(self) -> impl Stream<Item = Result<Row, io::Error>> + 'a + Unpin {
fn select_raw(self) -> impl Stream<Item = Result<Row<Postgres>, io::Error>> + 'a + Unpin {
// FIXME: Manually implement Stream on a new type to avoid the unfold adapter
stream::unfold(self.finish(), |conn| {
Box::pin(async {
@ -43,7 +46,7 @@ impl<'a, 'b> Prepare<'a, 'b> {
}
Message::DataRow(row) => {
break Some((Ok(Row(row)), conn));
break Some((Ok(Row::<Postgres>(row)), conn));
}
Message::CommandComplete(_) => {}

View File

@ -1,5 +1,16 @@
use crate::backend::Backend;
mod connection;
pub use connection::Connection;
pub(crate) mod protocol;
pub mod types;
pub struct Postgres;
impl Backend for Postgres {
type RawRow = protocol::DataRow;
type TypeMetadata = types::TypeMetadata;
}

View File

@ -1,5 +1,9 @@
use super::{BufMut, Encode};
use crate::types::{SqlType, ToSql, ToSqlAs};
use crate::{
postgres::Postgres,
serialize::ToSql,
types::{AsSql, SqlType},
};
use byteorder::{BigEndian, ByteOrder};
const TEXT: i16 = 0;
@ -24,18 +28,18 @@ impl BindValues {
}
#[inline]
pub fn add<T: ToSql>(&mut self, value: T)
pub fn add<T: AsSql<Postgres>>(&mut self, value: T)
where
T: ToSqlAs<<T as ToSql>::Type>,
T: ToSql<Postgres, <T as AsSql<Postgres>>::Type>,
{
self.add_as::<T::Type, T>(value);
}
pub fn add_as<ST: SqlType, T: ToSqlAs<ST>>(&mut self, value: T) {
pub fn add_as<ST: SqlType<Postgres>, T: ToSql<Postgres, ST>>(&mut self, value: T) {
// TODO: When/if we receive types that do _not_ support BINARY, we need to check here
// TODO: There is no need to be explicit unless we are expecting mixed BINARY / TEXT
self.types.push(ST::OID as i32);
self.types.push(ST::metadata().oid as i32);
let pos = self.values.len();
self.values.put_int_32(0); // skip over len
@ -120,7 +124,7 @@ impl Encode for Bind<'_> {
mod tests {
use super::{Bind, BindValues, BufMut, Encode};
const BIND: &[u8] = b"B\0\0\0\x16\0\0\0\0\0\x02\0\0\0\x011\0\0\0\x012\0\0";
const BIND: &[u8] = b"B\0\0\0\x18\0\0\0\x01\0\x01\0\x02\0\0\0\x011\0\0\0\x012\0\0";
#[test]
fn it_encodes_bind_for_two() {

View File

@ -1,4 +1,5 @@
use super::Decode;
use crate::row::RawRow;
use bytes::Bytes;
use std::{
convert::TryInto,
@ -46,19 +47,19 @@ impl Decode for DataRow {
}
}
impl DataRow {
impl RawRow for DataRow {
#[inline]
pub fn is_empty(&self) -> bool {
fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
fn len(&self) -> usize {
self.ranges.len()
}
#[inline]
pub fn get(&self, index: usize) -> Option<&[u8]> {
fn get(&self, index: usize) -> Option<&[u8]> {
Some(&self.buf[self.ranges[index].clone()?])
}
}
@ -81,6 +82,7 @@ impl Debug for DataRow {
#[cfg(test)]
mod tests {
use super::{DataRow, Decode};
use crate::row::RawRow;
use bytes::Bytes;
use std::io;

View File

@ -1,25 +1,39 @@
use crate::types::{FromSql, SqlType, ToSql, ToSqlAs};
use super::TypeMetadata;
use crate::{
deserialize::FromSql,
postgres::Postgres,
serialize::{IsNull, ToSql},
types::{AsSql, SqlType},
};
pub struct Bool;
impl SqlType for Bool {
const OID: u32 = 16;
impl SqlType<Postgres> for Bool {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 16,
array_oid: 1000,
}
}
}
impl ToSql for bool {
impl AsSql<Postgres> for bool {
type Type = Bool;
}
impl ToSqlAs<Bool> for bool {
impl ToSql<Postgres, Bool> for bool {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.push(self as u8);
IsNull::No
}
}
impl FromSql<Bool> for bool {
impl FromSql<Postgres, Bool> for bool {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
buf[0] != 0
fn from_sql(buf: Option<&[u8]>) -> Self {
// TODO: Handle optionals
buf.unwrap()[0] != 0
}
}

View File

@ -1,31 +1,51 @@
use crate::types::{FromSql, Text, ToSql, ToSqlAs};
use super::TypeMetadata;
use crate::{
deserialize::FromSql,
postgres::Postgres,
serialize::{IsNull, ToSql},
types::{AsSql, SqlType, Text},
};
impl ToSql for &'_ str {
type Type = Text;
}
impl ToSqlAs<Text> for &'_ str {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self.as_bytes());
impl SqlType<Postgres> for Text {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 25,
array_oid: 1009,
}
}
}
impl ToSql for String {
impl AsSql<Postgres> for &'_ str {
type Type = Text;
}
impl ToSqlAs<Text> for String {
impl ToSql<Postgres, Text> for &'_ str {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(self.as_bytes());
IsNull::No
}
}
impl FromSql<Text> for String {
impl AsSql<Postgres> for String {
type Type = Text;
}
impl ToSql<Postgres, Text> for String {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(self.as_bytes());
IsNull::No
}
}
impl FromSql<Postgres, Text> for String {
#[inline]
fn from_sql(buf: Option<&[u8]>) -> Self {
// TODO: Handle optionals
// Using lossy here as it should be impossible to get non UTF8 data here
String::from_utf8_lossy(buf).into()
String::from_utf8_lossy(buf.unwrap()).into()
}
}

View File

@ -3,3 +3,8 @@ mod character;
mod numeric;
pub use self::boolean::Bool;
pub struct TypeMetadata {
pub oid: u32,
pub array_oid: u32,
}

View File

@ -1,92 +1,152 @@
use crate::types::{BigInt, Double, FromSql, Int, Real, SmallInt, ToSql, ToSqlAs};
use super::TypeMetadata;
use crate::{
deserialize::FromSql,
postgres::Postgres,
serialize::{IsNull, ToSql},
types::{AsSql, BigInt, Double, Int, Real, SmallInt, SqlType},
};
use byteorder::{BigEndian, ByteOrder};
impl ToSql for i16 {
impl SqlType<Postgres> for SmallInt {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 21,
array_oid: 1005,
}
}
}
impl AsSql<Postgres> for i16 {
type Type = SmallInt;
}
impl ToSqlAs<SmallInt> for i16 {
impl ToSql<Postgres, SmallInt> for i16 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(&self.to_be_bytes());
IsNull::No
}
}
impl FromSql<SmallInt> for i16 {
impl FromSql<Postgres, SmallInt> for i16 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i16(buf)
fn from_sql(buf: Option<&[u8]>) -> Self {
// TODO: Handle optionals
BigEndian::read_i16(buf.unwrap())
}
}
impl ToSql for i32 {
impl SqlType<Postgres> for Int {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 23,
array_oid: 1007,
}
}
}
impl AsSql<Postgres> for i32 {
type Type = Int;
}
impl ToSqlAs<Int> for i32 {
impl ToSql<Postgres, Int> for i32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(&self.to_be_bytes());
IsNull::No
}
}
impl FromSql<Int> for i32 {
impl FromSql<Postgres, Int> for i32 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i32(buf)
fn from_sql(buf: Option<&[u8]>) -> Self {
// TODO: Handle optionals
BigEndian::read_i32(buf.unwrap())
}
}
impl ToSql for i64 {
impl SqlType<Postgres> for BigInt {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 20,
array_oid: 1016,
}
}
}
impl AsSql<Postgres> for i64 {
type Type = BigInt;
}
impl ToSqlAs<BigInt> for i64 {
impl ToSql<Postgres, BigInt> for i64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(&self.to_be_bytes());
IsNull::No
}
}
impl FromSql<BigInt> for i64 {
impl FromSql<Postgres, BigInt> for i64 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i64(buf)
fn from_sql(buf: Option<&[u8]>) -> Self {
// TODO: Handle optionals
BigEndian::read_i64(buf.unwrap())
}
}
impl ToSql for f32 {
impl SqlType<Postgres> for Real {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 700,
array_oid: 1021,
}
}
}
impl AsSql<Postgres> for f32 {
type Type = Real;
}
impl ToSqlAs<Real> for f32 {
impl ToSql<Postgres, Real> for f32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i32).to_sql(buf);
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
(self.to_bits() as i32).to_sql(buf)
}
}
impl FromSql<BigInt> for f32 {
impl FromSql<Postgres, BigInt> for f32 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
fn from_sql(buf: Option<&[u8]>) -> Self {
f32::from_bits(i32::from_sql(buf) as u32)
}
}
impl ToSql for f64 {
impl SqlType<Postgres> for Double {
fn metadata() -> TypeMetadata {
TypeMetadata {
oid: 701,
array_oid: 1022,
}
}
}
impl AsSql<Postgres> for f64 {
type Type = Double;
}
impl ToSqlAs<Double> for f64 {
impl ToSql<Postgres, Double> for f64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i64).to_sql(buf);
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull {
(self.to_bits() as i64).to_sql(buf)
}
}
impl FromSql<Double> for f64 {
impl FromSql<Postgres, Double> for f64 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
fn from_sql(buf: Option<&[u8]>) -> Self {
f64::from_bits(i64::from_sql(buf) as u64)
}
}

View File

@ -1,57 +1,140 @@
use crate::{
postgres::protocol::DataRow,
types::{FromSql, SqlType},
backend::Backend,
deserialize::FromSql,
postgres::{protocol::DataRow, Postgres},
types::SqlType,
};
// TODO: Make this generic over backend
pub struct Row(pub(crate) DataRow);
pub trait RawRow {
fn is_empty(&self) -> bool;
impl Row {
fn len(&self) -> usize;
fn get(&self, index: usize) -> Option<&[u8]>;
}
pub struct Row<B>(pub(crate) B::RawRow)
where
B: Backend;
impl<B> Row<B>
where
B: Backend,
{
#[inline]
pub fn get<ST, T>(&self, index: usize) -> T
where
ST: SqlType,
T: FromSql<ST>,
ST: SqlType<B>,
T: FromSql<B, ST>,
{
T::from_sql(self.0.get(index).unwrap())
T::from_sql(self.0.get(index))
}
}
pub trait FromRow<Record> {
fn from_row(row: Row) -> Self;
pub trait FromRow<B, Record>
where
B: Backend,
{
fn from_row(row: Row<B>) -> Self;
}
impl<ST, T> FromRow<ST> for T
impl<B, ST, T> FromRow<B, ST> for T
where
ST: SqlType,
T: FromSql<ST>,
B: Backend,
ST: SqlType<B>,
T: FromSql<B, ST>,
{
#[inline]
fn from_row(row: Row) -> Self {
fn from_row(row: Row<B>) -> Self {
row.get::<ST, T>(0)
}
}
impl<ST1, T1> FromRow<(ST1,)> for (T1,)
where
ST1: SqlType,
T1: FromSql<ST1>,
{
#[inline]
fn from_row(row: Row) -> Self {
(row.get::<ST1, T1>(0),)
}
// TODO: Think of a better way to generate these tuple implementations
macro_rules! impl_from_row_tuple {
($B:ident: $( ($idx:tt) -> $T:ident, $ST:ident );+;) => {
impl<$($ST,)+ $($T,)+> FromRow<Postgres, ($($ST,)+)> for ($($T,)+)
where
$($ST: SqlType<Postgres>,)+
$($T: FromSql<Postgres, $ST>,)+
{
#[inline]
fn from_row(row: Row<$B>) -> Self {
($(row.get::<$ST, $T>($idx),)+)
}
}
};
}
impl<ST1, ST2, T1, T2> FromRow<(ST1, ST2)> for (T1, T2)
where
ST1: SqlType,
ST2: SqlType,
T1: FromSql<ST1>,
T2: FromSql<ST2>,
{
#[inline]
fn from_row(row: Row) -> Self {
(row.get::<ST1, T1>(0), row.get::<ST2, T2>(1))
}
}
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
(4) -> ST5, T5;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
(4) -> ST5, T5;
(5) -> ST6, T6;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
(4) -> ST5, T5;
(5) -> ST6, T6;
(6) -> ST7, T7;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
(4) -> ST5, T5;
(5) -> ST6, T6;
(6) -> ST7, T7;
(7) -> ST8, T8;
);
impl_from_row_tuple!(Postgres:
(0) -> ST1, T1;
(1) -> ST2, T2;
(2) -> ST3, T3;
(3) -> ST4, T4;
(4) -> ST5, T5;
(5) -> ST6, T6;
(6) -> ST7, T7;
(7) -> ST8, T8;
(8) -> ST9, T9;
);

36
src/serialize.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::{
backend::Backend,
types::{AsSql, SqlType},
};
/// Annotates the result of [ToSql] to differentiate between an empty value and a null value.
pub enum IsNull {
/// The value was null (and no data was written to the buffer).
Yes,
/// The value was not null.
///
/// This does not necessarily mean that any data was written to the buffer.
No,
}
/// Serializes a single value to be sent to the database.
pub trait ToSql<B, ST>: AsSql<B>
where
B: Backend,
ST: SqlType<B>,
{
fn to_sql(self, buf: &mut Vec<u8>) -> IsNull;
}
impl<B, ST, T> ToSql<B, ST> for Option<T>
where
B: Backend,
ST: SqlType<B>,
T: ToSql<B, ST>,
{
#[inline]
fn to_sql(self, _buf: &mut Vec<u8>) -> IsNull {
IsNull::Yes
}
}

View File

@ -1,27 +1,33 @@
use crate::backend::Backend;
pub use crate::postgres::types::*;
// TODO: Better name for ToSql/ToSqlAs. ToSqlAs is the _conversion_ trait.
// ToSql is type fallback for Rust/SQL (e.g., what is the probable SQL type for this Rust type)
// TODO: Does [AsSql] need to be generic over back-end ?
// TODO: Make generic over backend
pub trait SqlType {
// FIXME: This is a postgres thing
const OID: u32;
pub trait SqlType<B>
where
B: Backend,
{
// FIXME: This should be a const fn
fn metadata() -> B::TypeMetadata;
}
pub trait ToSql {
/// Defines the canonical SQL that the implementing Rust type represents.
/// This trait is used to map Rust types to SQL types when the explicit mapping is missing.
pub trait AsSql<B>
where
B: Backend,
{
/// SQL type that should be inferred from the implementing Rust type.
type Type: SqlType;
type Type: SqlType<B>;
}
pub trait ToSqlAs<T: SqlType>: ToSql {
fn to_sql(self, buf: &mut Vec<u8>);
}
pub trait FromSql<T: SqlType>: ToSql {
// TODO: Errors?
fn from_sql(buf: &[u8]) -> Self;
impl<B, T> AsSql<B> for Option<T>
where
B: Backend,
T: AsSql<B>,
{
type Type = T::Type;
}
// Character types
@ -30,33 +36,32 @@ pub trait FromSql<T: SqlType>: ToSql {
pub struct Text;
impl SqlType for Text {
// FIXME: This is postgres-specific
const OID: u32 = 25;
}
// impl SqlType for Text {
// const OID: u32 = 25;
// }
// Numeric types
// i16
pub struct SmallInt;
impl SqlType for SmallInt {
const OID: u32 = 21;
}
// impl SqlType for SmallInt {
// const OID: u32 = 21;
// }
// i32
pub struct Int;
impl SqlType for Int {
const OID: u32 = 23;
}
// impl SqlType for Int {
// const OID: u32 = 23;
// }
// i64
pub struct BigInt;
impl SqlType for BigInt {
const OID: u32 = 20;
}
// impl SqlType for BigInt {
// const OID: u32 = 20;
// }
// decimal?
// TODO pub struct Decimal;
@ -64,13 +69,13 @@ impl SqlType for BigInt {
// f32
pub struct Real;
impl SqlType for Real {
const OID: u32 = 700;
}
// impl SqlType for Real {
// const OID: u32 = 700;
// }
// f64
pub struct Double;
impl SqlType for Double {
const OID: u32 = 701;
}
// impl SqlType for Double {
// const OID: u32 = 701;
// }