Skip to content

Commit d931394

Browse files
authored
Refactor raw paginator for affinity specifically (#7851)
Refactor some of the "raw pagination" logic into `RawPaginator`, to help de-duplicate pagination logic in cases that too complex (e.g., the result of JOINs, UNIONs) for the existing Diesel pagination tools.
1 parent 467e0af commit d931394

11 files changed

+370
-240
lines changed

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

+44
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ impl<T> ColumnWalker<T> {
2828

2929
macro_rules! impl_column_walker {
3030
( $len:literal $($column:ident)+ ) => (
31+
/// Returns all columns with the "(prefix)." string prepended.
32+
///
33+
/// For example, with the columns "id, name" and the prefix "foo:
34+
/// The output string would be: "foo.id, foo.name"
3135
#[allow(dead_code)]
3236
impl<$($column: Column),+> ColumnWalker<($($column,)+)> {
3337
pub fn with_prefix(prefix: &'static str) -> TrustedStr {
@@ -42,6 +46,46 @@ macro_rules! impl_column_walker {
4246
}
4347
}
4448

49+
/// Identical to [Self::with_prefix], but also aliases each column.
50+
///
51+
/// The aliased name is "(prefix)_(column name)".
52+
///
53+
/// For example, with the columns "id, name" and the prefix "foo:
54+
/// The output string would be: "foo.id as foo_id, foo.name as foo_name"
55+
#[allow(dead_code)]
56+
impl<$($column: Column),+> ColumnWalker<($($column,)+)> {
57+
pub fn with_prefix_and_alias(prefix: &'static str) -> TrustedStr {
58+
// This string is derived from:
59+
// - The "table" type, with associated columns, which
60+
// are not controlled by an arbitrary user, and
61+
// - The "prefix" type, which is a "&'static str" (AKA,
62+
// hopefully known at compile-time, and not leaked).
63+
TrustedStr::i_take_responsibility_for_validating_this_string(
64+
[
65+
$(
66+
format!(
67+
"{prefix}.{column} as {prefix}_{column}",
68+
prefix = prefix, column = $column::NAME
69+
),
70+
)+
71+
].join(", ")
72+
)
73+
}
74+
}
75+
76+
/// Returns all columns without any suffix.
77+
#[allow(dead_code)]
78+
impl<$($column: Column),+> ColumnWalker<($($column,)+)> {
79+
pub fn as_str() -> TrustedStr {
80+
// This string is derived from:
81+
// - The "table" type, with associated columns, which
82+
// are not controlled by an arbitrary user
83+
TrustedStr::i_take_responsibility_for_validating_this_string(
84+
[$($column::NAME,)+].join(", ")
85+
)
86+
}
87+
}
88+
4589
impl<$($column: Column),+> IntoIterator for ColumnWalker<($($column,)+)> {
4690
type Item = &'static str;
4791
type IntoIter = std::array::IntoIter<Self::Item, $len>;

nexus/db-queries/src/db/datastore/affinity.rs

+61-158
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::authz::ApiResource;
1010
use crate::db;
1111
use crate::db::collection_insert::AsyncInsertError;
1212
use crate::db::collection_insert::DatastoreCollection;
13+
use crate::db::column_walker::AllColumnsOf;
1314
use crate::db::datastore::InstanceStateComputer;
1415
use crate::db::datastore::OpContext;
1516
use crate::db::error::ErrorHandler;
@@ -27,15 +28,15 @@ use crate::db::model::Name;
2728
use crate::db::model::Project;
2829
use crate::db::model::VmmState;
2930
use crate::db::model::VmmStateEnum;
30-
use crate::db::pagination::paginated;
31-
use crate::db::raw_query_builder::QueryBuilder;
31+
use crate::db::pagination::RawPaginator;
3232
use crate::transaction_retry::OptionalError;
3333
use async_bb8_diesel::AsyncRunQueryDsl;
3434
use chrono::Utc;
35+
use diesel::helper_types::AsSelect;
36+
use diesel::pg::Pg;
3537
use diesel::prelude::*;
3638
use omicron_common::api::external;
3739
use omicron_common::api::external::CreateResult;
38-
use omicron_common::api::external::DataPageParams;
3940
use omicron_common::api::external::DeleteResult;
4041
use omicron_common::api::external::Error;
4142
use omicron_common::api::external::ListResultVec;
@@ -47,7 +48,6 @@ use omicron_uuid_kinds::AffinityGroupUuid;
4748
use omicron_uuid_kinds::AntiAffinityGroupUuid;
4849
use omicron_uuid_kinds::GenericUuid;
4950
use omicron_uuid_kinds::InstanceUuid;
50-
use ref_cast::RefCast;
5151
use uuid::Uuid;
5252

5353
impl DataStore {
@@ -61,22 +61,21 @@ impl DataStore {
6161

6262
opctx.authorize(authz::Action::ListChildren, authz_project).await?;
6363

64-
match pagparams {
65-
PaginatedBy::Id(pagparams) => {
66-
paginated(dsl::affinity_group, dsl::id, &pagparams)
67-
}
68-
PaginatedBy::Name(pagparams) => paginated(
69-
dsl::affinity_group,
70-
dsl::name,
71-
&pagparams.map_name(|n| Name::ref_cast(n)),
72-
),
73-
}
74-
.filter(dsl::project_id.eq(authz_project.id()))
75-
.filter(dsl::time_deleted.is_null())
76-
.select(AffinityGroup::as_select())
77-
.get_results_async(&*self.pool_connection_authorized(opctx).await?)
78-
.await
79-
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
64+
let mut paginator = RawPaginator::new();
65+
paginator
66+
.source()
67+
.sql("SELECT ")
68+
.sql(AllColumnsOf::<dsl::affinity_group>::as_str())
69+
.sql(" FROM affinity_group WHERE project_id = ")
70+
.param()
71+
.bind::<diesel::sql_types::Uuid, _>(authz_project.id())
72+
.sql(" AND time_deleted IS NULL");
73+
paginator
74+
.paginate_by_id_or_name(pagparams)
75+
.query::<AsSelect<AffinityGroup, Pg>>()
76+
.get_results_async(&*self.pool_connection_authorized(opctx).await?)
77+
.await
78+
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
8079
}
8180

8281
pub async fn anti_affinity_group_list(
@@ -89,22 +88,21 @@ impl DataStore {
8988

9089
opctx.authorize(authz::Action::ListChildren, authz_project).await?;
9190

92-
match pagparams {
93-
PaginatedBy::Id(pagparams) => {
94-
paginated(dsl::anti_affinity_group, dsl::id, &pagparams)
95-
}
96-
PaginatedBy::Name(pagparams) => paginated(
97-
dsl::anti_affinity_group,
98-
dsl::name,
99-
&pagparams.map_name(|n| Name::ref_cast(n)),
100-
),
101-
}
102-
.filter(dsl::project_id.eq(authz_project.id()))
103-
.filter(dsl::time_deleted.is_null())
104-
.select(AntiAffinityGroup::as_select())
105-
.get_results_async(&*self.pool_connection_authorized(opctx).await?)
106-
.await
107-
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
91+
let mut paginator = RawPaginator::new();
92+
paginator
93+
.source()
94+
.sql("SELECT ")
95+
.sql(AllColumnsOf::<dsl::anti_affinity_group>::as_str())
96+
.sql(" FROM anti_affinity_group WHERE project_id = ")
97+
.param()
98+
.bind::<diesel::sql_types::Uuid, _>(authz_project.id())
99+
.sql(" AND time_deleted IS NULL");
100+
paginator
101+
.paginate_by_id_or_name(pagparams)
102+
.query::<AsSelect<AntiAffinityGroup, Pg>>()
103+
.get_results_async(&*self.pool_connection_authorized(opctx).await?)
104+
.await
105+
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
108106
}
109107

110108
pub async fn affinity_group_create(
@@ -352,11 +350,13 @@ impl DataStore {
352350
) -> ListResultVec<external::AffinityGroupMember> {
353351
opctx.authorize(authz::Action::Read, authz_affinity_group).await?;
354352

355-
let mut query = QueryBuilder::new()
353+
let mut paginator = RawPaginator::new();
354+
paginator
355+
.source()
356356
.sql(
357357
"
358-
SELECT * FROM (
359-
SELECT instance.id as id,
358+
SELECT
359+
instance.id as id,
360360
instance.name as name,
361361
instance.state,
362362
instance.migration_id,
@@ -372,56 +372,8 @@ impl DataStore {
372372
group_id = ",
373373
)
374374
.param()
375-
.bind::<diesel::sql_types::Uuid, _>(authz_affinity_group.id())
376-
.sql(") ");
377-
378-
let (direction, limit) = match pagparams {
379-
PaginatedBy::Id(p) => (p.direction, p.limit),
380-
PaginatedBy::Name(p) => (p.direction, p.limit),
381-
};
382-
let asc = match direction {
383-
dropshot::PaginationOrder::Ascending => true,
384-
dropshot::PaginationOrder::Descending => false,
385-
};
386-
387-
match pagparams {
388-
PaginatedBy::Id(DataPageParams { marker, .. }) => {
389-
if let Some(id) = marker {
390-
query = query
391-
.sql("WHERE id ")
392-
.sql(if asc { ">" } else { "<" })
393-
.sql(" ")
394-
.param()
395-
.bind::<diesel::sql_types::Uuid, _>(**id);
396-
};
397-
query = query.sql(" ORDER BY id ");
398-
}
399-
PaginatedBy::Name(DataPageParams { marker, .. }) => {
400-
if let Some(name) = marker {
401-
query = query
402-
.sql("WHERE name ")
403-
.sql(if asc { ">" } else { "<" })
404-
.sql(" ")
405-
.param()
406-
.bind::<diesel::sql_types::Text, _>(Name(
407-
(*name).clone(),
408-
));
409-
};
410-
query = query.sql(" ORDER BY name ");
411-
}
412-
}
413-
if asc {
414-
query = query.sql("ASC ");
415-
} else {
416-
query = query.sql("DESC ");
417-
}
418-
419-
query = query
420-
.sql(" LIMIT ")
421-
.param()
422-
.bind::<diesel::sql_types::BigInt, _>(i64::from(limit.get()));
423-
424-
query
375+
.bind::<diesel::sql_types::Uuid, _>(authz_affinity_group.id());
376+
paginator.paginate_by_id_or_name(pagparams)
425377
.query::<(
426378
diesel::sql_types::Uuid,
427379
diesel::sql_types::Text,
@@ -457,77 +409,28 @@ impl DataStore {
457409
) -> ListResultVec<external::AntiAffinityGroupMember> {
458410
opctx.authorize(authz::Action::Read, authz_anti_affinity_group).await?;
459411

460-
let (direction, limit) = match pagparams {
461-
PaginatedBy::Id(p) => (p.direction, p.limit),
462-
PaginatedBy::Name(p) => (p.direction, p.limit),
463-
};
464-
let asc = match direction {
465-
dropshot::PaginationOrder::Ascending => true,
466-
dropshot::PaginationOrder::Descending => false,
467-
};
468-
469-
let mut query = QueryBuilder::new()
470-
.sql(
471-
"SELECT id,name,instance_state,migration_id,vmm_state
472-
FROM (
473-
SELECT
474-
instance.id as id,
475-
instance.name as name,
476-
instance.state as instance_state,
477-
instance.migration_id as migration_id,
478-
vmm.state as vmm_state
479-
FROM anti_affinity_group_instance_membership
480-
INNER JOIN instance
481-
ON instance.id = anti_affinity_group_instance_membership.instance_id
482-
LEFT JOIN vmm
483-
ON instance.active_propolis_id = vmm.id
484-
WHERE
485-
instance.time_deleted IS NULL AND
486-
vmm.time_deleted IS NULL AND
487-
group_id = ",
412+
let mut paginator = RawPaginator::new();
413+
paginator.source()
414+
.sql("
415+
SELECT
416+
instance.id as id,
417+
instance.name as name,
418+
instance.state as instance_state,
419+
instance.migration_id as migration_id,
420+
vmm.state as vmm_state
421+
FROM anti_affinity_group_instance_membership
422+
INNER JOIN instance
423+
ON instance.id = anti_affinity_group_instance_membership.instance_id
424+
LEFT JOIN vmm
425+
ON instance.active_propolis_id = vmm.id
426+
WHERE
427+
instance.time_deleted IS NULL AND
428+
vmm.time_deleted IS NULL AND
429+
group_id = ",
488430
)
489431
.param()
490-
.bind::<diesel::sql_types::Uuid, _>(authz_anti_affinity_group.id())
491-
.sql(") ");
492-
493-
match pagparams {
494-
PaginatedBy::Id(DataPageParams { marker, .. }) => {
495-
if let Some(id) = marker {
496-
query = query
497-
.sql("WHERE id ")
498-
.sql(if asc { ">" } else { "<" })
499-
.sql(" ")
500-
.param()
501-
.bind::<diesel::sql_types::Uuid, _>(**id);
502-
};
503-
query = query.sql(" ORDER BY id ");
504-
}
505-
PaginatedBy::Name(DataPageParams { marker, .. }) => {
506-
if let Some(name) = marker {
507-
query = query
508-
.sql("WHERE name ")
509-
.sql(if asc { ">" } else { "<" })
510-
.sql(" ")
511-
.param()
512-
.bind::<diesel::sql_types::Text, _>(Name(
513-
(*name).clone(),
514-
));
515-
};
516-
query = query.sql(" ORDER BY name ");
517-
}
518-
}
519-
if asc {
520-
query = query.sql("ASC ");
521-
} else {
522-
query = query.sql("DESC ");
523-
}
524-
525-
query = query
526-
.sql(" LIMIT ")
527-
.param()
528-
.bind::<diesel::sql_types::BigInt, _>(i64::from(limit.get()));
529-
530-
query
432+
.bind::<diesel::sql_types::Uuid, _>(authz_anti_affinity_group.id());
433+
paginator.paginate_by_id_or_name(pagparams)
531434
.query::<(
532435
diesel::sql_types::Uuid,
533436
diesel::sql_types::Text,

nexus/db-queries/src/db/datastore/cockroachdb_settings.rs

+13-16
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,17 @@ impl DataStore {
6666
type QueryRow = (sql_types::Text, sql_types::Text, sql_types::Text);
6767

6868
let conn = self.pool_connection_authorized(opctx).await?;
69-
let output: QueryOutput = QueryBuilder::new()
69+
let mut query = QueryBuilder::new();
70+
query
7071
.sql("SELECT ")
7172
.sql(STATE_FINGERPRINT_SQL)
7273
.sql(", * FROM ")
7374
.sql("[SHOW CLUSTER SETTING version], ")
74-
.sql("[SHOW CLUSTER SETTING cluster.preserve_downgrade_option]")
75-
.query::<QueryRow>()
76-
.get_result_async(&*conn)
77-
.await
78-
.map_err(|err| {
79-
public_error_from_diesel(err, ErrorHandler::Server)
80-
})?;
75+
.sql("[SHOW CLUSTER SETTING cluster.preserve_downgrade_option]");
76+
let output: QueryOutput =
77+
query.query::<QueryRow>().get_result_async(&*conn).await.map_err(
78+
|err| public_error_from_diesel(err, ErrorHandler::Server),
79+
)?;
8180
Ok(CockroachDbSettings {
8281
state_fingerprint: output.state_fingerprint,
8382
version: output.version,
@@ -96,7 +95,8 @@ impl DataStore {
9695
value: String,
9796
) -> Result<(), Error> {
9897
let conn = self.pool_connection_authorized(opctx).await?;
99-
QueryBuilder::new()
98+
let mut query = QueryBuilder::new();
99+
query
100100
.sql("SET CLUSTER SETTING ")
101101
.sql(setting)
102102
// `CASE` is the one conditional statement we get out of the
@@ -114,13 +114,10 @@ impl DataStore {
114114
// below in `test_state_fingerprint`).
115115
.sql(" ELSE NULL END")
116116
.bind::<sql_types::Text, _>(state_fingerprint)
117-
.bind::<sql_types::Text, _>(value.clone())
118-
.query::<()>()
119-
.execute_async(&*conn)
120-
.await
121-
.map_err(|err| {
122-
public_error_from_diesel(err, ErrorHandler::Server)
123-
})?;
117+
.bind::<sql_types::Text, _>(value.clone());
118+
query.query::<()>().execute_async(&*conn).await.map_err(|err| {
119+
public_error_from_diesel(err, ErrorHandler::Server)
120+
})?;
124121
info!(
125122
opctx.log,
126123
"set cockroachdb setting";

0 commit comments

Comments
 (0)