use std::fmt::Write;
use cratestack_sql::{ConflictTarget, Dialect, ModelDescriptor, SqlColumnValue, SqlValue};
pub fn render_upsert<M, PK>(
dialect: &dyn Dialect,
descriptor: &ModelDescriptor<M, PK>,
values: &[SqlColumnValue],
) -> (String, Vec<SqlValue>) {
render_upsert_with_conflict(dialect, descriptor, values, ConflictTarget::PrimaryKey)
}
pub fn render_upsert_with_conflict<M, PK>(
dialect: &dyn Dialect,
descriptor: &ModelDescriptor<M, PK>,
values: &[SqlColumnValue],
conflict_target: ConflictTarget,
) -> (String, Vec<SqlValue>) {
let mut sql = format!("INSERT INTO {} (", descriptor.table_name);
for (idx, value) in values.iter().enumerate() {
if idx > 0 {
sql.push_str(", ");
}
sql.push_str(value.column);
}
sql.push_str(") VALUES (");
let mut binds = Vec::with_capacity(values.len());
for (idx, value) in values.iter().enumerate() {
if idx > 0 {
sql.push_str(", ");
}
dialect.write_placeholder(&mut sql, idx + 1);
binds.push(value.value.clone());
}
sql.push_str(") ON CONFLICT (");
match conflict_target {
ConflictTarget::PrimaryKey => {
sql.push_str(descriptor.primary_key);
}
ConflictTarget::Columns(cols) => {
for (idx, column) in cols.iter().enumerate() {
if idx > 0 {
sql.push_str(", ");
}
sql.push_str(column);
}
}
}
sql.push_str(") DO UPDATE SET ");
if descriptor.upsert_update_columns.is_empty() {
let _ = write!(
&mut sql,
"{pk} = excluded.{pk}",
pk = descriptor.primary_key,
);
} else {
for (idx, column) in descriptor.upsert_update_columns.iter().enumerate() {
if idx > 0 {
sql.push_str(", ");
}
let _ = write!(&mut sql, "{column} = excluded.{column}");
}
}
if let Some(version_col) = descriptor.version_column {
let _ = write!(
&mut sql,
", {version_col} = {table}.{version_col} + 1",
table = descriptor.table_name,
);
}
sql.push_str(" RETURNING ");
sql.push_str(&descriptor.select_projection());
(sql, binds)
}