-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of "SELECT * FROM table"
This fleshes out the basic interfaces (minus compiler macros and plugins). Unfortunately, right now we are returning a `Vec` instead of a `Cursor` for returned values, as the structure of the postgres crate makes it impossible to abstract over the Statement, and not consume it immediately. This is an implementation problem, however, and shouldn't block figuring out the interface. We should, however, make sure that the final structs do not need to be loaded into memory all at once in the final version of this API.
- Loading branch information
Showing
6 changed files
with
324 additions
and
2 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,37 @@ | ||
extern crate postgres; | ||
|
||
use super::query_source::{Queriable, QuerySource}; | ||
use super::{Result, ConnectionResult}; | ||
use super::types::FromSql; | ||
use self::postgres::SslMode; | ||
|
||
pub struct Connection { | ||
internal_connection: postgres::Connection, | ||
} | ||
|
||
impl Connection { | ||
pub fn establish(database_url: &str) -> ConnectionResult<Connection> { | ||
let pgconn = try!(postgres::Connection::connect(database_url, &SslMode::None)); | ||
Ok(Connection { | ||
internal_connection: pgconn, | ||
}) | ||
} | ||
|
||
pub fn execute(&self, query: &str) -> Result<u64> { | ||
self.internal_connection.execute(query, &[]) | ||
.map_err(|e| e.into()) | ||
} | ||
|
||
pub fn query_all<T, U>(&self, source: &T) -> Result<Vec<U>> where | ||
T: QuerySource, | ||
U: Queriable<T>, | ||
{ | ||
let query = format!("SELECT {} FROM {}", source.select_clause(), source.from_clause()); | ||
let stmt = try!(self.internal_connection.prepare(&query)); | ||
let rows = try!(stmt.query(&[])); | ||
Ok(rows.into_iter().map(|row| { | ||
let values = U::Row::from_sql(&row, 0); | ||
U::build(values) | ||
}).collect()) | ||
} | ||
} |
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 |
---|---|---|
@@ -1,3 +1,91 @@ | ||
#[test] | ||
fn it_works() { | ||
pub mod types; | ||
|
||
mod query_source; | ||
mod connection; | ||
mod result; | ||
|
||
pub use result::*; | ||
pub use query_source::{QuerySource, Queriable}; | ||
pub use connection::Connection; | ||
|
||
#[cfg(test)] | ||
mod test_usage_without_macros_or_plugins { | ||
use super::{types, QuerySource, Queriable, Connection}; | ||
use std::env; | ||
|
||
#[derive(PartialEq, Eq, Debug)] | ||
struct User { | ||
id: i32, | ||
name: String, | ||
} | ||
|
||
struct UserTable; | ||
|
||
unsafe impl QuerySource for UserTable { | ||
type SqlType = (types::Serial, types::VarChar); | ||
|
||
fn select_clause(&self) -> &str { | ||
"*" | ||
} | ||
|
||
fn from_clause(&self) -> &str { | ||
"users" | ||
} | ||
} | ||
|
||
impl<QS> Queriable<QS> for User where | ||
QS: QuerySource, | ||
(i32, String): types::FromSql<QS::SqlType>, | ||
{ | ||
type Row = (i32, String); | ||
|
||
fn build(row: (i32, String)) -> Self { | ||
User { | ||
id: row.0, | ||
name: row.1, | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn it_can_perform_a_basic_query() { | ||
let connection_url = env::var("DATABASE_URL").ok() | ||
.expect("DATABASE_URL must be set in order to run tests"); | ||
let connection = Connection::establish(&connection_url) | ||
.unwrap(); | ||
|
||
let _t = TestTable::new(&connection, "users", | ||
"(id SERIAL PRIMARY KEY, name VARCHAR NOT NULL)"); | ||
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')") | ||
.unwrap(); | ||
|
||
let expected_users = vec![ | ||
User { id: 1, name: "Sean".to_string() }, | ||
User { id: 2, name: "Tess".to_string() }, | ||
]; | ||
let actual_users = connection.query_all(&UserTable).unwrap(); | ||
assert_eq!(expected_users, actual_users); | ||
} | ||
|
||
struct TestTable<'a> { | ||
connection: &'a Connection, | ||
name: String, | ||
} | ||
|
||
impl<'a> Drop for TestTable<'a> { | ||
fn drop(&mut self) { | ||
self.connection.execute(&format!("DROP TABLE {}", &self.name)) | ||
.unwrap(); | ||
} | ||
} | ||
|
||
impl<'a> TestTable<'a> { | ||
fn new(connection: &'a Connection, name: &str, values: &str) -> Self { | ||
connection.execute(&format!("CREATE TABLE {} {}", name, values)).unwrap(); | ||
TestTable { | ||
connection: connection, | ||
name: name.to_string(), | ||
} | ||
} | ||
} | ||
} |
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,14 @@ | ||
use super::types::{FromSql, NativeSqlType}; | ||
|
||
pub unsafe trait QuerySource { | ||
type SqlType: NativeSqlType; | ||
|
||
fn select_clause(&self) -> &str; | ||
fn from_clause(&self) -> &str; | ||
} | ||
|
||
pub trait Queriable<QS: QuerySource> { | ||
type Row: FromSql<QS::SqlType>; | ||
|
||
fn build(row: Self::Row) -> Self; | ||
} |
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,29 @@ | ||
extern crate postgres; | ||
|
||
use std::result; | ||
use std::convert::From; | ||
|
||
#[derive(Debug)] | ||
pub enum Error { | ||
NativeError(postgres::error::Error), | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum ConnectionError { | ||
NativeError(postgres::error::ConnectError), | ||
} | ||
|
||
pub type Result<T> = result::Result<T, Error>; | ||
pub type ConnectionResult<T> = result::Result<T, ConnectionError>; | ||
|
||
impl From<postgres::error::Error> for Error { | ||
fn from(e: postgres::error::Error) -> Self { | ||
Error::NativeError(e) | ||
} | ||
} | ||
|
||
impl From<postgres::error::ConnectError> for ConnectionError { | ||
fn from(e: postgres::error::ConnectError) -> Self { | ||
ConnectionError::NativeError(e) | ||
} | ||
} |
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,151 @@ | ||
extern crate postgres; | ||
|
||
use self::postgres::rows::Row; | ||
|
||
pub struct Serial; | ||
pub struct VarChar; | ||
|
||
pub trait NativeSqlType {} | ||
|
||
impl NativeSqlType for Serial {} | ||
impl NativeSqlType for VarChar {} | ||
|
||
pub trait FromSql<A: NativeSqlType> { | ||
fn from_sql(row: &Row, idx: usize) -> Self; | ||
} | ||
|
||
impl FromSql<Serial> for i32 { | ||
fn from_sql(row: &Row, idx: usize) -> Self { | ||
row.get(idx) | ||
} | ||
} | ||
impl FromSql<VarChar> for String { | ||
fn from_sql(row: &Row, idx: usize) -> Self { | ||
row.get(idx) | ||
} | ||
} | ||
|
||
macro_rules! tuple_impls { | ||
($( | ||
$Tuple:ident { | ||
$(($idx:expr) -> $T:ident, $ST:ident,)+ | ||
} | ||
)+) => { | ||
$( | ||
impl<$($T:NativeSqlType),+> NativeSqlType for ($($T,)+) {} | ||
impl<$($T),+,$($ST),+> FromSql<($($ST),+)> for ($($T),+) where | ||
$($T: FromSql<$ST>),+, | ||
$($ST: NativeSqlType),+ | ||
{ | ||
fn from_sql(row: &Row, idx: usize) -> Self { | ||
( | ||
$($T::from_sql(row, idx + $idx)),+ | ||
) | ||
} | ||
} | ||
)+ | ||
} | ||
} | ||
|
||
tuple_impls! { | ||
T2 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
} | ||
T3 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
} | ||
T4 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
} | ||
T5 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
} | ||
T6 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
} | ||
T7 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
} | ||
T8 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
(7) -> H, SH, | ||
} | ||
T9 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
(7) -> H, SH, | ||
(8) -> I, SI, | ||
} | ||
T10 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
(7) -> H, SH, | ||
(8) -> I, SI, | ||
(9) -> J, SJ, | ||
} | ||
T11 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
(7) -> H, SH, | ||
(8) -> I, SI, | ||
(9) -> J, SJ, | ||
(10) -> K, SK, | ||
} | ||
T12 { | ||
(0) -> A, SA, | ||
(1) -> B, SB, | ||
(2) -> C, SC, | ||
(3) -> D, SD, | ||
(4) -> E, SE, | ||
(5) -> F, SF, | ||
(6) -> G, SG, | ||
(7) -> H, SH, | ||
(8) -> I, SI, | ||
(9) -> J, SJ, | ||
(10) -> K, SK, | ||
(11) -> L, SL, | ||
} | ||
} | ||
|