Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions butane/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,21 @@ fn basic_load_first_ordered(conn: Connection) {
assert_eq!(found_desc, Some(foo2));
}
testall!(basic_load_first_ordered);

fn save_upserts_by_default(conn: Connection) {
let mut foo = Foo::new(1);
foo.bar = 42;
foo.save(&conn).unwrap();

// Create another foo object with the same primary key,
// but a different bar value.
let mut foo = Foo::new(1);
foo.bar = 43;
// Save should do an upsert, so it will update the bar value
// rather than throwing a conflict
foo.save(&conn).unwrap();

let retrieved = Foo::get(&conn, 1).unwrap();
assert_eq!(retrieved.bar, 43);
}
testall!(save_upserts_by_default);
10 changes: 9 additions & 1 deletion butane_core/src/codegen/dbobj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,25 @@ pub fn impl_dbobject(ast_struct: &ItemStruct, config: &Config) -> TokenStream2 {
#pklit,
<#pktype as butane::FieldType>::SQLTYPE);
if self.state.saved {
// Already exists in db, do an update
#(#values_no_pk)*
if values.len() > 0 {
conn.update(Self::TABLE,
pkcol,
butane::ToSql::to_sql_ref(self.pk()),
&[#save_cols], &values)?;
}
} else {
} else if #auto_pk {
// Since we expect our pk field to be invalid and to be created by the insert,
// we do a pure insert, no upsert allowed.
#(#values)*
let pk = conn.insert_returning_pk(Self::TABLE, &[#insert_cols], &pkcol, &values)?;
#(#post_insert)*
} else {
// Do an upsert
#(#values)*
conn.insert_or_replace(Self::TABLE, &[#insert_cols], &pkcol, &values)?;
self.state.saved = true
}
#many_save
Ok(())
Expand Down
22 changes: 14 additions & 8 deletions butane_core/src/db/pg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,14 +715,20 @@ pub fn sql_insert_or_replace_with_placeholders(
n + 1
});
write!(w, ")").unwrap();
write!(w, " ON CONFLICT ({}) DO UPDATE SET (", pkcol.name()).unwrap();
helper::list_columns(columns, w);
write!(w, ") = (").unwrap();
columns.iter().fold("", |sep, c| {
write!(w, "{}excluded.{}", sep, c.name()).unwrap();
", "
});
write!(w, ")").unwrap();
write!(w, " ON CONFLICT ({}) DO ", pkcol.name()).unwrap();
if columns.len() > 1 {
write!(w, "UPDATE SET (").unwrap();
helper::list_columns(columns, w);
write!(w, ") = (").unwrap();
columns.iter().fold("", |sep, c| {
write!(w, "{}excluded.{}", sep, c.name()).unwrap();
", "
});
write!(w, ")").unwrap();
} else {
// If the pk is the only column and it already exists, then there's nothing to update.
write!(w, "NOTHING").unwrap();
}
}

fn pgtype_for_val(val: &SqlVal) -> postgres::types::Type {
Expand Down
28 changes: 24 additions & 4 deletions butane_core/src/db/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ impl ConnectionMethods for rusqlite::Connection {
&self,
table: &str,
columns: &[Column],
_pkcol: &Column,
pkcol: &Column,
values: &[SqlValRef],
) -> Result<()> {
let mut sql = String::new();
sql_insert_or_update(table, columns, &mut sql);
sql_insert_or_update(table, columns, pkcol, &mut sql);
self.execute(&sql, rusqlite::params_from_iter(values))?;
Ok(())
}
Expand Down Expand Up @@ -611,8 +611,8 @@ fn change_column(
result
}

pub fn sql_insert_or_update(table: &str, columns: &[Column], w: &mut impl Write) {
write!(w, "INSERT OR REPLACE ").unwrap();
pub fn sql_insert_or_update(table: &str, columns: &[Column], pkcol: &Column, w: &mut impl Write) {
write!(w, "INSERT ").unwrap();
write!(w, "INTO {} (", helper::quote_reserved_word(table)).unwrap();
helper::list_columns(columns, w);
write!(w, ") VALUES (").unwrap();
Expand All @@ -621,6 +621,26 @@ pub fn sql_insert_or_update(table: &str, columns: &[Column], w: &mut impl Write)
", "
});
write!(w, ")").unwrap();
write!(w, " ON CONFLICT ({}) DO ", pkcol.name()).unwrap();
if columns.len() > 1 {
write!(w, "UPDATE SET (").unwrap();
helper::list_columns(columns, w);
write!(w, ") = (").unwrap();
columns.iter().fold("", |sep, c| {
write!(
w,
"{}excluded.{}",
sep,
helper::quote_reserved_word(c.name())
)
.unwrap();
", "
});
write!(w, ")").unwrap();
} else {
// If the pk is the only column and it already exists, then there's nothing to update.
write!(w, "NOTHING").unwrap();
}
}

#[derive(Debug)]
Expand Down