[postgres] Initial experiment with FromSql

This commit is contained in:
Ryan Leckey 2019-08-05 08:07:45 -07:00
parent ebb3588d30
commit c67f751968
11 changed files with 246 additions and 131 deletions

View File

@ -58,15 +58,15 @@ async fn main() -> io::Result<()> {
// println!(" :: insert");
let row = conn
.prepare("SELECT pg_typeof($1), pg_typeof($2)")
.bind(20)
.bind_as::<sqlx::postgres::types::BigInt, _>(10)
.prepare("SELECT $1")
.bind("We're cooking with fire now")
.get()
.await?;
.await?
.unwrap();
println!("{:?}", row);
let value: String = row.get(0);
// println!(" :: select");
println!(" - {}", value);
// conn.prepare("SELECT id FROM users")
// .select()

View File

@ -22,4 +22,5 @@ pub mod postgres;
#[macro_use]
pub mod macros;
pub mod row;
pub mod types;

View File

@ -1,14 +1,17 @@
use super::prepare::Prepare;
use crate::postgres::protocol::{self, DataRow, Message};
use crate::{
postgres::protocol::{self, DataRow, Message},
row::Row,
};
use std::io;
impl<'a, 'b> Prepare<'a, 'b> {
pub async fn get(self) -> io::Result<Option<DataRow>> {
pub async fn get(self) -> io::Result<Option<Row>> {
let conn = self.finish();
conn.flush().await?;
let mut row: Option<DataRow> = None;
let mut raw: Option<DataRow> = None;
while let Some(message) = conn.receive().await? {
match message {
@ -21,13 +24,13 @@ impl<'a, 'b> Prepare<'a, 'b> {
Message::DataRow(data_row) => {
// note: because we used `EXECUTE 1` this will only execute once
row = Some(data_row);
raw = Some(data_row);
}
Message::CommandComplete(_) => {}
Message::ReadyForQuery(_) => {
return Ok(row);
return Ok(raw.map(Row));
}
message => {

View File

@ -50,7 +50,7 @@ impl<'a, 'b> Prepare<'a, 'b> {
formats: self.bind.formats(),
values_len: self.bind.values_len(),
values: self.bind.values(),
result_formats: &[],
result_formats: &[1],
});
self.connection.write(protocol::Execute {

View File

@ -1,5 +1,5 @@
mod connection;
pub use connection::Connection;
mod protocol;
pub(crate) mod protocol;
pub mod types;

View File

@ -0,0 +1,25 @@
use crate::types::{SqlType, ToSql, ToSqlAs, FromSql};
pub struct Bool;
impl SqlType for Bool {
const OID: u32 = 16;
}
impl ToSql for bool {
type Type = Bool;
}
impl ToSqlAs<Bool> for bool {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.push(self as u8);
}
}
impl FromSql<Bool> for bool {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
buf[0] != 0
}
}

View File

@ -0,0 +1,31 @@
use crate::types::{ToSql, ToSqlAs, FromSql, 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 ToSql for String {
type Type = Text;
}
impl ToSqlAs<Text> for String {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self.as_bytes());
}
}
impl FromSql<Text> for String {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
// Using lossy here as it should be impossible to get non UTF8 data here
String::from_utf8_lossy(buf).into()
}
}

View File

@ -1,119 +1,5 @@
use crate::types::{SqlType, ToSql, ToSqlAs};
mod character;
mod numeric;
mod boolean;
// TODO: Generalize by Backend and move common types to crate [sqlx::types]
// Character
// https://www.postgresql.org/docs/devel/datatype-character.html
pub struct Text;
impl SqlType for Text {
const OID: u32 = 25;
}
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());
}
}
// Numeric
// https://www.postgresql.org/docs/devel/datatype-numeric.html
// i16
pub struct SmallInt;
impl SqlType for SmallInt {
const OID: u32 = 21;
}
impl ToSql for i16 {
type Type = SmallInt;
}
impl ToSqlAs<SmallInt> for i16 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
// i32
pub struct Int;
impl SqlType for Int {
const OID: u32 = 23;
}
impl ToSql for i32 {
type Type = Int;
}
impl ToSqlAs<Int> for i32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
// i64
pub struct BigInt;
impl SqlType for BigInt {
const OID: u32 = 20;
}
impl ToSql for i64 {
type Type = BigInt;
}
impl ToSqlAs<BigInt> for i64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
// decimal?
// TODO pub struct Decimal;
// f32
pub struct Real;
impl SqlType for Real {
const OID: u32 = 700;
}
impl ToSql for f32 {
type Type = Real;
}
impl ToSqlAs<Real> for f32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i32).to_sql(buf);
}
}
// f64
pub struct Double;
impl SqlType for Double {
const OID: u32 = 701;
}
impl ToSql for f64 {
type Type = Double;
}
impl ToSqlAs<Double> for f64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i64).to_sql(buf);
}
}
pub use self::boolean::Bool;

View File

@ -0,0 +1,92 @@
use crate::types::{ToSql, ToSqlAs, FromSql, SmallInt, Int, BigInt, Real, Double};
use byteorder::{BigEndian, ByteOrder};
impl ToSql for i16 {
type Type = SmallInt;
}
impl ToSqlAs<SmallInt> for i16 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl FromSql<SmallInt> for i16 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i16(buf)
}
}
impl ToSql for i32 {
type Type = Int;
}
impl ToSqlAs<Int> for i32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl FromSql<Int> for i32 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i32(buf)
}
}
impl ToSql for i64 {
type Type = BigInt;
}
impl ToSqlAs<BigInt> for i64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl FromSql<BigInt> for i64 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
BigEndian::read_i64(buf)
}
}
impl ToSql for f32 {
type Type = Real;
}
impl ToSqlAs<Real> for f32 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i32).to_sql(buf);
}
}
impl FromSql<BigInt> for f32 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
f32::from_bits(i32::from_sql(buf) as u32)
}
}
impl ToSql for f64 {
type Type = Double;
}
impl ToSqlAs<Double> for f64 {
#[inline]
fn to_sql(self, buf: &mut Vec<u8>) {
(self.to_bits() as i64).to_sql(buf);
}
}
impl FromSql<Double> for f64 {
#[inline]
fn from_sql(buf: &[u8]) -> Self {
f64::from_bits(i64::from_sql(buf) as u64)
}
}

17
src/row.rs Normal file
View File

@ -0,0 +1,17 @@
use crate::{
postgres::protocol::DataRow,
types::{FromSql, SqlType},
};
// TODO: Make this generic over backend
pub struct Row(pub(crate) DataRow);
impl Row {
pub fn get<ST, T>(&self, index: usize) -> T
where
ST: SqlType,
T: FromSql<ST>,
{
T::from_sql(self.0.get(index).unwrap())
}
}

View File

@ -1,6 +1,10 @@
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: Make generic over backend
pub trait SqlType {
// FIXME: This is a postgres thing
const OID: u32;
@ -14,3 +18,59 @@ pub trait ToSql {
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;
}
// Character types
// All character types (VARCHAR, CHAR, TEXT, etc.) are represented equivalently in binary and all fold
// to this `Text` type.
pub struct Text;
impl SqlType for Text {
// FIXME: This is postgres-specific
const OID: u32 = 25;
}
// Numeric types
// i16
pub struct SmallInt;
impl SqlType for SmallInt {
const OID: u32 = 21;
}
// i32
pub struct Int;
impl SqlType for Int {
const OID: u32 = 23;
}
// i64
pub struct BigInt;
impl SqlType for BigInt {
const OID: u32 = 20;
}
// decimal?
// TODO pub struct Decimal;
// f32
pub struct Real;
impl SqlType for Real {
const OID: u32 = 700;
}
// f64
pub struct Double;
impl SqlType for Double {
const OID: u32 = 701;
}