Skip to content

Commit

Permalink
Initial implementation of "SELECT * FROM table"
Browse files Browse the repository at this point in the history
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
sgrif committed Aug 29, 2015
1 parent b6b6570 commit d936a6a
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 2 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
name = "yaqb"
version = "0.1.0"
authors = ["Sean Griffin <sean@thoughtbot.com>"]

[dependencies]
postgres = "0.9.*"
37 changes: 37 additions & 0 deletions src/connection/mod.rs
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())
}
}
92 changes: 90 additions & 2 deletions src/lib.rs
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(),
}
}
}
}
14 changes: 14 additions & 0 deletions src/query_source.rs
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;
}
29 changes: 29 additions & 0 deletions src/result.rs
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)
}
}
151 changes: 151 additions & 0 deletions src/types.rs
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,
}
}

0 comments on commit d936a6a

Please sign in to comment.