Skip to content

Commit eba4844

Browse files
committed
store: Do not emit an index in block$ twice
If an immutable entity type has a `block` column, we would emit the index on the block$ twice, making deployment fail
1 parent 09579fb commit eba4844

File tree

2 files changed

+103
-7
lines changed

2 files changed

+103
-7
lines changed

store/postgres/src/relational/ddl_tests.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,74 @@ fn can_copy_from() {
352352
);
353353
}
354354

355+
/// Check that we do not create the index on `block$` twice. There was a bug
356+
/// that if an immutable entity type had a `block` field and index creation
357+
/// was postponed, we would emit the index on `block$` twice, once from
358+
/// `Table.create_time_travel_indexes` and once through
359+
/// `IndexList.indexes_for_table`
360+
#[test]
361+
fn postponed_indexes_with_block_column() {
362+
fn index_list() -> IndexList {
363+
// To generate this list, print the output of `layout.as_ddl(None)`, run
364+
// that in Postgres and do `select indexdef from pg_indexes where
365+
// schemaname = 'sgd0815'`
366+
const INDEX_DEFS: &[&str] = &[
367+
"CREATE UNIQUE INDEX data_pkey ON sgd0815.data USING btree (vid)",
368+
"CREATE UNIQUE INDEX data_id_key ON sgd0815.data USING btree (id)",
369+
"CREATE INDEX data_block ON sgd0815.data USING btree (block$)",
370+
"CREATE INDEX attr_1_0_data_block ON sgd0815.data USING btree (block, \"block$\")",
371+
];
372+
373+
let mut indexes: HashMap<String, Vec<CreateIndex>> = HashMap::new();
374+
indexes.insert(
375+
"data".to_string(),
376+
INDEX_DEFS
377+
.iter()
378+
.map(|def| CreateIndex::parse(def.to_string()))
379+
.collect(),
380+
);
381+
IndexList { indexes }
382+
}
383+
// Names of the two indexes we are interested in. Not the leading space
384+
// to guard a little against overlapping names
385+
const BLOCK_IDX: &str = " data_block";
386+
const ATTR_IDX: &str = " attr_1_0_data_block";
387+
388+
let layout = test_layout(BLOCK_GQL);
389+
390+
// Create everything
391+
let sql = layout.as_ddl(None).unwrap();
392+
assert!(sql.contains(BLOCK_IDX));
393+
assert!(sql.contains(ATTR_IDX));
394+
395+
// Defer attribute indexes
396+
let sql = layout.as_ddl(Some(index_list())).unwrap();
397+
assert!(sql.contains(BLOCK_IDX));
398+
assert!(!sql.contains(ATTR_IDX));
399+
// This used to be duplicated
400+
let count = sql.matches(BLOCK_IDX).count();
401+
assert_eq!(1, count);
402+
403+
let table = layout.table(&SqlName::from("Data")).unwrap();
404+
let sql = table.create_postponed_indexes(vec![], false);
405+
assert_eq!(1, sql.len());
406+
assert!(!sql[0].contains(BLOCK_IDX));
407+
assert!(sql[0].contains(ATTR_IDX));
408+
409+
let dst_nsp = Namespace::new("sgd2".to_string()).unwrap();
410+
let arr = index_list()
411+
.indexes_for_table(&dst_nsp, &table.name.to_string(), &table, true, false)
412+
.unwrap();
413+
assert_eq!(1, arr.len());
414+
assert!(!arr[0].1.contains(BLOCK_IDX));
415+
assert!(arr[0].1.contains(ATTR_IDX));
416+
417+
let arr = index_list()
418+
.indexes_for_table(&dst_nsp, &table.name.to_string(), &table, false, false)
419+
.unwrap();
420+
assert_eq!(0, arr.len());
421+
}
422+
355423
const THING_GQL: &str = r#"
356424
type Thing @entity {
357425
id: ID!
@@ -1109,3 +1177,15 @@ on "sgd0815"."stats_3_day" using btree("volume");
11091177
create index stats_3_day_dims
11101178
on "sgd0815"."stats_3_day"(group_2, group_1, timestamp);
11111179
"#;
1180+
1181+
const BLOCK_GQL: &str = r#"
1182+
type Block @entity(immutable: true) {
1183+
id: ID!
1184+
number: Int!
1185+
}
1186+
1187+
type Data @entity(immutable: true) {
1188+
id: ID!
1189+
block: Block!
1190+
}
1191+
"#;

store/postgres/src/relational/index.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl Display for Expr {
123123
Expr::Column(s) => write!(f, "{s}")?,
124124
Expr::Prefix(s, _) => write!(f, "{s}")?,
125125
Expr::Vid => write!(f, "vid")?,
126-
Expr::Block => write!(f, "block")?,
126+
Expr::Block => write!(f, "{BLOCK_COLUMN}")?,
127127
Expr::BlockRange => write!(f, "block_range")?,
128128
Expr::BlockRangeLower => write!(f, "lower(block_range)")?,
129129
Expr::BlockRangeUpper => write!(f, "upper(block_range)")?,
@@ -488,12 +488,29 @@ impl CreateIndex {
488488
&& columns[1] == Expr::BlockRange
489489
}
490490
Method::Brin => false,
491-
Method::BTree | Method::Gin => {
491+
Method::Gin => {
492+
// 'using gin(<attr>)'
492493
columns.len() == 1
493494
&& columns[0].is_attribute()
494495
&& cond.is_none()
495496
&& with.is_none()
496497
}
498+
Method::BTree => {
499+
match columns.len() {
500+
1 => {
501+
// 'using btree(<attr>)'
502+
columns[0].is_attribute() && cond.is_none() && with.is_none()
503+
}
504+
2 => {
505+
// 'using btree(<attr>, block$)'
506+
columns[0].is_attribute()
507+
&& columns[1] == Expr::Block
508+
&& cond.is_none()
509+
&& with.is_none()
510+
}
511+
_ => false,
512+
}
513+
}
497514
Method::Unknown(_) => false,
498515
}
499516
}
@@ -537,6 +554,7 @@ impl CreateIndex {
537554
None,
538555
),
539556
dummy(false, BTree, &[Expr::BlockRangeUpper], Some(Cond::Closed)),
557+
dummy(false, BTree, &[Expr::Block], None),
540558
]
541559
};
542560
}
@@ -630,7 +648,7 @@ impl CreateIndex {
630648
}
631649

632650
pub fn fields_exist_in_dest<'a>(&self, dest_table: &'a Table) -> bool {
633-
fn column_exists<'a>(it: &mut impl Iterator<Item = &'a str>, column_name: &String) -> bool {
651+
fn column_exists<'a>(it: &mut impl Iterator<Item = &'a str>, column_name: &str) -> bool {
634652
it.any(|c| *c == *column_name)
635653
}
636654

@@ -667,9 +685,7 @@ impl CreateIndex {
667685
}
668686
Expr::Vid => (),
669687
Expr::Block => {
670-
if !column_exists(cols, &"block".to_string()) {
671-
return false;
672-
}
688+
return dest_table.immutable;
673689
}
674690
Expr::Unknown(expression) => {
675691
if some_column_contained(
@@ -776,7 +792,7 @@ impl IndexList {
776792
// First we check if the fields do exist in the destination subgraph.
777793
// In case of grafting that is not given.
778794
if ci.fields_exist_in_dest(dest_table)
779-
// Then we check if the index is one of the default indexes not based on
795+
// Then we check if the index is one of the default indexes not based on
780796
// the attributes. Those will be created anyway and we should skip them.
781797
&& !ci.is_default_non_attr_index()
782798
// Then ID based indexes in the immutable tables are also created initially

0 commit comments

Comments
 (0)