use super::super::naming::{pascal, snake, sql};
use super::{insert, policy, targets};
use crate::database::introspection::{Column, Table};
pub(super) fn render(table: &Table, key: &[String], merge: Option<&Column>) -> String {
let payload = match merge {
Some(column) => vec![column],
None => policy::payload(table, key),
};
let mut assignments = payload.iter().map(|column| {
let name = sql(&column.name);
if merge.is_some() {
format!("{name} = {}.{name} || EXCLUDED.{name}", sql("existing"))
} else {
format!("{name} = EXCLUDED.{name}")
}
}).collect::<Vec<_>>();
if let Some(clock) = policy::clock(table).filter(|clock| !key.contains(&clock.name)) {
assignments.push(format!("{} = CURRENT_TIMESTAMP", sql(&clock.name)));
}
if assignments.is_empty() {
return String::new();
}
let mode = merge.map(|column| format!("merge_{}", snake(&column.name)))
.unwrap_or_else(|| "snapshot".to_string());
let function = format!("upsert_{}_{}_by_{}", snake(&table.name), mode, targets::suffix(key));
let model = pascal(&table.name);
let target = key.iter().map(|name| sql(name)).collect::<Vec<_>>().join(", ");
let conflict = format!(" ON CONFLICT ({target}) DO UPDATE SET {} RETURNING *", assignments.join(", "));
let insert = insert::render(table);
format!(
"/// Atomic {mode}; e.g. `{function}(pool, input).await` returns the stored row.\n\
pub async fn {function}(pool: &PgPool, input: New{model}) -> Result<{model}, sqlx::Error> {{\n\
{insert}\
query.push({conflict:?});\n\
query.build_query_as::<{model}>().fetch_one(pool).await\n\
}}\n\n"
)
}