From a75a1f27c69ca74c6c4f12b66010c510b8a1effc Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 11 Mar 2026 15:24:00 -0700 Subject: [PATCH] graph: Check for update attempts of immutable entities in one batch The ultimate check that immutable entities are not updated is the unique constraint in the database. But we check when adding immutable entities to a batch, too, to provide a clearer and more timely error message. --- graph/src/components/store/write.rs | 21 +++++++++++++++++++++ graph/src/schema/entity_key.rs | 14 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/graph/src/components/store/write.rs b/graph/src/components/store/write.rs index e1c079103eb..8c9671cb347 100644 --- a/graph/src/components/store/write.rs +++ b/graph/src/components/store/write.rs @@ -474,6 +474,27 @@ impl RowGroup { if self.immutable { match row { EntityModification::Insert { .. } => { + // Check if this is an attempt to overwrite an immutable + // entity. We allow overwriting immutable entities in + // the same block, but not across blocks; if such an + // attempt happens across two batches, it would result + // in a database constraint violation. This check is + // simply here to provide a friendlier error message and + // to raise the error earlier, before we actually write + // to the database + match self + .last_mod + .get(row.id()) + .and_then(|&idx| self.rows.get(idx)) + { + Some(prev) if prev.block() != row.block() => { + return Err(StoreError::Input( + format!("entity {} is immutable; inserting it at block {} is not possible as it was already inserted at block {}", + row.key(), row.block(), prev.block()))); + } + _ => { /* nothing to check */ } + } + self.push_row(row); } EntityModification::Overwrite { .. } | EntityModification::Remove { .. } => { diff --git a/graph/src/schema/entity_key.rs b/graph/src/schema/entity_key.rs index 520d3d6320a..3a5a8581a3c 100644 --- a/graph/src/schema/entity_key.rs +++ b/graph/src/schema/entity_key.rs @@ -59,3 +59,17 @@ impl std::fmt::Debug for EntityKey { ) } } + +impl std::fmt::Display for EntityKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.causality_region == CausalityRegion::ONCHAIN { + write!(f, "{}[{}]", self.entity_type, self.entity_id) + } else { + write!( + f, + "{}/{}[{}]", + self.entity_type, self.causality_region, self.entity_id + ) + } + } +}