feat: add unique constraint restriction for KV (#3723)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kartikeya Hegde
2024-02-26 13:37:05 +00:00
committed by GitHub
parent 9aabb14a60
commit c117f8ec63
6 changed files with 126 additions and 10 deletions

View File

@ -165,10 +165,12 @@ impl RedisErrorExt for error_stack::Report<RedisError> {
RedisError::NotFound => self.change_context(DataStorageError::ValueNotFound(format!(
"Data does not exist for key {key}",
))),
RedisError::SetNxFailed => self.change_context(DataStorageError::DuplicateValue {
entity: "redis",
key: Some(key.to_string()),
}),
RedisError::SetNxFailed | RedisError::SetAddMembersFailed => {
self.change_context(DataStorageError::DuplicateValue {
entity: "redis",
key: Some(key.to_string()),
})
}
_ => self.change_context(DataStorageError::KVError),
}
}

View File

@ -19,9 +19,10 @@ pub mod refund;
mod reverse_lookup;
mod utils;
use common_utils::errors::CustomResult;
use database::store::PgPool;
pub use mock_db::MockDb;
use redis_interface::errors::RedisError;
use redis_interface::{errors::RedisError, SaddReply};
pub use crate::database::store::DatabaseStore;
@ -259,3 +260,74 @@ pub(crate) fn diesel_error_to_data_error(
_ => StorageError::DatabaseError(error_stack::report!(*diesel_error)),
}
}
#[async_trait::async_trait]
pub trait UniqueConstraints {
fn unique_constraints(&self) -> Vec<String>;
fn table_name(&self) -> &str;
async fn check_for_constraints(
&self,
redis_conn: &Arc<redis_interface::RedisConnectionPool>,
) -> CustomResult<(), RedisError> {
let constraints = self.unique_constraints();
let sadd_result = redis_conn
.sadd(
&format!("unique_constraint:{}", self.table_name()),
constraints,
)
.await?;
match sadd_result {
SaddReply::KeyNotSet => Err(error_stack::report!(RedisError::SetAddMembersFailed)),
SaddReply::KeySet => Ok(()),
}
}
}
impl UniqueConstraints for diesel_models::Address {
fn unique_constraints(&self) -> Vec<String> {
vec![format!("address_{}", self.address_id)]
}
fn table_name(&self) -> &str {
"Address"
}
}
impl UniqueConstraints for diesel_models::PaymentIntent {
fn unique_constraints(&self) -> Vec<String> {
vec![format!("pi_{}_{}", self.merchant_id, self.payment_id)]
}
fn table_name(&self) -> &str {
"PaymentIntent"
}
}
impl UniqueConstraints for diesel_models::PaymentAttempt {
fn unique_constraints(&self) -> Vec<String> {
vec![format!(
"pa_{}_{}_{}",
self.merchant_id, self.payment_id, self.attempt_id
)]
}
fn table_name(&self) -> &str {
"PaymentAttempt"
}
}
impl UniqueConstraints for diesel_models::Refund {
fn unique_constraints(&self) -> Vec<String> {
vec![format!("refund_{}_{}", self.merchant_id, self.refund_id)]
}
fn table_name(&self) -> &str {
"Refund"
}
}
impl UniqueConstraints for diesel_models::ReverseLookup {
fn unique_constraints(&self) -> Vec<String> {
vec![format!("reverselookup_{}", self.lookup_id)]
}
fn table_name(&self) -> &str {
"ReverseLookup"
}
}

View File

@ -7,7 +7,7 @@ use router_derive::TryGetEnumVariant;
use router_env::logger;
use serde::de;
use crate::{metrics, store::kv::TypedSql, KVRouterStore};
use crate::{metrics, store::kv::TypedSql, KVRouterStore, UniqueConstraints};
pub trait KvStorePartition {
fn partition_number(key: PartitionKey<'_>, num_partitions: u8) -> u32 {
@ -95,7 +95,7 @@ pub async fn kv_wrapper<'a, T, D, S>(
where
T: de::DeserializeOwned,
D: crate::database::store::DatabaseStore,
S: serde::Serialize + Debug + KvStorePartition,
S: serde::Serialize + Debug + KvStorePartition + UniqueConstraints + Sync,
{
let redis_conn = store.get_redis_conn()?;
@ -147,6 +147,8 @@ where
KvOperation::HSetNx(field, value, sql) => {
logger::debug!(kv_operation= %operation, value = ?value);
value.check_for_constraints(&redis_conn).await?;
let result = redis_conn
.serialize_and_set_hash_field_if_not_exist(key, field, value, Some(ttl))
.await?;
@ -168,6 +170,8 @@ where
.serialize_and_set_key_if_not_exist(key, value, Some(ttl.into()))
.await?;
value.check_for_constraints(&redis_conn).await?;
if matches!(result, redis_interface::SetnxReply::KeySet) {
store
.push_to_drainer_stream::<S>(sql, partition_key)