Skip to content

Commit eacf678

Browse files
authored
Merge pull request diesel-rs#1545 from diesel-rs/sg-fix-select-expressions
Fix `SelectStatement` being incorrectly allowed in an expression context
2 parents d9ccd1f + 4bc1d09 commit eacf678

12 files changed

Lines changed: 197 additions & 102 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
7575
* `#[derive(Identifiable)]` now correctly associates `#[primary_key]` with the
7676
column name, not field name.
7777

78+
* Select statements can no longer incorrectly appear in an expression context.
79+
80+
* `exists` can no longer incorrectly receive values other than select
81+
statements.
82+
7883
### Jokes
7984

8085
* Diesel is now powered by the blockchain because it's 2018.

diesel/src/expression/array_comparison.rs

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use backend::Backend;
22
use expression::*;
3+
use expression::subselect::Subselect;
34
use query_builder::*;
45
use result::QueryResult;
56
use sql_types::Bool;
@@ -103,7 +104,6 @@ where
103104
impl_selectable_expression!(In<T, U>);
104105
impl_selectable_expression!(NotIn<T, U>);
105106

106-
use std::marker::PhantomData;
107107
use query_builder::{BoxedSelectStatement, SelectStatement};
108108

109109
pub trait AsInExpression<T> {
@@ -136,10 +136,7 @@ where
136136
type InExpression = Subselect<Self, ST>;
137137

138138
fn as_in_expression(self) -> Self::InExpression {
139-
Subselect {
140-
values: self,
141-
_sql_type: PhantomData,
142-
}
139+
Subselect::new(self)
143140
}
144141
}
145142

@@ -150,10 +147,7 @@ where
150147
type InExpression = Subselect<Self, ST>;
151148

152149
fn as_in_expression(self) -> Self::InExpression {
153-
Subselect {
154-
values: self,
155-
_sql_type: PhantomData,
156-
}
150+
Subselect::new(self)
157151
}
158152
}
159153

@@ -209,43 +203,3 @@ impl<T> QueryId for Many<T> {
209203

210204
const HAS_STATIC_QUERY_ID: bool = false;
211205
}
212-
213-
#[derive(Debug, Copy, Clone, QueryId)]
214-
pub struct Subselect<T, ST> {
215-
values: T,
216-
_sql_type: PhantomData<ST>,
217-
}
218-
219-
impl<T: Expression, ST> Expression for Subselect<T, ST> {
220-
type SqlType = ST;
221-
}
222-
223-
impl<T, ST> MaybeEmpty for Subselect<T, ST> {
224-
fn is_empty(&self) -> bool {
225-
false
226-
}
227-
}
228-
229-
impl<T, ST, QS> SelectableExpression<QS> for Subselect<T, ST>
230-
where
231-
Subselect<T, ST>: AppearsOnTable<QS>,
232-
T: SelectableExpression<QS>,
233-
{
234-
}
235-
236-
impl<T, ST, QS> AppearsOnTable<QS> for Subselect<T, ST>
237-
where
238-
Subselect<T, ST>: Expression,
239-
T: AppearsOnTable<QS>,
240-
{
241-
}
242-
243-
impl<T, ST, DB> QueryFragment<DB> for Subselect<T, ST>
244-
where
245-
DB: Backend,
246-
T: QueryFragment<DB>,
247-
{
248-
fn walk_ast(&self, pass: AstPass<DB>) -> QueryResult<()> {
249-
self.values.walk_ast(pass)
250-
}
251-
}

diesel/src/expression/exists.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use backend::Backend;
2-
use expression::{Expression, NonAggregate};
2+
use expression::{AppearsOnTable, Expression, NonAggregate, SelectableExpression};
3+
use expression::subselect::Subselect;
34
use query_builder::*;
45
use result::QueryResult;
56
use sql_types::Bool;
67

78
/// Creates a SQL `EXISTS` expression.
89
///
9-
/// The argument must be a complete SQL query. The result of this could in
10-
/// theory be passed to `.filter`, but since the query cannot reference columns
11-
/// from the outer query, this is of limited usefulness.
10+
/// The argument must be a complete SQL query. The query may reference columns
11+
/// from the outer table.
1212
///
1313
/// # Example
1414
///
@@ -30,15 +30,15 @@ use sql_types::Bool;
3030
/// # }
3131
/// ```
3232
pub fn exists<T>(query: T) -> Exists<T> {
33-
Exists(query)
33+
Exists(Subselect::new(query))
3434
}
3535

3636
#[derive(Debug, Clone, Copy, QueryId)]
37-
pub struct Exists<T>(T);
37+
pub struct Exists<T>(Subselect<T, ()>);
3838

3939
impl<T> Expression for Exists<T>
4040
where
41-
T: Expression,
41+
Subselect<T, ()>: Expression,
4242
{
4343
type SqlType = Bool;
4444
}
@@ -58,4 +58,16 @@ where
5858
}
5959
}
6060

61-
impl_selectable_expression!(Exists<T>);
61+
impl<T, QS> SelectableExpression<QS> for Exists<T>
62+
where
63+
Self: AppearsOnTable<QS>,
64+
Subselect<T, ()>: SelectableExpression<QS>,
65+
{
66+
}
67+
68+
impl<T, QS> AppearsOnTable<QS> for Exists<T>
69+
where
70+
Self: Expression,
71+
Subselect<T, ()>: AppearsOnTable<QS>,
72+
{
73+
}

diesel/src/expression/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub mod nullable;
4343
pub mod operators;
4444
#[doc(hidden)]
4545
pub mod sql_literal;
46+
#[doc(hidden)]
47+
pub mod subselect;
4648

4749
#[doc(hidden)]
4850
pub mod dsl {

diesel/src/expression/subselect.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use std::marker::PhantomData;
2+
3+
use expression::*;
4+
use expression::array_comparison::MaybeEmpty;
5+
use query_builder::*;
6+
use result::QueryResult;
7+
8+
#[derive(Debug, Copy, Clone, QueryId)]
9+
pub struct Subselect<T, ST> {
10+
values: T,
11+
_sql_type: PhantomData<ST>,
12+
}
13+
14+
impl<T, ST> Subselect<T, ST> {
15+
pub(crate) fn new(values: T) -> Self {
16+
Self {
17+
values,
18+
_sql_type: PhantomData,
19+
}
20+
}
21+
}
22+
23+
impl<T: SelectQuery, ST> Expression for Subselect<T, ST> {
24+
type SqlType = ST;
25+
}
26+
27+
impl<T, ST> MaybeEmpty for Subselect<T, ST> {
28+
fn is_empty(&self) -> bool {
29+
false
30+
}
31+
}
32+
33+
impl<T, ST, QS> SelectableExpression<QS> for Subselect<T, ST>
34+
where
35+
Subselect<T, ST>: AppearsOnTable<QS>,
36+
T: ValidSubselect<QS>,
37+
{
38+
}
39+
40+
impl<T, ST, QS> AppearsOnTable<QS> for Subselect<T, ST>
41+
where
42+
Subselect<T, ST>: Expression,
43+
T: ValidSubselect<QS>,
44+
{
45+
}
46+
47+
impl<T, ST> NonAggregate for Subselect<T, ST> {}
48+
49+
impl<T, ST, DB> QueryFragment<DB> for Subselect<T, ST>
50+
where
51+
DB: Backend,
52+
T: QueryFragment<DB>,
53+
{
54+
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
55+
self.values.walk_ast(out.reborrow())?;
56+
Ok(())
57+
}
58+
}
59+
60+
pub trait ValidSubselect<QS> {}

diesel/src/pg/expression/array_comparison.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use expression::{AsExpression, Expression, NonAggregate};
2+
use expression::subselect::Subselect;
23
use pg::Pg;
34
use query_builder::*;
45
use result::QueryResult;
@@ -29,7 +30,7 @@ use sql_types::Array;
2930
/// ```
3031
pub fn any<ST, T>(vals: T) -> Any<T::Expression>
3132
where
32-
T: AsExpression<Array<ST>>,
33+
T: AsArrayExpression<ST>,
3334
{
3435
Any::new(vals.as_expression())
3536
}
@@ -57,7 +58,7 @@ where
5758
/// ```
5859
pub fn all<ST, T>(vals: T) -> All<T::Expression>
5960
where
60-
T: AsExpression<Array<ST>>,
61+
T: AsArrayExpression<ST>,
6162
{
6263
All::new(vals.as_expression())
6364
}
@@ -139,3 +140,43 @@ where
139140
Expr: NonAggregate,
140141
{
141142
}
143+
144+
pub trait AsArrayExpression<ST> {
145+
type Expression: Expression<SqlType = Array<ST>>;
146+
147+
fn as_expression(self) -> Self::Expression;
148+
}
149+
150+
impl<ST, T> AsArrayExpression<ST> for T
151+
where
152+
T: AsExpression<Array<ST>>,
153+
{
154+
type Expression = <T as AsExpression<Array<ST>>>::Expression;
155+
156+
fn as_expression(self) -> Self::Expression {
157+
AsExpression::as_expression(self)
158+
}
159+
}
160+
161+
impl<ST, S, F, W, O, L, Of, G, FU> AsArrayExpression<ST>
162+
for SelectStatement<S, F, W, O, L, Of, G, FU>
163+
where
164+
Self: SelectQuery<SqlType = ST>,
165+
{
166+
type Expression = Subselect<Self, Array<ST>>;
167+
168+
fn as_expression(self) -> Self::Expression {
169+
Subselect::new(self)
170+
}
171+
}
172+
173+
impl<'a, ST, QS, DB> AsArrayExpression<ST> for BoxedSelectStatement<'a, ST, QS, DB>
174+
where
175+
Self: SelectQuery<SqlType = ST>,
176+
{
177+
type Expression = Subselect<Self, Array<ST>>;
178+
179+
fn as_expression(self) -> Self::Expression {
180+
Subselect::new(self)
181+
}
182+
}

diesel/src/pg/query_builder/distinct_on.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use pg::Pg;
22
use query_dsl::methods::DistinctOnDsl;
3-
use query_builder::{AstPass, QueryFragment, SelectStatement};
3+
use query_builder::{AstPass, QueryFragment, SelectQuery, SelectStatement};
44
use result::QueryResult;
5-
use expression::{Expression, SelectableExpression};
5+
use expression::SelectableExpression;
66

77
/// Represents `DISTINCT ON (...)`
88
#[derive(Debug, Clone, Copy, QueryId)]
@@ -24,8 +24,8 @@ impl<ST, F, S, D, W, O, L, Of, G, Selection> DistinctOnDsl<Selection>
2424
for SelectStatement<F, S, D, W, O, L, Of, G>
2525
where
2626
Selection: SelectableExpression<F>,
27-
Self: Expression<SqlType = ST>,
28-
SelectStatement<F, S, DistinctOnClause<Selection>, W, O, L, Of, G>: Expression<SqlType = ST>,
27+
Self: SelectQuery<SqlType = ST>,
28+
SelectStatement<F, S, DistinctOnClause<Selection>, W, O, L, Of, G>: SelectQuery<SqlType = ST>,
2929
{
3030
type Output = SelectStatement<F, S, DistinctOnClause<Selection>, W, O, L, Of, G>;
3131

diesel/src/query_builder/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ impl<'a, T: Query> Query for &'a T {
108108
type SqlType = T::SqlType;
109109
}
110110

111+
/// Indicates that a type is a `SELECT` statement.
112+
///
113+
/// This trait differs from `Query` in two ways:
114+
/// - It is implemented only for select statements, rather than all queries
115+
/// which return a value.
116+
/// - It has looser constraints. A type implementing `SelectQuery` is known to
117+
/// be potentially valid if used as a subselect, but it is not necessarily
118+
/// able to be executed.
119+
pub trait SelectQuery {
120+
/// The SQL type of the `SELECT` clause
121+
type SqlType;
122+
}
123+
111124
/// An untyped fragment of SQL.
112125
///
113126
/// This may be a complete SQL command (such as an update statement without a

diesel/src/query_builder/select_statement/boxed.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::marker::PhantomData;
33
use backend::Backend;
44
use dsl::AsExprOf;
55
use expression::*;
6+
use expression::subselect::ValidSubselect;
67
use insertable::Insertable;
78
use query_builder::*;
89
use query_builder::distinct_clause::DistinctClause;
@@ -64,14 +65,14 @@ where
6465
type SqlType = ST;
6566
}
6667

67-
impl<'a, ST, QS, DB> Expression for BoxedSelectStatement<'a, ST, QS, DB>
68+
impl<'a, ST, QS, DB> SelectQuery for BoxedSelectStatement<'a, ST, QS, DB>
6869
where
69-
Self: Query<SqlType = ST>,
70+
DB: Backend,
7071
{
7172
type SqlType = ST;
7273
}
7374

74-
impl<'a, ST, QS, QS2, DB> AppearsOnTable<QS2> for BoxedSelectStatement<'a, ST, QS, DB>
75+
impl<'a, ST, QS, QS2, DB> ValidSubselect<QS2> for BoxedSelectStatement<'a, ST, QS, DB>
7576
where
7677
Self: Query<SqlType = ST>,
7778
{

0 commit comments

Comments
 (0)