diff --git a/crates/storage-pg/.sqlx/query-b26ae7dd28f8a756b55a76e80cdedd7be9ba26435ea4a914421483f8ed832537.json b/crates/storage-pg/.sqlx/query-7f4c4634ada4dc2745530dcca8eee92abf78dfbdf1a25e58a2bc9c14be8035f0.json similarity index 70% rename from crates/storage-pg/.sqlx/query-b26ae7dd28f8a756b55a76e80cdedd7be9ba26435ea4a914421483f8ed832537.json rename to crates/storage-pg/.sqlx/query-7f4c4634ada4dc2745530dcca8eee92abf78dfbdf1a25e58a2bc9c14be8035f0.json index accc70f5..e9af498f 100644 --- a/crates/storage-pg/.sqlx/query-b26ae7dd28f8a756b55a76e80cdedd7be9ba26435ea4a914421483f8ed832537.json +++ b/crates/storage-pg/.sqlx/query-7f4c4634ada4dc2745530dcca8eee92abf78dfbdf1a25e58a2bc9c14be8035f0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (user_id, username, created_at)\n VALUES ($1, $2, $3)\n ", + "query": "\n INSERT INTO users (user_id, username, created_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (username) DO NOTHING\n ", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "b26ae7dd28f8a756b55a76e80cdedd7be9ba26435ea4a914421483f8ed832537" + "hash": "7f4c4634ada4dc2745530dcca8eee92abf78dfbdf1a25e58a2bc9c14be8035f0" } diff --git a/crates/storage-pg/src/user/mod.rs b/crates/storage-pg/src/user/mod.rs index f5cf37d8..7dbf666d 100644 --- a/crates/storage-pg/src/user/mod.rs +++ b/crates/storage-pg/src/user/mod.rs @@ -161,10 +161,11 @@ impl<'c> UserRepository for PgUserRepository<'c> { let id = Ulid::from_datetime_with_source(created_at.into(), rng); tracing::Span::current().record("user.id", tracing::field::display(id)); - sqlx::query!( + let res = sqlx::query!( r#" INSERT INTO users (user_id, username, created_at) VALUES ($1, $2, $3) + ON CONFLICT (username) DO NOTHING "#, Uuid::from(id), username, @@ -174,6 +175,10 @@ impl<'c> UserRepository for PgUserRepository<'c> { .execute(&mut *self.conn) .await?; + // If the user already exists, want to return an error but not poison the + // transaction + DatabaseError::ensure_affected_rows(&res, 1)?; + Ok(User { id, username, diff --git a/crates/storage-pg/src/user/tests.rs b/crates/storage-pg/src/user/tests.rs index 7036ecbd..cf46bfa6 100644 --- a/crates/storage-pg/src/user/tests.rs +++ b/crates/storage-pg/src/user/tests.rs @@ -63,12 +63,38 @@ async fn test_user_repo(pool: PgPool) { assert!(repo.user().lookup(user.id).await.unwrap().is_some()); // Adding a second time should give a conflict + // It should not poison the transaction though assert!(repo .user() .add(&mut rng, &clock, USERNAME.to_owned()) .await .is_err()); + // Try locking a user + assert!(user.is_valid()); + let user = repo.user().lock(&clock, user).await.unwrap(); + assert!(!user.is_valid()); + + // Check that the property is retrieved on lookup + let user = repo.user().lookup(user.id).await.unwrap().unwrap(); + assert!(!user.is_valid()); + + // Locking a second time should not fail + let user = repo.user().lock(&clock, user).await.unwrap(); + assert!(!user.is_valid()); + + // Try unlocking a user + let user = repo.user().unlock(user).await.unwrap(); + assert!(user.is_valid()); + + // Check that the property is retrieved on lookup + let user = repo.user().lookup(user.id).await.unwrap().unwrap(); + assert!(user.is_valid()); + + // Unlocking a second time should not fail + let user = repo.user().unlock(user).await.unwrap(); + assert!(user.is_valid()); + repo.save().await.unwrap(); }