| Crates.io | pg-upsert |
| lib.rs | pg-upsert |
| version | 0.1.1 |
| created_at | 2025-12-25 20:33:58.42856+00 |
| updated_at | 2025-12-25 20:46:55.972023+00 |
| description | PostgreSQL UPSERT operations using sqlx |
| homepage | https://github.com/avbel/pg-upsert |
| repository | https://github.com/avbel/pg-upsert |
| max_upload_size | |
| id | 2004924 |
| size | 115,185 |
A Rust library for PostgreSQL UPSERT operations using sqlx.
ON CONFLICT DO UPDATE and ON CONFLICT DO NOTHINGAdd to your Cargo.toml:
[dependencies]
pg-upsert = "0.1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
chrono = "0.4" # Required for date/time field types
use pg_upsert::{upsert, fields, UpsertOptions};
use sqlx::PgPool;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = PgPool::connect("postgres://localhost/mydb").await?;
let rows_affected = upsert(
&pool,
"users",
&["id"], // conflict fields
vec![
fields!["id" => 1, "name" => "Alice", "email" => "alice@example.com"],
fields!["id" => 2, "name" => "Bob", "email" => "bob@example.com"],
],
UpsertOptions::default(),
).await?;
println!("Affected rows: {}", rows_affected);
Ok(())
}
Only updates if incoming version > existing version:
use pg_upsert::FieldValue;
let rows_affected = upsert(
&pool,
"products",
&["sku"],
vec![
fields![
"sku" => "ABC123",
"price" => FieldValue::Numeric("29.99".to_string()),
"version" => 5
],
],
UpsertOptions {
version_field: Some("version".into()),
..Default::default()
},
).await?;
Insert only, skip if row exists:
let rows_affected = upsert(
&pool,
"events",
&["event_id"],
vec![
fields!["event_id" => "evt_001", "data" => "payload"],
],
UpsertOptions {
do_nothing_on_conflict: true,
..Default::default()
},
).await?;
let rows_affected = upsert(
&pool,
"inventory",
&["warehouse_id", "product_id"], // composite key
vec![
fields!["warehouse_id" => 1, "product_id" => 100, "quantity" => 50],
],
UpsertOptions::default(),
).await?;
For financial data or any scenario requiring exact decimal precision:
use pg_upsert::FieldValue;
let rows_affected = upsert(
&pool,
"transactions",
&["transaction_id"],
vec![
fields![
"transaction_id" => "TXN-001",
"amount" => FieldValue::Numeric("1234567.89".to_string()),
"fee" => FieldValue::Numeric("0.0025".to_string()),
"balance" => FieldValue::Numeric("99999999.999999".to_string())
],
],
UpsertOptions::default(),
).await?;
The fields! macro supports:
i32, i64f32, f64String, &strFieldValue::Numeric(String) for high-precision decimal values (PostgreSQL NUMERIC/DECIMAL types)boolVec<u8>chrono::NaiveDate, chrono::NaiveTime, chrono::NaiveDateTime, chrono::DateTime<Utc>Option<T> (becomes NULL when None)use chrono::{NaiveDate, Utc};
use pg_upsert::FieldValue;
fields![
"id" => 1_i64,
"name" => "test",
"price" => 19.99_f64,
"balance" => FieldValue::Numeric("99999.999999999999".to_string()), // High-precision numeric
"active" => true,
"created_at" => Utc::now(),
"birth_date" => NaiveDate::from_ymd_opt(1990, 1, 15).unwrap(),
"description" => None::<String>, // NULL value
]
let options = UpsertOptions::new()
.with_version_field("version")
.with_do_nothing_on_conflict(false);
MIT