Skip to content

Commit a4e1216

Browse files
authored
[nexus] Make 'update_and_check' CTE explicitly request columns (#4572)
Related to #4570 , but not a direct fix for it This PR removes a usage of ".\*" from a SQL query. Using ".\*" in sql queries is somewhat risky -- it makes an implicit dependency on order, and can make backwards compatibility difficult in certain circumstances. Instead, this PR provides a `ColumnWalker`, for converting a tuple of columns to an iterator, and requests the expected columns explicitly.
1 parent bb7ee84 commit a4e1216

File tree

3 files changed

+141
-20
lines changed

3 files changed

+141
-20
lines changed
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! CTE utility for iterating over all columns in a table.
6+
7+
use diesel::prelude::*;
8+
use std::marker::PhantomData;
9+
10+
/// Used to iterate over a tuple of columns ("T").
11+
///
12+
/// Diesel exposes "AllColumns" as a tuple, which is difficult to iterate over
13+
/// -- after all, all the types are distinct. However, each of these types
14+
/// implements "Column", so we can use a macro to provide a
15+
/// "convertion-to-iterator" implemenation for our expected tuples.
16+
pub(crate) struct ColumnWalker<T> {
17+
remaining: PhantomData<T>,
18+
}
19+
20+
impl<T> ColumnWalker<T> {
21+
pub fn new() -> Self {
22+
Self { remaining: PhantomData }
23+
}
24+
}
25+
26+
macro_rules! impl_column_walker {
27+
( $len:literal $($column:ident)+ ) => (
28+
impl<$($column: Column),+> IntoIterator for ColumnWalker<($($column,)+)> {
29+
type Item = &'static str;
30+
type IntoIter = std::array::IntoIter<Self::Item, $len>;
31+
32+
fn into_iter(self) -> Self::IntoIter {
33+
[$($column::NAME,)+].into_iter()
34+
}
35+
}
36+
);
37+
}
38+
39+
// implementations for 1 - 32 columns
40+
impl_column_walker! { 1 A }
41+
impl_column_walker! { 2 A B }
42+
impl_column_walker! { 3 A B C }
43+
impl_column_walker! { 4 A B C D }
44+
impl_column_walker! { 5 A B C D E }
45+
impl_column_walker! { 6 A B C D E F }
46+
impl_column_walker! { 7 A B C D E F G }
47+
impl_column_walker! { 8 A B C D E F G H }
48+
impl_column_walker! { 9 A B C D E F G H I }
49+
impl_column_walker! { 10 A B C D E F G H I J }
50+
impl_column_walker! { 11 A B C D E F G H I J K }
51+
impl_column_walker! { 12 A B C D E F G H I J K L }
52+
impl_column_walker! { 13 A B C D E F G H I J K L M }
53+
impl_column_walker! { 14 A B C D E F G H I J K L M N }
54+
impl_column_walker! { 15 A B C D E F G H I J K L M N O }
55+
impl_column_walker! { 16 A B C D E F G H I J K L M N O P }
56+
impl_column_walker! { 17 A B C D E F G H I J K L M N O P Q }
57+
impl_column_walker! { 18 A B C D E F G H I J K L M N O P Q R }
58+
impl_column_walker! { 19 A B C D E F G H I J K L M N O P Q R S }
59+
impl_column_walker! { 20 A B C D E F G H I J K L M N O P Q R S T }
60+
impl_column_walker! { 21 A B C D E F G H I J K L M N O P Q R S T U }
61+
impl_column_walker! { 22 A B C D E F G H I J K L M N O P Q R S T U V }
62+
impl_column_walker! { 23 A B C D E F G H I J K L M N O P Q R S T U V W }
63+
impl_column_walker! { 24 A B C D E F G H I J K L M N O P Q R S T U V W X }
64+
impl_column_walker! { 25 A B C D E F G H I J K L M N O P Q R S T U V W X Y }
65+
impl_column_walker! { 26 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
66+
impl_column_walker! { 27 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 }
67+
impl_column_walker! { 28 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 }
68+
impl_column_walker! { 29 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 }
69+
impl_column_walker! { 30 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 }
70+
impl_column_walker! { 31 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 E1 }
71+
impl_column_walker! { 32 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 E1 F1 }
72+
73+
#[cfg(test)]
74+
mod test {
75+
use super::*;
76+
77+
table! {
78+
test_schema.test_table (id) {
79+
id -> Uuid,
80+
value -> Int4,
81+
time_deleted -> Nullable<Timestamptz>,
82+
}
83+
}
84+
85+
// We can convert all a tables columns into an iteratable format.
86+
#[test]
87+
fn test_walk_table() {
88+
let all_columns =
89+
ColumnWalker::<<test_table::table as Table>::AllColumns>::new();
90+
91+
let mut iter = all_columns.into_iter();
92+
assert_eq!(iter.next(), Some("id"));
93+
assert_eq!(iter.next(), Some("value"));
94+
assert_eq!(iter.next(), Some("time_deleted"));
95+
assert_eq!(iter.next(), None);
96+
}
97+
98+
// We can, if we want to, also make a ColumnWalker out of an arbitrary tuple
99+
// of columns.
100+
#[test]
101+
fn test_walk_columns() {
102+
let all_columns = ColumnWalker::<(
103+
test_table::columns::id,
104+
test_table::columns::value,
105+
)>::new();
106+
107+
let mut iter = all_columns.into_iter();
108+
assert_eq!(iter.next(), Some("id"));
109+
assert_eq!(iter.next(), Some("value"));
110+
assert_eq!(iter.next(), None);
111+
}
112+
}

nexus/db-queries/src/db/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod collection_attach;
1212
pub mod collection_detach;
1313
pub mod collection_detach_many;
1414
pub mod collection_insert;
15+
mod column_walker;
1516
mod config;
1617
mod cte_utils;
1718
// This is marked public for use by the integration tests

nexus/db-queries/src/db/update_and_check.rs

+28-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
//! CTE implementation for "UPDATE with extended return status".
66
7+
use super::column_walker::ColumnWalker;
78
use super::pool::DbConnection;
89
use async_bb8_diesel::AsyncRunQueryDsl;
910
use diesel::associations::HasTable;
@@ -21,7 +22,7 @@ use std::marker::PhantomData;
2122
/// allows referencing generics with names (and extending usage
2223
/// without re-stating those generic parameters everywhere).
2324
pub trait UpdateStatementExt {
24-
type Table: QuerySource;
25+
type Table: Table + QuerySource;
2526
type WhereClause;
2627
type Changeset;
2728

@@ -32,7 +33,7 @@ pub trait UpdateStatementExt {
3233

3334
impl<T, U, V> UpdateStatementExt for UpdateStatement<T, U, V>
3435
where
35-
T: QuerySource,
36+
T: Table + QuerySource,
3637
{
3738
type Table = T;
3839
type WhereClause = U;
@@ -201,11 +202,11 @@ where
201202
///
202203
/// ```text
203204
/// // WITH found AS (SELECT <primary key> FROM T WHERE <primary key = value>)
204-
/// // updated AS (UPDATE T SET <constraints> RETURNING *)
205+
/// // updated AS (UPDATE T SET <constraints> RETURNING <primary key>)
205206
/// // SELECT
206207
/// // found.<primary key>
207208
/// // updated.<primary key>
208-
/// // found.*
209+
/// // found.<all columns>
209210
/// // FROM
210211
/// // found
211212
/// // LEFT JOIN
@@ -217,41 +218,48 @@ impl<US, K, Q> QueryFragment<Pg> for UpdateAndQueryStatement<US, K, Q>
217218
where
218219
US: UpdateStatementExt,
219220
US::Table: HasTable<Table = US::Table> + Table,
221+
ColumnWalker<<<US as UpdateStatementExt>::Table as Table>::AllColumns>:
222+
IntoIterator<Item = &'static str>,
220223
PrimaryKey<US>: diesel::Column,
221224
UpdateStatement<US::Table, US::WhereClause, US::Changeset>:
222225
QueryFragment<Pg>,
223226
{
224227
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
228+
let primary_key = <PrimaryKey<US> as Column>::NAME;
229+
225230
out.push_sql("WITH found AS (");
226231
self.find_subquery.walk_ast(out.reborrow())?;
227232
out.push_sql("), updated AS (");
228233
self.update_statement.walk_ast(out.reborrow())?;
229-
// TODO: Only need primary? Or would we actually want
230-
// to pass the returned rows back through the result?
231-
out.push_sql(" RETURNING *) ");
234+
out.push_sql(" RETURNING ");
235+
out.push_identifier(primary_key)?;
236+
out.push_sql(") ");
232237

233238
out.push_sql("SELECT");
234239

235-
let name = <PrimaryKey<US> as Column>::NAME;
236240
out.push_sql(" found.");
237-
out.push_identifier(name)?;
241+
out.push_identifier(primary_key)?;
238242
out.push_sql(", updated.");
239-
out.push_identifier(name)?;
240-
// TODO: I'd prefer to list all columns explicitly. But how?
241-
// The types exist within Table::AllColumns, and each one
242-
// has a name as "<C as Column>::Name".
243-
// But Table::AllColumns is a tuple, which makes iteration
244-
// a pain.
245-
//
246-
// TODO: Technically, we're repeating the PK here.
247-
out.push_sql(", found.*");
243+
out.push_identifier(primary_key)?;
244+
245+
// List all the "found" columns explicitly.
246+
// This admittedly repeats the primary key, but that keeps the query
247+
// "simple" since it returns all columns in the same order as
248+
// AllColumns.
249+
let all_columns = ColumnWalker::<
250+
<<US as UpdateStatementExt>::Table as Table>::AllColumns,
251+
>::new();
252+
for column in all_columns.into_iter() {
253+
out.push_sql(", found.");
254+
out.push_identifier(column)?;
255+
}
248256

249257
out.push_sql(" FROM found LEFT JOIN updated ON");
250258
out.push_sql(" found.");
251-
out.push_identifier(name)?;
259+
out.push_identifier(primary_key)?;
252260
out.push_sql(" = ");
253261
out.push_sql("updated.");
254-
out.push_identifier(name)?;
262+
out.push_identifier(primary_key)?;
255263

256264
Ok(())
257265
}

0 commit comments

Comments
 (0)