Skip to content

Instantly share code, notes, and snippets.

@pauldorehill
Last active February 1, 2021 07:05
Show Gist options
  • Save pauldorehill/a2e55df30aaefb011c244e16b7164a87 to your computer and use it in GitHub Desktop.
Save pauldorehill/a2e55df30aaefb011c244e16b7164a87 to your computer and use it in GitHub Desktop.
Example trait for sqlx to solve issues around returning Vec<T>
// [dependencies]
// sqlx = { version = "0.4", features = ["runtime-async-std-rustls", "postgres"] }
// async-std = { version = "1", features = ["attributes"] }
// dotenv = "0.15"
// anyhow = "1"
// In root of project '.env' file with export DATABASE_URL="postgres://postgres:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
// Rough DB Schema
// CREATE TABLE public.giant (
// id integer NOT NULL,
// name text NOT NULL
// );
// CREATE TABLE public.meal (
// id integer NOT NULL,
// name text NOT NULL
// );
// CREATE TABLE public.giant_meal (
// giant_id integer NOT NULL,
// meal_id integer NOT NULL
// );
use sqlx::PgPool;
use std::{collections::HashMap, hash::Hash};
trait FromDBFlatten
where
Self: From<<Self as FromDBFlatten>::DatabaseType>,
{
type DatabaseType;
type GroupBy: Hash + Eq;
type VecType: From<Self::DatabaseType>;
fn group_by_field(db_item: &Self::DatabaseType) -> Self::GroupBy;
fn vec_field(&mut self) -> &mut Vec<Self::VecType>;
fn group_by(items: Vec<Self::DatabaseType>) -> HashMap<Self::GroupBy, Self> {
let mut m: HashMap<Self::GroupBy, Self> = HashMap::new();
for db_item in items {
match m.entry(Self::group_by_field(&db_item)) {
std::collections::hash_map::Entry::Occupied(mut o) => {
Self::vec_field(o.get_mut()).push(db_item.into())
}
std::collections::hash_map::Entry::Vacant(v) => {
v.insert(db_item.into());
}
}
}
m
}
fn flatten(items: Vec<Self::DatabaseType>) -> Vec<Self> {
Self::group_by(items).into_iter().map(|(_, v)| v).collect()
}
fn flatten_one(items: Vec<Self::DatabaseType>) -> Option<Self> {
Self::group_by(items).into_iter().next().map(|(_, v)| v)
}
}
#[derive(sqlx::Type, Debug)]
struct Meal {
id: i32,
name: String,
}
impl From<GiantDB> for Meal {
fn from(giant: GiantDB) -> Self {
Self {
id: giant.meal.id,
name: giant.meal.name,
}
}
}
#[derive(Debug)]
struct Giant {
id: i32,
name: String,
meals: Vec<Meal>,
}
impl From<GiantDB> for Giant {
fn from(v: GiantDB) -> Self {
Self {
id: v.id,
name: v.name,
meals: vec![v.meal],
}
}
}
struct GiantDB {
id: i32,
name: String,
meal: Meal,
}
impl FromDBFlatten for Giant {
type DatabaseType = GiantDB;
type GroupBy = i32;
type VecType = Meal;
fn group_by_field(db_item: &Self::DatabaseType) -> Self::GroupBy {
db_item.id
}
fn vec_field(&mut self) -> &mut Vec<Self::VecType> {
&mut self.meals
}
}
#[async_std::main]
async fn main() -> anyhow::Result<()> {
let pool = PgPool::connect(&dotenv::var("DATABASE_URL").unwrap()).await?;
let giant: Giant = sqlx::query_as!(
GiantDB,
r#"
SELECT giant.id AS id, giant.name AS name, (meal.id, meal.name) AS "meal!: Meal"
FROM giant
LEFT JOIN giant_meal on giant.id = giant_meal.giant_id
LEFT JOIN meal on giant_meal.meal_id = meal.id"#
)
.fetch_one(&pool)
.await
.map(Into::into)?;
println!("{:#?}", giant);
let giants: Vec<Giant> = sqlx::query_as!(
GiantDB,
r#"
SELECT giant.id AS id, giant.name AS name, (meal.id, meal.name) AS "meal!: Meal"
FROM giant
LEFT JOIN giant_meal on giant.id = giant_meal.giant_id
LEFT JOIN meal on giant_meal.meal_id = meal.id"#
)
.fetch_all(&pool)
.await
.map(FromDBFlatten::flatten)?;
println!("{:#?}", giants);
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment