Last active
February 1, 2021 07:05
-
-
Save pauldorehill/a2e55df30aaefb011c244e16b7164a87 to your computer and use it in GitHub Desktop.
Example trait for sqlx to solve issues around returning Vec<T>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [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