Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the usage of tuples in argument positions from our API #747

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions diesel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ url = { version = "1.4.0", optional = true }
uuid = { version = ">=0.2.0, <0.5.0", optional = true, features = ["use_std"] }

[dev-dependencies]
assert_matches = "1.0.1"
cfg-if = "0.1.0"
diesel_codegen = "0.11.0"
dotenv = "0.8.0"
Expand Down
48 changes: 14 additions & 34 deletions diesel/src/expression/expression_methods/eq_all.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use expression::Expression;
use expression::predicates::And;
use expression::expression_methods::*;
use expression::predicates::And;
use hlist::*;
use types::Bool;

/// This method is used by `FindDsl` to work with tuples. Because we cannot
/// This method is used by `FindDsl` to work with hlists. Because we cannot
/// express this without specialization or overlapping impls, it is brute force
/// implemented on columns in the `column!` macro.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by comment: Actually, the macro is called __diesel_column!

#[doc(hidden)]
Expand All @@ -13,45 +14,24 @@ pub trait EqAll<Rhs> {
fn eq_all(self, rhs: Rhs) -> Self::Output;
}

// FIXME: This is much easier to represent with a macro once macro types are stable
// which appears to be slated for 1.13
impl<L1, L2, R1, R2> EqAll<(R1, R2)> for (L1, L2) where
L1: EqAll<R1>,
L2: EqAll<R2>,
impl<L1, L2, LTail, R1, R2, RTail> EqAll<Cons<R1, Cons<R2, RTail>>>
for Cons<L1, Cons<L2, LTail>> where
L1: EqAll<R1>,
Cons<L2, LTail>: EqAll<Cons<R2, RTail>>,
{
type Output = And<<L1 as EqAll<R1>>::Output, <L2 as EqAll<R2>>::Output>;
type Output = And<<L1 as EqAll<R1>>::Output, <Cons<L2, LTail> as EqAll<Cons<R2, RTail>>>::Output>;

fn eq_all(self, rhs: (R1, R2)) -> Self::Output {
fn eq_all(self, rhs: Cons<R1, Cons<R2, RTail>>) -> Self::Output {
self.0.eq_all(rhs.0).and(self.1.eq_all(rhs.1))
}
}

impl<L1, L2, L3, R1, R2, R3> EqAll<(R1, R2, R3)> for (L1, L2, L3) where
L1: EqAll<R1>,
L2: EqAll<R2>,
L3: EqAll<R3>,
{
type Output = And<<L1 as EqAll<R1>>::Output, And<<L2 as EqAll<R2>>::Output, <L3 as EqAll<R3>>::Output>>;

fn eq_all(self, rhs: (R1, R2, R3)) -> Self::Output {
self.0.eq_all(rhs.0).and(
self.1.eq_all(rhs.1).and(self.2.eq_all(rhs.2)))
}
}

impl<L1, L2, L3, L4, R1, R2, R3, R4> EqAll<(R1, R2, R3, R4)> for (L1, L2, L3, L4) where
L1: EqAll<R1>,
L2: EqAll<R2>,
L3: EqAll<R3>,
L4: EqAll<R4>,
impl<Left, Right> EqAll<Cons<Right, Nil>> for Cons<Left, Nil> where
Left: EqAll<Right>,
{
type Output = And<<L1 as EqAll<R1>>::Output, And<<L2 as EqAll<R2>>::Output, And<<L3 as EqAll<R3>>::Output, <L4 as EqAll<R4>>::Output>>>;
type Output = <Left as EqAll<Right>>::Output;

fn eq_all(self, rhs: (R1, R2, R3, R4)) -> Self::Output {
self.0.eq_all(rhs.0).and(
self.1.eq_all(rhs.1).and(
self.2.eq_all(rhs.2).and(
self.3.eq_all(rhs.3)
)))
fn eq_all(self, rhs: Cons<Right, Nil>) -> Self::Output {
self.0.eq_all(rhs.0)
}
}
10 changes: 5 additions & 5 deletions diesel/src/expression/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,33 @@ macro_rules! sql_function_body {

#[allow(non_camel_case_types)]
impl<$($arg_name),*> $crate::expression::Expression for $struct_name<$($arg_name),*> where
for <'a> ($(&'a $arg_name),*): $crate::expression::Expression,
for <'a> Hlist!($(&'a $arg_name),*): $crate::expression::Expression,
{
type SqlType = $return_type;
}

#[allow(non_camel_case_types)]
impl<$($arg_name),*, DB> $crate::query_builder::QueryFragment<DB> for $struct_name<$($arg_name),*> where
DB: $crate::backend::Backend,
for <'a> ($(&'a $arg_name),*): $crate::query_builder::QueryFragment<DB>,
for <'a> Hlist!($(&'a $arg_name),*): $crate::query_builder::QueryFragment<DB>,
{
fn to_sql(&self, out: &mut DB::QueryBuilder) -> $crate::query_builder::BuildQueryResult {
use $crate::query_builder::QueryBuilder;
out.push_sql(concat!(stringify!($fn_name), "("));
try!($crate::query_builder::QueryFragment::to_sql(
&($(&self.$arg_name),*), out));
&hlist!($(&self.$arg_name),*), out));
out.push_sql(")");
Ok(())
}

fn collect_binds(&self, out: &mut DB::BindCollector) -> $crate::result::QueryResult<()> {
try!($crate::query_builder::QueryFragment::collect_binds(
&($(&self.$arg_name),*), out));
&hlist!($(&self.$arg_name),*), out));
Ok(())
}

fn is_safe_to_cache_prepared(&self) -> bool {
($(&self.$arg_name),*).is_safe_to_cache_prepared()
hlist!($(&self.$arg_name),*).is_safe_to_cache_prepared()
}
}

Expand Down
33 changes: 33 additions & 0 deletions diesel/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub use self::dsl::*;
pub use self::sql_literal::SqlLiteral;

use backend::Backend;
use hlist::*;

/// Represents a typed fragment of SQL. Apps should not need to implement this
/// type directly, but it may be common to use this as type boundaries.
Expand All @@ -81,6 +82,17 @@ impl<'a, T: Expression + ?Sized> Expression for &'a T {
type SqlType = T::SqlType;
}

impl<Head, Tail> Expression for Cons<Head, Tail> where
Head: Expression + NonAggregate,
Tail: Expression + NonAggregate,
{
type SqlType = Cons<Head::SqlType, Tail::SqlType>;
}

impl Expression for Nil {
type SqlType = Nil;
}

/// Describes how a type can be represented as an expression for a given type.
/// These types couldn't just implement [`Expression`](trait.Expression.html)
/// directly, as many things can be used as an expression of multiple types.
Expand Down Expand Up @@ -130,6 +142,18 @@ impl<'a, T: ?Sized, QS> SelectableExpression<QS> for &'a T where
type SqlTypeForSelect = T::SqlTypeForSelect;
}

impl<Head, Tail, QS> SelectableExpression<QS> for Cons<Head, Tail> where
Head: SelectableExpression<QS>,
Tail: SelectableExpression<QS>,
Cons<Head, Tail>: Expression,
{
type SqlTypeForSelect = Cons<Head::SqlTypeForSelect, Tail::SqlTypeForSelect>;
}

impl<QS> SelectableExpression<QS> for Nil {
type SqlTypeForSelect = Nil;
}

/// Marker trait to indicate that an expression does not include any aggregate
/// functions. Used to ensure that aggregate expressions aren't mixed with
/// non-aggregate expressions in a select clause, and that they're never
Expand All @@ -143,6 +167,15 @@ impl<T: NonAggregate + ?Sized> NonAggregate for Box<T> {
impl<'a, T: NonAggregate + ?Sized> NonAggregate for &'a T {
}

impl<Head, Tail> NonAggregate for Cons<Head, Tail> where
Head: NonAggregate,
Tail: NonAggregate,
{
}

impl NonAggregate for Nil {
}

use query_builder::{QueryFragment, QueryId};

/// Helper trait used when boxing expressions. This exists to work around the
Expand Down
88 changes: 88 additions & 0 deletions diesel/src/hlist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::fmt::{Debug, Formatter, Error as FmtError};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add some (module-level) docs here. At least for contributors looking to understand diesel's inner workings. Like this maybe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the whole thing will need a lot of documentation if merged.

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Cons<Head, Tail>(pub Head, pub Tail);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Nil;

pub trait Hlist {
fn len() -> usize;
}

impl<T, U> Hlist for Cons<T, U> where
U: Hlist,
{
fn len() -> usize {
U::len() + 1
}
}

impl Hlist for Nil {
fn len() -> usize {
0
}
}

impl Debug for Nil {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FmtError> {
formatter.write_str("hlist()")
}
}

impl<T, U> Debug for Cons<T, U> where
Cons<T, U>: DebugItems,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FmtError> {
let mut items: &DebugItems = &*self;
let mut formatter = formatter.debug_tuple("hlist");
while let Some((head, tail)) = items.pop() {
items = tail;
formatter.field(head);
}
formatter.finish()
}
}

#[doc(hidden)]
pub trait DebugItems {
fn pop(&self) -> Option<(&Debug, &DebugItems)>;
}

impl<T, U> DebugItems for Cons<T, U> where
T: Debug,
U: DebugItems,
{
fn pop(&self) -> Option<(&Debug, &DebugItems)> {
Some((&self.0, &self.1))
}
}

impl DebugItems for Nil {
fn pop(&self) -> Option<(&Debug, &DebugItems)> {
None
}
}

#[test]
fn debug_empty_hlist() {
assert_eq!("hlist()", format!("{:?}", Nil));
assert_eq!("hlist()", format!("{:#?}", Nil));
}

#[test]
fn debug_single_item_hlist() {
assert_eq!("hlist(1)", format!("{:?}", Cons(1, Nil)));
assert_eq!("hlist(2)", format!("{:?}", Cons(2, Nil)));
assert_eq!(r#"hlist("hello")"#, format!("{:?}", Cons("hello", Nil)));
assert_eq!(r#"hlist("world")"#, format!("{:?}", Cons("world", Nil)));
}

#[test]
fn debug_multi_item_hlist() {
assert_eq!("hlist(1, 2)", format!("{:?}", Cons(1, Cons(2, Nil))));
assert_eq!("hlist(2, 3)", format!("{:?}", Cons(2, Cons(3, Nil))));
let str_hlist = Cons("hello", Cons("world", Nil));
assert_eq!(r#"hlist("hello", "world")"#, format!("{:?}", str_hlist));
let mixed_hlist = Cons("hello", Cons(1, Cons("world", Cons(2, Nil))));
assert_eq!(r#"hlist("hello", 1, "world", 2)"#, format!("{:?}", mixed_hlist));
}
Loading