Skip to content

Commit 9aa807b

Browse files
committed
store: ON CONFLICT DO NOTHING for skip_duplicates inserts
Add conditional ON CONFLICT (primary_key) DO NOTHING clause to InsertQuery::walk_ast() when table.immutable && table.skip_duplicates. This handles cross-batch duplicates where an entity committed in a previous batch is re-inserted due to the immutable entity cache skipping store lookups. Two unit tests verify: skip_duplicates inserts include ON CONFLICT, default immutable inserts do not.
1 parent e85145d commit 9aa807b

2 files changed

Lines changed: 72 additions & 2 deletions

File tree

store/postgres/src/relational/query_tests.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ use std::{collections::BTreeSet, sync::Arc};
22

33
use diesel::{debug_query, pg::Pg};
44
use graph::{
5+
components::store::write::RowGroup,
56
data_source::CausalityRegion,
6-
prelude::{r, serde_json as json, DeploymentHash, EntityFilter},
7+
entity,
8+
prelude::{r, serde_json as json, DeploymentHash, EntityFilter, Logger},
79
schema::InputSchema,
10+
slog,
811
};
912

1013
use crate::{
1114
block_range::BoundSide,
1215
layout_for_tests::{make_dummy_site, Namespace},
1316
relational::{Catalog, ColumnType, Layout},
14-
relational_queries::{FindRangeQuery, FromColumnValue},
17+
relational_queries::{FindRangeQuery, FromColumnValue, InsertQuery},
1518
};
1619

1720
use crate::relational_queries::Filter;
@@ -197,3 +200,64 @@ fn test_id_type_casting(table: &crate::relational::Table, expected_cast: &str, t
197200
sql
198201
);
199202
}
203+
204+
fn insert_sql_for_schema(gql: &str, entity_type_name: &str) -> String {
205+
use graph::components::store::write::EntityModification;
206+
207+
let layout = test_layout(gql);
208+
let schema = &layout.input_schema;
209+
let et = schema.entity_type(entity_type_name).unwrap();
210+
let table = layout.table_for_entity(&et).unwrap();
211+
212+
let mut entity = entity! { schema => id: "test1" };
213+
entity.set_vid(1).unwrap();
214+
let key = et.key(graph::data::store::Id::String("test1".into()));
215+
let emod = EntityModification::Insert {
216+
key,
217+
data: Arc::new(entity),
218+
block: 1,
219+
end: None,
220+
};
221+
222+
let logger = Logger::root(slog::Discard, slog::o!());
223+
let mut group = RowGroup::new(et, table.immutable, table.skip_duplicates, logger);
224+
group.push(emod, 1).unwrap();
225+
226+
let chunks: Vec<_> = group.write_chunks(100).collect();
227+
let chunk = &chunks[0];
228+
let query = InsertQuery::new(table.as_ref(), chunk).unwrap();
229+
debug_query::<Pg, _>(&query).to_string()
230+
}
231+
232+
#[test]
233+
fn skip_duplicates_insert_generates_on_conflict() {
234+
let schema = "
235+
type Thing @entity(immutable: true, skipDuplicates: true) {
236+
id: String!
237+
}";
238+
let sql = insert_sql_for_schema(schema, "Thing");
239+
assert!(
240+
sql.contains("ON CONFLICT"),
241+
"Expected ON CONFLICT in SQL for skip_duplicates immutable table, got: {}",
242+
sql
243+
);
244+
assert!(
245+
sql.contains("DO NOTHING"),
246+
"Expected DO NOTHING in SQL for skip_duplicates immutable table, got: {}",
247+
sql
248+
);
249+
}
250+
251+
#[test]
252+
fn default_immutable_insert_has_no_on_conflict_skip_duplicates() {
253+
let schema = "
254+
type Thing @entity(immutable: true) {
255+
id: String!
256+
}";
257+
let sql = insert_sql_for_schema(schema, "Thing");
258+
assert!(
259+
!sql.contains("ON CONFLICT"),
260+
"Default immutable table should NOT have ON CONFLICT, got: {}",
261+
sql
262+
);
263+
}

store/postgres/src/relational_queries.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,12 @@ impl<'a> QueryFragment<Pg> for InsertQuery<'a> {
25842584
out.push_sql(")");
25852585
}
25862586

2587+
if self.table.immutable && self.table.skip_duplicates {
2588+
out.push_sql("\n ON CONFLICT (");
2589+
out.push_identifier(self.table.primary_key().name.as_str())?;
2590+
out.push_sql(") DO NOTHING");
2591+
}
2592+
25872593
Ok(())
25882594
}
25892595
}

0 commit comments

Comments
 (0)