use super::{deserialize::deserialize, schema::Table};
use crate::bson::Array;
use crate::storage::Error;
use crate::storage::associations::HasMany;
use change_case::snake_case;
use futures_util::TryStreamExt;
use pbbson::Model;
use sqlx::{Pool, Postgres};
use std::collections::HashMap;
use std::fmt::Debug;
use std::str::FromStr;
pub(super) async fn perform<B: Clone + Debug + FromStr + Send + Sync + ToString>(
pool: &Pool<Postgres>,
table: &Table,
id: &str,
has_manys_by_bucket: &HashMap<String, Vec<HasMany<B>>>,
) -> Result<Model, Error> {
let sql = {
let table_name = &table.name;
let maybe_id_field = table.fields_by_name.get("id");
let param = match maybe_id_field {
None => "$1".to_string(),
Some(field) => format!("$1::{}", field.data_type),
};
format!("SELECT * FROM {table_name} WHERE id = {param}")
};
log::debug!(sql; "Finding one");
let mut rows = sqlx::query(&sql).bind(id).fetch(pool);
let row = match rows.try_next().await? {
None => return Err(Error::not_found("No such record")),
Some(row) => row,
};
let mut model = deserialize(row, table)?;
let has_manys = match has_manys_by_bucket.get(&table.name) {
None => vec![],
Some(has_manys) => has_manys.clone(),
};
for has_many in has_manys.iter() {
let inverse = match &has_many.inverse {
None => continue,
Some(inverse) => match super::create::find_field(table, inverse) {
None => {
continue;
}
Some(field) => field,
},
};
let remote_table_name = snake_case(&has_many.remote.to_string());
let pk_column_name = match table.primary_key.first() {
None => continue,
Some(pk_column_name) => pk_column_name,
};
let sql = {
let maybe_id_field = table.fields_by_name.get("id");
let param = match maybe_id_field {
None => "$1".to_string(),
Some(field) => format!("$1::{}", field.data_type),
};
let inverse_column_name = &inverse.column_name;
format!("SELECT {pk_column_name} FROM {remote_table_name} WHERE {inverse_column_name} = {param}")
};
log::debug!(sql; "Finding one");
let mut rows = sqlx::query(&sql).bind(id).fetch(pool);
let mut array = Array::new();
while let Some(row) = rows.try_next().await? {
let model = deserialize(row, table)?;
if let Some(pk_value) = model.get(pk_column_name) {
array.push(pk_value.clone());
}
}
model.insert(has_many.local.clone(), array);
}
Ok(model)
}