Skip to content

Commit 89ec242

Browse files
test(compiler): From index to inner join
This patch removes the invalid translation from IndexJoin to JoinExpr, since there is no way to capture the implicit projection of IndexJoin. Instead we translate from IndexJoin directly to QueryExpr.
1 parent d30bec1 commit 89ec242

File tree

2 files changed

+140
-24
lines changed

2 files changed

+140
-24
lines changed

crates/core/src/vm.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,7 @@ pub fn build_query<'a>(
6565
index_side: Table::MemTable(_),
6666
..
6767
},
68-
) => {
69-
let join: JoinExpr = join.into();
70-
let iter = join_inner(ctx, stdb, tx, result, join, true)?;
71-
Box::new(iter)
72-
}
68+
) => build_query(ctx, stdb, tx, join.to_inner_join().into())?,
7369
Query::IndexJoin(IndexJoin {
7470
probe_side,
7571
probe_field,

crates/vm/src/expr.rs

Lines changed: 139 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,63 @@ impl IndexJoin {
504504
}
505505
}
506506
}
507+
508+
// Convert this index join to an inner join, followed by a projection.
509+
// This is needed for incremental evaluation of index joins.
510+
// In particular when there are updates to both the left and right tables.
511+
// In other words, when an index join has two delta tables.
512+
pub fn to_inner_join(self) -> QueryExpr {
513+
if self.return_index_rows {
514+
let col_lhs = self.index_side.head().fields[usize::from(self.index_col)].field.clone();
515+
let col_rhs = self.probe_field;
516+
let rhs = self.probe_side;
517+
518+
let fields = self
519+
.index_side
520+
.head()
521+
.fields
522+
.iter()
523+
.cloned()
524+
.map(|Column { field, .. }| field.into())
525+
.collect();
526+
527+
let table = self.index_side.get_db_table().map(|t| t.table_id);
528+
let source = self.index_side.into();
529+
let inner_join = Query::JoinInner(JoinExpr::new(rhs, col_lhs, col_rhs));
530+
let project = Query::Project(fields, table);
531+
let query = if let Some(predicate) = self.index_select {
532+
vec![predicate.into(), inner_join, project]
533+
} else {
534+
vec![inner_join, project]
535+
};
536+
QueryExpr { source, query }
537+
} else {
538+
let col_rhs = self.index_side.head().fields[usize::from(self.index_col)].field.clone();
539+
let col_lhs = self.probe_field;
540+
let mut rhs: QueryExpr = self.index_side.into();
541+
542+
if let Some(predicate) = self.index_select {
543+
rhs.query.push(predicate.into());
544+
}
545+
546+
let fields = self
547+
.probe_side
548+
.source
549+
.head()
550+
.fields
551+
.iter()
552+
.cloned()
553+
.map(|Column { field, .. }| field.into())
554+
.collect();
555+
556+
let table = self.probe_side.source.get_db_table().map(|t| t.table_id);
557+
let source = self.probe_side.source;
558+
let inner_join = Query::JoinInner(JoinExpr::new(rhs, col_lhs, col_rhs));
559+
let project = Query::Project(fields, table);
560+
let query = vec![inner_join, project];
561+
QueryExpr { source, query }
562+
}
563+
}
507564
}
508565

509566
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
@@ -513,25 +570,6 @@ pub struct JoinExpr {
513570
pub col_rhs: FieldName,
514571
}
515572

516-
//TODO: A posterior PR should fix the pass of multi-columns to the query optimization
517-
impl From<IndexJoin> for JoinExpr {
518-
fn from(mut value: IndexJoin) -> Self {
519-
if let Some(predicate) = value.index_select {
520-
value.probe_side.query.push(predicate.into());
521-
};
522-
let pos = value.index_col;
523-
if value.return_index_rows {
524-
let col_lhs = value.index_side.head().fields[usize::from(pos)].field.clone();
525-
let col_rhs = value.probe_field;
526-
JoinExpr::new(value.probe_side, col_lhs, col_rhs)
527-
} else {
528-
let col_rhs = value.index_side.head().fields[usize::from(pos)].field.clone();
529-
let col_lhs = value.probe_field;
530-
JoinExpr::new(value.index_side.into(), col_lhs, col_rhs)
531-
}
532-
}
533-
}
534-
535573
impl JoinExpr {
536574
pub fn new(rhs: QueryExpr, col_lhs: FieldName, col_rhs: FieldName) -> Self {
537575
Self { rhs, col_lhs, col_rhs }
@@ -1931,6 +1969,88 @@ mod tests {
19311969
assert!(matches!(auth.check_auth(ALICE, BOB), Err(AuthError::OwnerRequired)));
19321970
}
19331971

1972+
fn mem_table(name: &str, fields: &[(&str, AlgebraicType, bool)]) -> MemTable {
1973+
let table_access = StAccess::Public;
1974+
let data = Vec::new();
1975+
let head = Header::new(
1976+
name.into(),
1977+
fields
1978+
.iter()
1979+
.enumerate()
1980+
.map(|(i, (field, ty, _))| Column::new(FieldName::named(name, field), ty.clone(), i.into()))
1981+
.collect(),
1982+
fields
1983+
.iter()
1984+
.enumerate()
1985+
.filter(|(_, (_, _, indexed))| *indexed)
1986+
.map(|(i, _)| (ColId(i as u32).into(), Constraints::indexed()))
1987+
.collect(),
1988+
);
1989+
MemTable {
1990+
head,
1991+
data,
1992+
table_access,
1993+
}
1994+
}
1995+
1996+
#[test]
1997+
fn test_index_to_inner_join() {
1998+
let index_side = mem_table(
1999+
"index",
2000+
&[("a", AlgebraicType::U8, false), ("b", AlgebraicType::U8, true)],
2001+
);
2002+
let probe_side = mem_table(
2003+
"probe",
2004+
&[("c", AlgebraicType::U8, false), ("b", AlgebraicType::U8, true)],
2005+
);
2006+
2007+
let probe_field = probe_side.head.fields[1].field.clone();
2008+
let select_field = FieldName::Name {
2009+
table: "index".into(),
2010+
field: "a".into(),
2011+
};
2012+
let index_select = ColumnOp::cmp(select_field, OpCmp::Eq, 0.into());
2013+
let join = IndexJoin {
2014+
probe_side: probe_side.clone().into(),
2015+
probe_field,
2016+
index_side: index_side.clone().into(),
2017+
index_select: Some(index_select.clone()),
2018+
index_col: 1.into(),
2019+
return_index_rows: false,
2020+
};
2021+
2022+
let expr = join.to_inner_join();
2023+
2024+
assert_eq!(expr.source, SourceExpr::MemTable(probe_side));
2025+
assert_eq!(expr.query.len(), 2);
2026+
2027+
let Query::JoinInner(ref join) = expr.query[0] else {
2028+
panic!("expected an inner join, but got {:#?}", expr.query[0]);
2029+
};
2030+
2031+
assert_eq!(join.col_lhs, FieldName::named("probe", "b"));
2032+
assert_eq!(join.col_rhs, FieldName::named("index", "b"));
2033+
assert_eq!(
2034+
join.rhs,
2035+
QueryExpr {
2036+
source: SourceExpr::MemTable(index_side),
2037+
query: vec![index_select.into()]
2038+
}
2039+
);
2040+
2041+
let Query::Project(ref fields, None) = expr.query[1] else {
2042+
panic!("expected a projection, but got {:#?}", expr.query[1]);
2043+
};
2044+
2045+
assert_eq!(
2046+
fields,
2047+
&vec![
2048+
FieldName::named("probe", "c").into(),
2049+
FieldName::named("probe", "b").into()
2050+
]
2051+
);
2052+
}
2053+
19342054
#[test]
19352055
fn test_auth_table() {
19362056
tables().iter().for_each(assert_owner_private)

0 commit comments

Comments
 (0)