Permalink
Browse files

Add the foundation for select clauses

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 2ba8bf481c3d21d8b6b731bd9ae6c290f70b57e7
Showing with 92 additions and 38 deletions.
  1. +28 −23 src/lib.rs
  2. +38 −5 src/query_source.rs
  3. +26 −10 src/types.rs
@@ -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();
@@ -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);
}
}
@@ -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()
}
}
@@ -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 {

0 comments on commit 2ba8bf4

Please sign in to comment.