Skip to content

Commit

Permalink
arcane chicanery which bends the type system to our will
Browse files Browse the repository at this point in the history
  • Loading branch information
smklein committed Feb 15, 2024
1 parent 291791d commit c868f71
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 28 deletions.
4 changes: 2 additions & 2 deletions nexus/db-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ fn build_resource_identity(
let identity_name = format_ident!("{}Identity", struct_name);
quote! {
#[doc = #identity_doc]
#[derive(Clone, Debug, PartialEq, Eq, Selectable, Queryable, QueryableByName, Insertable, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Selectable, Queryable, Insertable, serde::Serialize, serde::Deserialize)]
#[diesel(table_name = #table_name) ]
pub struct #identity_name {
pub id: ::uuid::Uuid,
Expand Down Expand Up @@ -322,7 +322,7 @@ fn build_asset_identity(
let identity_name = format_ident!("{}Identity", struct_name);
quote! {
#[doc = #identity_doc]
#[derive(Clone, Debug, PartialEq, Selectable, Queryable, QueryableByName, Insertable, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Selectable, Queryable, Insertable, serde::Serialize, serde::Deserialize)]
#[diesel(table_name = #table_name) ]
pub struct #identity_name {
pub id: ::uuid::Uuid,
Expand Down
1 change: 0 additions & 1 deletion nexus/db-model/src/dataset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use uuid::Uuid;
/// available to a service on the Sled.
#[derive(
Queryable,
QueryableByName,
Insertable,
Debug,
Clone,
Expand Down
1 change: 0 additions & 1 deletion nexus/db-model/src/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use uuid::Uuid;
/// allocated within a volume.
#[derive(
Queryable,
QueryableByName,
Insertable,
Debug,
Clone,
Expand Down
21 changes: 5 additions & 16 deletions nexus/db-queries/src/db/datastore/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,30 +128,19 @@ impl DataStore {
let (blocks_per_extent, extent_count) =
Self::get_crucible_allocation(&block_size, size);

#[derive(diesel::QueryableByName, Queryable)]
struct DatasetAndRegion {
#[diesel(embed)]
dataset: Dataset,
#[diesel(embed)]
region: Region,
}

let dataset_and_regions: Vec<_> =
let dataset_and_regions: Vec<(Dataset, Region)> =
crate::db::queries::region_allocation::allocation_query(
volume_id,
block_size.to_bytes() as u64,
blocks_per_extent,
extent_count,
allocation_strategy,
)
.get_results_async::<DatasetAndRegion>(
&*self.pool_connection_authorized(&opctx).await?,
)
.get_results_async(&*self.pool_connection_authorized(&opctx).await?)
.await
.map_err(|e| crate::db::queries::region_allocation::from_diesel(e))?
.into_iter()
.map(|DatasetAndRegion { dataset, region }| (dataset, region))
.collect::<Vec<_>>();
.map_err(|e| {
crate::db::queries::region_allocation::from_diesel(e)
})?;

info!(
self.log,
Expand Down
13 changes: 7 additions & 6 deletions nexus/db-queries/src/db/queries/region_allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
use crate::db::column_walker::AllColumnsOf;
use crate::db::datastore::REGION_REDUNDANCY_THRESHOLD;
use crate::db::raw_query_builder::QueryBuilder;
use crate::db::model::{Dataset, Region};
use crate::db::raw_query_builder::{QueryBuilder, TypedSqlQuery};
use crate::db::schema;
use crate::db::true_or_cast_error::matches_sentinel;
use const_format::concatcp;
Expand Down Expand Up @@ -63,17 +64,17 @@ pub fn from_diesel(e: DieselError) -> external::Error {
error::public_error_from_diesel(e, error::ErrorHandler::Server)
}

type SelectableSql<T> = <
<T as diesel::Selectable<Pg>>::SelectExpression as diesel::Expression
>::SqlType;

pub fn allocation_query(
volume_id: uuid::Uuid,
block_size: u64,
blocks_per_extent: u64,
extent_count: u64,
allocation_strategy: &RegionAllocationStrategy,
) -> diesel::query_builder::BoxedSqlQuery<
'static,
Pg,
diesel::query_builder::SqlQuery,
> {
) -> TypedSqlQuery<(SelectableSql<Dataset>, SelectableSql<Region>)> {
let (seed, distinct_sleds) = {
let (input_seed, distinct_sleds) = match allocation_strategy {
RegionAllocationStrategy::Random { seed } => (seed, false),
Expand Down
44 changes: 42 additions & 2 deletions nexus/db-queries/src/db/raw_query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
//! These largely side-step Diesel's type system,
//! and are recommended for more complex CTE
use crate::db::pool::DbConnection;
use diesel::pg::Pg;
use diesel::query_builder::{AstPass, Query, QueryFragment, QueryId};
use diesel::sql_types;
use diesel::RunQueryDsl;
use std::cell::Cell;
use std::marker::PhantomData;

// Keeps a counter to "how many bind parameters have been used" to
// aid in the construction of the query string.
Expand Down Expand Up @@ -149,7 +153,43 @@ impl QueryBuilder {
}

/// Takes the final boxed query
pub fn query(self) -> BoxedQuery {
self.query
pub fn query<T>(self) -> TypedSqlQuery<T> {
TypedSqlQuery { inner: self.query, _phantom: PhantomData }
}
}

/// Diesel's [diesel::query_builder::BoxedSqlQuery] has a few drawbacks that
/// make wrapper more palatable:
///
/// - It always implements "Query" with SqlType = Untyped, so a caller could try to
/// execute this query and get back any type.
/// - It forces the usage of "QueryableByName", which acts wrong if we're
/// returning multiple columns with the same name (this is normal! If you want
/// to UNION two objects that both have "id" columns, this happens).
#[derive(QueryId)]
pub struct TypedSqlQuery<T> {
inner: diesel::query_builder::BoxedSqlQuery<
'static,
Pg,
diesel::query_builder::SqlQuery,
>,
_phantom: PhantomData<T>,
}

impl<T> QueryFragment<Pg> for TypedSqlQuery<T> {
fn walk_ast<'a>(
&'a self,
mut out: AstPass<'_, 'a, Pg>,
) -> diesel::QueryResult<()> {
out.unsafe_to_cache_prepared();

self.inner.walk_ast(out.reborrow())?;
Ok(())
}
}

impl<T> RunQueryDsl<DbConnection> for TypedSqlQuery<T> {}

impl<T> Query for TypedSqlQuery<T> {
type SqlType = T;
}

0 comments on commit c868f71

Please sign in to comment.