Skip to content

Commit e5fc2a8

Browse files
authored
Merge pull request #3921 from dvogel/pg-tablesample
Add Postgres TABLESAMPLE support.
2 parents fed3896 + 8b0ac9b commit e5fc2a8

File tree

35 files changed

+588
-22
lines changed

35 files changed

+588
-22
lines changed

diesel/src/internal/table_macro.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#[doc(hidden)]
22
pub use crate::expression::nullable::Nullable as NullableExpression;
33
#[doc(hidden)]
4+
#[cfg(feature = "postgres_backend")]
5+
pub use crate::pg::query_builder::tablesample::TablesampleMethod;
6+
#[doc(hidden)]
47
pub use crate::query_builder::from_clause::{FromClause, NoFromClause};
58
#[doc(hidden)]
69
pub use crate::query_builder::nodes::{

diesel/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,14 @@ pub mod dsl {
334334

335335
#[doc(inline)]
336336
pub use diesel_derives::auto_type;
337+
338+
#[cfg(feature = "postgres_backend")]
339+
#[doc(inline)]
340+
pub use crate::pg::expression::extensions::OnlyDsl;
341+
342+
#[cfg(feature = "postgres_backend")]
343+
#[doc(inline)]
344+
pub use crate::pg::expression::extensions::TablesampleDsl;
337345
}
338346

339347
pub mod helper_types {

diesel/src/macros/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,34 @@ macro_rules! __diesel_internal_backend_specific_allow_tables_to_appear_in_same_q
243243
for $left::table
244244
{
245245
}
246+
impl<TSM> $crate::query_source::TableNotEqual<$left::table>
247+
for $crate::query_builder::Tablesample<$right::table, TSM>
248+
where
249+
TSM: $crate::internal::table_macro::TablesampleMethod,
250+
{
251+
}
252+
impl<TSM> $crate::query_source::TableNotEqual<$right::table>
253+
for $crate::query_builder::Tablesample<$left::table, TSM>
254+
where
255+
TSM: $crate::internal::table_macro::TablesampleMethod,
256+
{
257+
}
258+
impl<TSM>
259+
$crate::query_source::TableNotEqual<
260+
$crate::query_builder::Tablesample<$left::table, TSM>,
261+
> for $right::table
262+
where
263+
TSM: $crate::internal::table_macro::TablesampleMethod,
264+
{
265+
}
266+
impl<TSM>
267+
$crate::query_source::TableNotEqual<
268+
$crate::query_builder::Tablesample<$right::table, TSM>,
269+
> for $left::table
270+
where
271+
TSM: $crate::internal::table_macro::TablesampleMethod,
272+
{
273+
}
246274
};
247275
}
248276
#[doc(hidden)]

diesel/src/pg/expression/extensions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! re-exported in `diesel::dsl`
44
mod interval_dsl;
55
mod only_dsl;
6+
mod tablesample_dsl;
67

78
pub use self::interval_dsl::IntervalDsl;
89
pub use self::only_dsl::OnlyDsl;
10+
pub use self::tablesample_dsl::TablesampleDsl;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::pg::query_builder::tablesample::{BernoulliMethod, SystemMethod};
2+
use crate::query_builder::Tablesample;
3+
use crate::Table;
4+
5+
/// The `tablesample` method
6+
///
7+
/// The `TABLESAMPLE` clause is used to select a randomly sampled subset of rows from a table.
8+
///
9+
/// This is only implemented for the Postgres backend. While `TABLESAMPLE` is standardized in
10+
/// SQL:2003, in practice each RDBMS seems to implement a superset of the SQL:2003 syntax,
11+
/// supporting a wide variety of sampling methods.
12+
///
13+
/// Calling this function on a table (`mytable.tablesample(...)`) will result in the SQL
14+
/// `FROM mytable TABLESAMPLE ...` --
15+
/// `mytable.tablesample(...)` can be used just like any table in diesel since it implements
16+
/// [Table](crate::Table).
17+
///
18+
/// The `BernoulliMethod` and `SystemMethod` types can be used to indicate the sampling method for
19+
/// a `TABLESAMPLE method(p)` clause where p is specified by the portion argument. The provided
20+
/// percentage should be an integer between 0 and 100.
21+
///
22+
/// To generate a `TABLESAMPLE ... REPEATABLE (f)` clause, you'll need to call
23+
/// [`.with_seed(f)`](Tablesample::with_seed).
24+
///
25+
/// Example:
26+
///
27+
/// ```rust
28+
/// # include!("../../../doctest_setup.rs");
29+
/// # use schema::{posts, users};
30+
/// # use diesel::dsl::*;
31+
/// # fn main() {
32+
/// # let connection = &mut establish_connection();
33+
/// let random_user_ids = users::table
34+
/// .tablesample_bernoulli(10)
35+
/// .select((users::id))
36+
/// .load::<i32>(connection);
37+
/// # }
38+
/// ```
39+
/// Selects the ids for a random 10 percent of users.
40+
///
41+
/// It can also be used in inner joins:
42+
///
43+
/// ```rust
44+
/// # include!("../../../doctest_setup.rs");
45+
/// # use schema::{posts, users};
46+
/// # use diesel::dsl::*;
47+
/// # fn main() {
48+
/// # let connection = &mut establish_connection();
49+
/// # let _ =
50+
/// users::table
51+
/// .tablesample_system(10).with_seed(42.0)
52+
/// .inner_join(posts::table)
53+
/// .select((users::name, posts::title))
54+
/// .load::<(String, String)>(connection);
55+
/// # }
56+
/// ```
57+
/// That query selects all of the posts for all of the users in a random 10 percent storage pages,
58+
/// returning the same results each time it is run due to the static seed of 42.0.
59+
///
60+
pub trait TablesampleDsl: Table {
61+
/// See the trait-level docs.
62+
fn tablesample_bernoulli(self, portion: i16) -> Tablesample<Self, BernoulliMethod> {
63+
Tablesample::new(self, portion)
64+
}
65+
66+
/// See the trait-level docs.
67+
fn tablesample_system(self, portion: i16) -> Tablesample<Self, SystemMethod> {
68+
Tablesample::new(self, portion)
69+
}
70+
}
71+
72+
impl<T: Table> TablesampleDsl for T {}

diesel/src/pg/query_builder/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod limit_offset;
77
pub(crate) mod on_constraint;
88
pub(crate) mod only;
99
mod query_fragment_impls;
10+
pub(crate) mod tablesample;
1011
pub use self::distinct_on::DistinctOnClause;
1112
pub use self::distinct_on::OrderDecorator;
1213

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use crate::expression::{Expression, ValidGrouping};
2+
use crate::pg::Pg;
3+
use crate::query_builder::{AsQuery, AstPass, FromClause, QueryFragment, QueryId, SelectStatement};
4+
use crate::query_source::QuerySource;
5+
use crate::result::QueryResult;
6+
use crate::sql_types::{Double, SmallInt};
7+
use crate::{JoinTo, SelectableExpression, Table};
8+
use std::marker::PhantomData;
9+
10+
#[doc(hidden)]
11+
pub trait TablesampleMethod: Clone {
12+
fn method_name_sql() -> &'static str;
13+
}
14+
15+
#[derive(Clone, Copy, Debug)]
16+
/// Used to specify the `BERNOULLI` sampling method.
17+
pub struct BernoulliMethod;
18+
19+
impl TablesampleMethod for BernoulliMethod {
20+
fn method_name_sql() -> &'static str {
21+
"BERNOULLI"
22+
}
23+
}
24+
25+
#[derive(Clone, Copy, Debug)]
26+
/// Used to specify the `SYSTEM` sampling method.
27+
pub struct SystemMethod;
28+
29+
impl TablesampleMethod for SystemMethod {
30+
fn method_name_sql() -> &'static str {
31+
"SYSTEM"
32+
}
33+
}
34+
35+
/// Represents a query with a `TABLESAMPLE` clause.
36+
#[derive(Debug, Clone, Copy)]
37+
pub struct Tablesample<S, TSM>
38+
where
39+
TSM: TablesampleMethod,
40+
{
41+
source: S,
42+
method: PhantomData<TSM>,
43+
portion: i16,
44+
seed: Option<f64>,
45+
}
46+
47+
impl<S, TSM> Tablesample<S, TSM>
48+
where
49+
TSM: TablesampleMethod,
50+
{
51+
pub(crate) fn new(source: S, portion: i16) -> Tablesample<S, TSM> {
52+
Tablesample {
53+
source,
54+
method: PhantomData,
55+
portion,
56+
seed: None,
57+
}
58+
}
59+
60+
/// This method allows you to specify the random number generator seed to use in the sampling
61+
/// method. This allows you to obtain repeatable results.
62+
pub fn with_seed(self, seed: f64) -> Tablesample<S, TSM> {
63+
Tablesample {
64+
source: self.source,
65+
method: self.method,
66+
portion: self.portion,
67+
seed: Some(seed),
68+
}
69+
}
70+
}
71+
72+
impl<S, TSM> QueryId for Tablesample<S, TSM>
73+
where
74+
S: QueryId,
75+
TSM: TablesampleMethod,
76+
{
77+
type QueryId = ();
78+
const HAS_STATIC_QUERY_ID: bool = false;
79+
}
80+
81+
impl<S, TSM> QuerySource for Tablesample<S, TSM>
82+
where
83+
S: Table + Clone,
84+
TSM: TablesampleMethod,
85+
<S as QuerySource>::DefaultSelection:
86+
ValidGrouping<()> + SelectableExpression<Tablesample<S, TSM>>,
87+
{
88+
type FromClause = Self;
89+
type DefaultSelection = <S as QuerySource>::DefaultSelection;
90+
91+
fn from_clause(&self) -> Self::FromClause {
92+
self.clone()
93+
}
94+
95+
fn default_selection(&self) -> Self::DefaultSelection {
96+
self.source.default_selection()
97+
}
98+
}
99+
100+
impl<S, TSM> QueryFragment<Pg> for Tablesample<S, TSM>
101+
where
102+
S: QueryFragment<Pg>,
103+
TSM: TablesampleMethod,
104+
{
105+
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
106+
self.source.walk_ast(out.reborrow())?;
107+
out.push_sql(" TABLESAMPLE ");
108+
out.push_sql(TSM::method_name_sql());
109+
out.push_sql("(");
110+
out.push_bind_param::<SmallInt, _>(&self.portion)?;
111+
out.push_sql(")");
112+
if let Some(f) = &self.seed {
113+
out.push_sql(" REPEATABLE(");
114+
out.push_bind_param::<Double, _>(f)?;
115+
out.push_sql(")");
116+
}
117+
Ok(())
118+
}
119+
}
120+
121+
impl<S, TSM> AsQuery for Tablesample<S, TSM>
122+
where
123+
S: Table + Clone,
124+
TSM: TablesampleMethod,
125+
<S as QuerySource>::DefaultSelection:
126+
ValidGrouping<()> + SelectableExpression<Tablesample<S, TSM>>,
127+
{
128+
type SqlType = <<Self as QuerySource>::DefaultSelection as Expression>::SqlType;
129+
type Query = SelectStatement<FromClause<Self>>;
130+
fn as_query(self) -> Self::Query {
131+
SelectStatement::simple(self)
132+
}
133+
}
134+
135+
impl<S, T, TSM> JoinTo<T> for Tablesample<S, TSM>
136+
where
137+
S: JoinTo<T>,
138+
T: Table,
139+
S: Table,
140+
TSM: TablesampleMethod,
141+
{
142+
type FromClause = <S as JoinTo<T>>::FromClause;
143+
type OnClause = <S as JoinTo<T>>::OnClause;
144+
145+
fn join_target(rhs: T) -> (Self::FromClause, Self::OnClause) {
146+
<S as JoinTo<T>>::join_target(rhs)
147+
}
148+
}
149+
150+
impl<S, TSM> Table for Tablesample<S, TSM>
151+
where
152+
S: Table + Clone + AsQuery,
153+
TSM: TablesampleMethod,
154+
155+
<S as Table>::PrimaryKey: SelectableExpression<Tablesample<S, TSM>>,
156+
<S as Table>::AllColumns: SelectableExpression<Tablesample<S, TSM>>,
157+
<S as QuerySource>::DefaultSelection:
158+
ValidGrouping<()> + SelectableExpression<Tablesample<S, TSM>>,
159+
{
160+
type PrimaryKey = <S as Table>::PrimaryKey;
161+
type AllColumns = <S as Table>::AllColumns;
162+
163+
fn primary_key(&self) -> Self::PrimaryKey {
164+
self.source.primary_key()
165+
}
166+
167+
fn all_columns() -> Self::AllColumns {
168+
S::all_columns()
169+
}
170+
}
171+
172+
#[cfg(test)]
173+
mod test {
174+
use super::*;
175+
use crate::backend::Backend;
176+
use crate::pg::Pg;
177+
use crate::query_builder::QueryBuilder;
178+
use diesel::dsl::*;
179+
use diesel::*;
180+
181+
macro_rules! assert_sql {
182+
($query:expr, $sql:expr) => {
183+
let mut query_builder = <Pg as Backend>::QueryBuilder::default();
184+
$query.to_sql(&mut query_builder, &Pg).unwrap();
185+
let sql = query_builder.finish();
186+
assert_eq!(sql, $sql);
187+
};
188+
}
189+
190+
table! {
191+
users {
192+
id -> Integer,
193+
name -> VarChar,
194+
}
195+
}
196+
197+
#[test]
198+
fn test_generated_tablesample_sql() {
199+
assert_sql!(
200+
users::table.tablesample_bernoulli(10),
201+
"\"users\" TABLESAMPLE BERNOULLI($1)"
202+
);
203+
204+
assert_sql!(
205+
users::table.tablesample_system(10),
206+
"\"users\" TABLESAMPLE SYSTEM($1)"
207+
);
208+
209+
assert_sql!(
210+
users::table.tablesample_system(10).with_seed(42.0),
211+
"\"users\" TABLESAMPLE SYSTEM($1) REPEATABLE($2)"
212+
);
213+
}
214+
}

diesel/src/query_builder/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ pub(crate) use self::insert_statement::ColumnList;
120120
#[cfg(feature = "postgres_backend")]
121121
pub use crate::pg::query_builder::only::Only;
122122

123+
#[cfg(feature = "postgres_backend")]
124+
pub use crate::pg::query_builder::tablesample::{Tablesample, TablesampleMethod};
125+
123126
use crate::backend::Backend;
124127
use crate::result::QueryResult;
125128
use std::error::Error;

0 commit comments

Comments
 (0)