Skip to content

Commit af05829

Browse files
committed
graph: Detect unique constraint violations and mark as deterministic
1 parent 7665833 commit af05829

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

Diff for: graph/src/components/store/err.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub enum StoreError {
7070
WriteFailure(String, BlockNumber, String, String),
7171
#[error("database query timed out")]
7272
StatementTimeout,
73+
#[error("database constraint violated: {0}")]
74+
ConstraintViolation(String),
7375
}
7476

7577
// Convenience to report an internal error
@@ -127,6 +129,7 @@ impl Clone for StoreError {
127129
Self::WriteFailure(arg0.clone(), arg1.clone(), arg2.clone(), arg3.clone())
128130
}
129131
Self::StatementTimeout => Self::StatementTimeout,
132+
Self::ConstraintViolation(arg0) => Self::ConstraintViolation(arg0.clone()),
130133
}
131134
}
132135
}
@@ -135,6 +138,7 @@ impl StoreError {
135138
pub fn from_diesel_error(e: &DieselError) -> Option<Self> {
136139
const CONN_CLOSE: &str = "server closed the connection unexpectedly";
137140
const STMT_TIMEOUT: &str = "canceling statement due to statement timeout";
141+
const UNIQUE_CONSTR: &str = "duplicate key value violates unique constraint";
138142
let DieselError::DatabaseError(_, info) = e else {
139143
return None;
140144
};
@@ -146,6 +150,12 @@ impl StoreError {
146150
Some(StoreError::DatabaseUnavailable)
147151
} else if info.message().contains(STMT_TIMEOUT) {
148152
Some(StoreError::StatementTimeout)
153+
} else if info.message().contains(UNIQUE_CONSTR) {
154+
let msg = match info.details() {
155+
Some(details) => format!("{}: {}", info.message(), details.replace('\n', " ")),
156+
None => info.message().to_string(),
157+
};
158+
Some(StoreError::ConstraintViolation(msg))
149159
} else {
150160
None
151161
}
@@ -174,7 +184,8 @@ impl StoreError {
174184
| UnknownTable(_)
175185
| UnknownAttribute(_, _)
176186
| InvalidIdentifier(_)
177-
| UnsupportedFilter(_, _) => true,
187+
| UnsupportedFilter(_, _)
188+
| ConstraintViolation(_) => true,
178189

179190
// non-deterministic errors
180191
Unknown(_)

Diff for: store/test-store/tests/postgres/writable.rs

+18
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ fn read_range_pool_created_test() {
449449
let pool_created_type = TEST_SUBGRAPH_SCHEMA.entity_type("PoolCreated").unwrap();
450450
let entity_types = vec![pool_created_type.clone()];
451451

452+
let mut last_op: Option<EntityOperation> = None;
452453
for count in (1..=2).map(|x| x as i64) {
453454
let id = if count == 1 {
454455
"0xff80818283848586"
@@ -478,6 +479,7 @@ fn read_range_pool_created_test() {
478479
data,
479480
};
480481

482+
last_op = Some(op.clone());
481483
transact_entity_operations(
482484
&subgraph_store,
483485
&deployment,
@@ -500,5 +502,21 @@ fn read_range_pool_created_test() {
500502
let a = result_entities[index as usize].clone();
501503
assert_eq!(a, format!("{:?}", en));
502504
}
505+
506+
// Make sure we get a constraint violation
507+
let op = last_op.take().unwrap();
508+
509+
transact_entity_operations(&subgraph_store, &deployment, block_pointer(3), vec![op])
510+
.await
511+
.unwrap();
512+
let res = writable.flush().await;
513+
let exp = "duplicate key value violates unique constraint \"pool_created_pkey\": Key (vid)=(2) already exists.";
514+
match res {
515+
Ok(_) => panic!("Expected error, but got success"),
516+
Err(StoreError::ConstraintViolation(msg)) => {
517+
assert_eq!(msg, exp);
518+
}
519+
Err(e) => panic!("Expected constraint violation, but got {:?}", e),
520+
}
503521
})
504522
}

0 commit comments

Comments
 (0)