Skip to content

Commit

Permalink
Add basic support for additional expressions in from clause
Browse files Browse the repository at this point in the history
This is useful for things like full text search, where you need to do an
expensive calculation to a parameter going into the where clause, but
doing it inline would repeat the calculation for each loop.

I had to expose a lot of internals here, because this is yet another
case where we can't properly express what we want without either
specialization or rust-lang/rust#29864. I
could not find a way to properly enforce the selectability of `Aliased`
when `with` was called more than once (this is ultimately the same
problem as joining multiple times). In the interim, we'll allow the
alias to be used anywhere.
  • Loading branch information
sgrif committed Nov 25, 2015
1 parent 7714144 commit b590d57
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/expression/aliased.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use expression::{Expression, NonAggregate, SelectableExpression};
use query_builder::*;
use query_source::*;

#[derive(Debug, Clone, Copy)]
pub struct Aliased<'a, Expr> {
expr: Expr,
alias: &'a str,
}

impl<'a, Expr> Aliased<'a, Expr> {
pub fn new(expr: Expr, alias: &'a str) -> Self {
Aliased {
expr: expr,
alias: alias,
}
}
}

pub struct FromEverywhere;

impl<'a, T> Expression for Aliased<'a, T> where T: Expression {
type SqlType = T::SqlType;

fn to_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
out.push_identifier(&self.alias)
}
}

// FIXME This is incorrect, should only be selectable from WithQuerySource
impl<'a, T, QS> SelectableExpression<QS> for Aliased<'a, T> where
Aliased<'a, T>: Expression,
{
}

impl<'a, T: Expression> QuerySource for Aliased<'a, T> {
fn from_clause(&self, out: &mut QueryBuilder) -> BuildQueryResult {
try!(self.expr.to_sql(out));
out.push_sql(" ");
out.push_identifier(&self.alias)
}
}

impl<'a, T> NonAggregate for Aliased<'a, T> where Aliased<'a, T>: Expression {
}
1 change: 1 addition & 0 deletions src/expression/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[macro_use]
pub mod ops;

pub mod aliased;
pub mod array_comparison;
pub mod bound;
pub mod count;
Expand Down
7 changes: 7 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ macro_rules! column {

impl $crate::expression::SelectableExpression<$($table)::*> for $column_name {}

impl<'a, ST, Left, Right> SelectableExpression<
$crate::WithQuerySource<'a, Left, Right>, ST> for $column_name where
ST: NativeSqlType,
$column_name: SelectableExpression<Left, ST>
{
}

impl $crate::expression::NonAggregate for $column_name {}

impl $crate::query_source::Column for $column_name {
Expand Down
14 changes: 14 additions & 0 deletions src/query_builder/select_statement/dsl_impls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use expression::*;
use expression::aliased::Aliased;
use query_builder::*;
use query_builder::limit_clause::*;
use query_builder::offset_clause::*;
Expand Down Expand Up @@ -79,3 +80,16 @@ impl<ST, S, F, W, O, L, Of> OffsetDsl for SelectStatement<ST, S, F, W, O, L, Of>
self.order, self.limit, offset_clause)
}
}

impl<'a, ST, S, F, W, O, L, Of, Expr> WithDsl<'a, Expr>
for SelectStatement<ST, S, F, W, O, L, Of> where
SelectStatement<ST, S, WithQuerySource<'a, F, Expr>, W, O, L, Of>: Query,
{
type Output = SelectStatement<ST, S, WithQuerySource<'a, F, Expr>, W, O, L, Of>;

fn with(self, expr: Aliased<'a, Expr>) -> Self::Output {
let source = WithQuerySource::new(self.from, expr);
SelectStatement::new(self.select, source, self.where_clause,
self.order, self.limit, self.offset)
}
}
2 changes: 2 additions & 0 deletions src/query_dsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod select_dsl;
pub mod filter_dsl;
mod offset_dsl;
mod order_dsl;
mod with_dsl;

pub use self::count_dsl::CountDsl;
pub use self::filter_dsl::{FilterDsl, FilterOutput, FindByOutput};
Expand All @@ -13,3 +14,4 @@ pub use self::load_dsl::LoadDsl;
pub use self::offset_dsl::{OffsetDsl, OffsetOutput};
pub use self::order_dsl::{OrderDsl, OrderOutput};
pub use self::select_dsl::{SelectDsl, SelectSqlDsl, SelectOutput};
pub use self::with_dsl::{WithDsl, WithQuerySource};
46 changes: 46 additions & 0 deletions src/query_dsl/with_dsl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use expression::Expression;
use expression::aliased::Aliased;
use query_builder::*;
use query_source::QuerySource;

pub trait WithDsl<'a, Expr> {
type Output: AsQuery;

fn with(self, expr: Aliased<'a, Expr>) -> Self::Output;
}

impl<'a, T, Expr> WithDsl<'a, Expr> for T where
T: QuerySource + AsQuery,
T::Query: WithDsl<'a, Expr>
{
type Output = <T::Query as WithDsl<'a, Expr>>::Output;

fn with(self, expr: Aliased<'a, Expr>) -> Self::Output {
self.as_query().with(expr)
}
}

pub struct WithQuerySource<'a, Left, Right> {
left: Left,
right: Aliased<'a, Right>,
}

impl<'a, Left, Right> WithQuerySource<'a, Left, Right> {
pub fn new(left: Left, right: Aliased<'a, Right>) -> Self {
WithQuerySource {
left: left,
right: right,
}
}
}

impl<'a, Left, Right> QuerySource for WithQuerySource<'a, Left, Right> where
Left: QuerySource,
Aliased<'a, Right>: QuerySource + Expression,
{
fn from_clause(&self, out: &mut QueryBuilder) -> BuildQueryResult {
try!(self.left.from_clause(out));
out.push_sql(", ");
self.right.from_clause(out)
}
}
17 changes: 17 additions & 0 deletions tests/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ fn test_count_star() {
assert!(query_builder.sql.starts_with("SELECT COUNT(*) FROM"));
}

use yaqb::types::VarChar;
sql_function!(lower, lower_t, (x: VarChar) -> VarChar);

#[test]
fn test_with_expression_aliased() {
let connection = connection();

let mut query_builder = ::yaqb::query_builder::pg::PgQueryBuilder::new(&connection);
let n = lower("sean").aliased("n");
let source = users.with(n).filter(n.eq("Jim")).select(id);
Expression::to_sql(&source.as_query(), &mut query_builder).unwrap();
assert_eq!(
r#"SELECT "users"."id" FROM "users", lower($1) "n" WHERE "n" = $2"#,
&query_builder.sql
);
}

table! {
numbers (n) {
n -> Integer,
Expand Down

0 comments on commit b590d57

Please sign in to comment.