diff --git a/examples/postgres.rs b/examples/postgres.rs index 51b81901..a70a04ec 100644 --- a/examples/postgres.rs +++ b/examples/postgres.rs @@ -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::(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() diff --git a/src/lib.rs b/src/lib.rs index 893b6b43..4b929d90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,4 +22,5 @@ pub mod postgres; #[macro_use] pub mod macros; +pub mod row; pub mod types; diff --git a/src/postgres/connection/get.rs b/src/postgres/connection/get.rs index 5f64b4d6..4fbf99c6 100644 --- a/src/postgres/connection/get.rs +++ b/src/postgres/connection/get.rs @@ -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> { + pub async fn get(self) -> io::Result> { let conn = self.finish(); conn.flush().await?; - let mut row: Option = None; + let mut raw: Option = 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 => { diff --git a/src/postgres/connection/prepare.rs b/src/postgres/connection/prepare.rs index b409c1a1..89ee593b 100644 --- a/src/postgres/connection/prepare.rs +++ b/src/postgres/connection/prepare.rs @@ -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 { diff --git a/src/postgres/mod.rs b/src/postgres/mod.rs index 8d959ded..63964864 100644 --- a/src/postgres/mod.rs +++ b/src/postgres/mod.rs @@ -1,5 +1,5 @@ mod connection; pub use connection::Connection; -mod protocol; +pub(crate) mod protocol; pub mod types; diff --git a/src/postgres/types/boolean.rs b/src/postgres/types/boolean.rs new file mode 100644 index 00000000..5bdc7118 --- /dev/null +++ b/src/postgres/types/boolean.rs @@ -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 for bool { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.push(self as u8); + } +} + +impl FromSql for bool { + #[inline] + fn from_sql(buf: &[u8]) -> Self { + buf[0] != 0 + } +} diff --git a/src/postgres/types/character.rs b/src/postgres/types/character.rs new file mode 100644 index 00000000..669bbd04 --- /dev/null +++ b/src/postgres/types/character.rs @@ -0,0 +1,31 @@ +use crate::types::{ToSql, ToSqlAs, FromSql, Text}; + +impl ToSql for &'_ str { + type Type = Text; +} + +impl ToSqlAs for &'_ str { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.extend_from_slice(self.as_bytes()); + } +} + +impl ToSql for String { + type Type = Text; +} + +impl ToSqlAs for String { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.extend_from_slice(self.as_bytes()); + } +} + +impl FromSql 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() + } +} diff --git a/src/postgres/types/mod.rs b/src/postgres/types/mod.rs index cd0a140d..a782a008 100644 --- a/src/postgres/types/mod.rs +++ b/src/postgres/types/mod.rs @@ -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 for &'_ str { - #[inline] - fn to_sql(self, buf: &mut Vec) { - 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 for i16 { - #[inline] - fn to_sql(self, buf: &mut Vec) { - 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 for i32 { - #[inline] - fn to_sql(self, buf: &mut Vec) { - 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 for i64 { - #[inline] - fn to_sql(self, buf: &mut Vec) { - 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 for f32 { - #[inline] - fn to_sql(self, buf: &mut Vec) { - (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 for f64 { - #[inline] - fn to_sql(self, buf: &mut Vec) { - (self.to_bits() as i64).to_sql(buf); - } -} +pub use self::boolean::Bool; diff --git a/src/postgres/types/numeric.rs b/src/postgres/types/numeric.rs new file mode 100644 index 00000000..0bc5ade5 --- /dev/null +++ b/src/postgres/types/numeric.rs @@ -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 for i16 { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.extend_from_slice(&self.to_be_bytes()); + } +} + +impl FromSql for i16 { + #[inline] + fn from_sql(buf: &[u8]) -> Self { + BigEndian::read_i16(buf) + } +} + +impl ToSql for i32 { + type Type = Int; +} + +impl ToSqlAs for i32 { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.extend_from_slice(&self.to_be_bytes()); + } +} + +impl FromSql for i32 { + #[inline] + fn from_sql(buf: &[u8]) -> Self { + BigEndian::read_i32(buf) + } +} + +impl ToSql for i64 { + type Type = BigInt; +} + +impl ToSqlAs for i64 { + #[inline] + fn to_sql(self, buf: &mut Vec) { + buf.extend_from_slice(&self.to_be_bytes()); + } +} + +impl FromSql for i64 { + #[inline] + fn from_sql(buf: &[u8]) -> Self { + BigEndian::read_i64(buf) + } +} + +impl ToSql for f32 { + type Type = Real; +} + +impl ToSqlAs for f32 { + #[inline] + fn to_sql(self, buf: &mut Vec) { + (self.to_bits() as i32).to_sql(buf); + } +} + +impl FromSql 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 for f64 { + #[inline] + fn to_sql(self, buf: &mut Vec) { + (self.to_bits() as i64).to_sql(buf); + } +} + +impl FromSql for f64 { + #[inline] + fn from_sql(buf: &[u8]) -> Self { + f64::from_bits(i64::from_sql(buf) as u64) + } +} diff --git a/src/row.rs b/src/row.rs new file mode 100644 index 00000000..3a272cec --- /dev/null +++ b/src/row.rs @@ -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(&self, index: usize) -> T + where + ST: SqlType, + T: FromSql, + { + T::from_sql(self.0.get(index).unwrap()) + } +} diff --git a/src/types.rs b/src/types.rs index 778781ac..8c897e1a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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: ToSql { fn to_sql(self, buf: &mut Vec); } + +pub trait FromSql: 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; +}