Skip to content

Commit

Permalink
Add the foundation for select clauses
Browse files Browse the repository at this point in the history
This function is marked as unsafe, as we cannot verify that the types of
the selected columns are actually correct. The safe wrapper for this
will end up being a compile-time macro, which verifies the types of the
selected columns (this is expected to still allow for an arbitrary SQL
string).

For implementing the safe wrapper, the biggest question is how to get
the from clause statically at compile time. Hopefully switching to
associated constants will allow us to grab the from clause of the source
at compile time. The expected implementation will then create a prepared
statement with the query, and get type information from that.

I had attempted to have `FromSql` imply `Queriable`, however I received
an error about conflicting implementations. The written impl was:

```rust
impl<T, QS> Queriable<QS> for T where
    QS: QuerySource,
    T: FromSql<QS::SqlType>,
{
    type Row = Self;

    fn build(row: Self::Row) -> Self {
        row
    }
}
```

However, this led to an error about the impl for `User` conflicting.
Presumably since we're grabbing an associated type from `QuerySource`,
the compiler can't actually verify that there is no implementation of
`FromSql` for `User`? If so, that is a strange limitation.

As a result of this issue, I am using a macro to define the invocation
instead.
  • Loading branch information
sgrif committed Aug 29, 2015
1 parent d936a6a commit 2ba8bf4
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 38 deletions.
51 changes: 28 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,23 @@ mod test_usage_without_macros_or_plugins {
}
}

#[test]
fn it_can_perform_a_basic_query() {
fn connection() -> Connection {
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)
let result = Connection::establish(&connection_url).unwrap();
result.execute("BEGIN").unwrap();
result
}

fn setup_users_table(connection: &Connection) {
connection.execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR NOT NULL)")
.unwrap();
}

let _t = TestTable::new(&connection, "users",
"(id SERIAL PRIMARY KEY, name VARCHAR NOT NULL)");
#[test]
fn it_can_perform_a_basic_query() {
let connection = connection();
setup_users_table(&connection);
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();

Expand All @@ -67,25 +75,22 @@ mod test_usage_without_macros_or_plugins {
assert_eq!(expected_users, actual_users);
}

struct TestTable<'a> {
connection: &'a Connection,
name: String,
}
#[test]
fn with_select_clause() {
let connection = connection();
setup_users_table(&connection);
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();

impl<'a> Drop for TestTable<'a> {
fn drop(&mut self) {
self.connection.execute(&format!("DROP TABLE {}", &self.name))
.unwrap();
}
}
let select_id = unsafe { UserTable.select::<types::Serial>("id") };
let select_name = unsafe { UserTable.select::<types::VarChar>("name") };

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(),
}
}
let expected_ids = vec![1, 2];
let expected_names = vec!["Sean".to_string(), "Tess".to_string()];
let actual_ids = connection.query_all::<_, i32>(&select_id).unwrap();
let actual_names = connection.query_all::<_, String>(&select_name).unwrap();

assert_eq!(expected_ids, actual_ids);
assert_eq!(expected_names, actual_names);
}
}
43 changes: 38 additions & 5 deletions src/query_source.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
use super::types::{FromSql, NativeSqlType};
use types::{FromSql, NativeSqlType};
use std::marker::PhantomData;

pub unsafe trait QuerySource {
pub trait Queriable<QS: QuerySource> {
type Row: FromSql<QS::SqlType>;

fn build(row: Self::Row) -> Self;
}

pub unsafe trait QuerySource: Sized {
type SqlType: NativeSqlType;

fn select_clause(&self) -> &str;
fn from_clause(&self) -> &str;

unsafe fn select<A: NativeSqlType>(self, columns: &'static str) -> SelectedQuerySource<A, Self> {
SelectedQuerySource {
columns: columns,
source: self,
_marker: PhantomData,
}
}
}

pub trait Queriable<QS: QuerySource> {
type Row: FromSql<QS::SqlType>;
pub struct SelectedQuerySource<A, S> where
A: NativeSqlType,
S: QuerySource,
{
columns: &'static str,
source: S,
_marker: PhantomData<A>,
}

fn build(row: Self::Row) -> Self;
unsafe impl<A, S> QuerySource for SelectedQuerySource<A, S> where
A: NativeSqlType,
S: QuerySource,
{
type SqlType = A;

fn select_clause(&self) -> &str {
self.columns
}

fn from_clause(&self) -> &str {
self.source.from_clause()
}
}
36 changes: 26 additions & 10 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
extern crate postgres;

use {Queriable, QuerySource};

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)
macro_rules! primitive_impls {
($($Source:ident -> $Target:ident),+,) => {
$(
impl NativeSqlType for $Source {}
impl FromSql<$Source> for $Target {
fn from_sql(row: &Row, idx: usize) -> Self {
row.get(idx)
}
}

impl<QS> Queriable<QS> for $Target where
QS: QuerySource<SqlType=$Source>
{
type Row = Self;

fn build(row: Self::Row) -> Self {
row
}
}
)+
}
}
impl FromSql<VarChar> for String {
fn from_sql(row: &Row, idx: usize) -> Self {
row.get(idx)
}

primitive_impls! {
Serial -> i32,
VarChar -> String,
}

macro_rules! tuple_impls {
Expand Down

0 comments on commit 2ba8bf4

Please sign in to comment.