@@ -9,9 +9,8 @@ use spacetimedb_lib::identity::AuthCtx;
99use spacetimedb_primitives:: { ColId , TableId } ;
1010use spacetimedb_sats:: db:: auth:: { StAccess , StTableType } ;
1111use spacetimedb_sats:: db:: def:: { ColumnDef , IndexDef , ProductTypeMeta , TableDef } ;
12- use spacetimedb_sats:: relation:: {
13- DbTable , FieldExpr , FieldName , Header , MemTable , RelIter , RelValue , Relation , RowCount , Table ,
14- } ;
12+ use spacetimedb_sats:: relation:: { DbTable , FieldExpr , FieldName , RelValueRef , Relation } ;
13+ use spacetimedb_sats:: relation:: { Header , MemTable , RelIter , RelValue , RowCount , Table } ;
1514use spacetimedb_sats:: { AlgebraicValue , ProductValue } ;
1615use spacetimedb_vm:: env:: EnvDb ;
1716use spacetimedb_vm:: errors:: ErrorVm ;
@@ -53,13 +52,31 @@ pub fn build_query<'a>(
5352 let iter = result. select ( move |row| cmp. compare ( row, & header) ) ;
5453 Box :: new ( iter)
5554 }
55+ // If this is an index join between two virtual tables, replace with an inner join.
56+ // Such a plan is possible under incremental evaluation,
57+ // when there are updates to both base tables,
58+ // however an index lookup is invalid on a virtual table.
59+ //
60+ // TODO: This logic should be entirely encapsulated within the query planner.
61+ // It should not be possible for the planner to produce an invalid plan.
62+ Query :: IndexJoin ( join)
63+ if !db_table
64+ && matches ! ( join. probe_side. source, SourceExpr :: MemTable ( _) )
65+ && join. probe_side . source . table_name ( ) != result. head ( ) . table_name =>
66+ {
67+ let join: JoinExpr = join. into ( ) ;
68+ let iter = join_inner ( ctx, stdb, tx, result, join, true ) ?;
69+ Box :: new ( iter)
70+ }
5671 Query :: IndexJoin ( IndexJoin {
5772 probe_side,
5873 probe_field,
5974 index_header,
75+ index_select,
6076 index_table,
6177 index_col,
62- } ) if db_table => {
78+ return_index_rows,
79+ } ) => {
6380 let probe_side = build_query ( ctx, stdb, tx, probe_side. into ( ) ) ?;
6481 Box :: new ( IndexSemiJoin {
6582 ctx,
@@ -68,16 +85,13 @@ pub fn build_query<'a>(
6885 probe_side,
6986 probe_field,
7087 index_header,
88+ index_select,
7189 index_table,
7290 index_col,
7391 index_iter : None ,
92+ return_index_rows,
7493 } )
7594 }
76- Query :: IndexJoin ( join) => {
77- let join: JoinExpr = join. into ( ) ;
78- let iter = join_inner ( ctx, stdb, tx, result, join, true ) ?;
79- Box :: new ( iter)
80- }
8195 Query :: Select ( cmp) => {
8296 let header = result. head ( ) . clone ( ) ;
8397 let iter = result. select ( move |row| cmp. compare ( row, & header) ) ;
@@ -189,12 +203,15 @@ pub struct IndexSemiJoin<'a, Rhs: RelOps> {
189203 // The field whose value will be used to probe the index.
190204 pub probe_field : FieldName ,
191205 // The header for the index side of the join.
192- // Also the return header since we are returning values from the index side.
193206 pub index_header : Header ,
207+ // An optional predicate to evaluate over the matching rows of the index.
208+ pub index_select : Option < ColumnOp > ,
194209 // The table id on which the index is defined.
195210 pub index_table : TableId ,
196211 // The column id for which the index is defined.
197212 pub index_col : ColId ,
213+ // Is this a left or right semijion?
214+ pub return_index_rows : bool ,
198215 // An iterator for the index side.
199216 // A new iterator will be instantiated for each row on the probe side.
200217 pub index_iter : Option < IterByColEq < ' a > > ,
@@ -206,9 +223,32 @@ pub struct IndexSemiJoin<'a, Rhs: RelOps> {
206223 ctx : & ' a ExecutionContext < ' a > ,
207224}
208225
226+ impl < ' a , Rhs : RelOps > IndexSemiJoin < ' a , Rhs > {
227+ fn filter ( & self , index_row : RelValueRef ) -> Result < bool , ErrorVm > {
228+ if let Some ( op) = & self . index_select {
229+ Ok ( op. compare ( index_row, & self . index_header ) ?)
230+ } else {
231+ Ok ( true )
232+ }
233+ }
234+
235+ fn map ( & self , index_row : RelValue , probe_row : Option < RelValue > ) -> RelValue {
236+ if let Some ( value) = probe_row {
237+ if !self . return_index_rows {
238+ return value;
239+ }
240+ }
241+ index_row
242+ }
243+ }
244+
209245impl < ' a , Rhs : RelOps > RelOps for IndexSemiJoin < ' a , Rhs > {
210246 fn head ( & self ) -> & Header {
211- & self . index_header
247+ if self . return_index_rows {
248+ & self . index_header
249+ } else {
250+ self . probe_side . head ( )
251+ }
212252 }
213253
214254 fn row_count ( & self ) -> RowCount {
@@ -218,8 +258,13 @@ impl<'a, Rhs: RelOps> RelOps for IndexSemiJoin<'a, Rhs> {
218258 #[ tracing:: instrument( skip_all) ]
219259 fn next ( & mut self ) -> Result < Option < RelValue > , ErrorVm > {
220260 // Return a value from the current index iterator, if not exhausted.
221- if let Some ( value) = self . index_iter . as_mut ( ) . and_then ( |iter| iter. next ( ) ) {
222- return Ok ( Some ( value. to_rel_value ( ) ) ) ;
261+ if self . return_index_rows {
262+ while let Some ( value) = self . index_iter . as_mut ( ) . and_then ( |iter| iter. next ( ) ) {
263+ let value = value. to_rel_value ( ) ;
264+ if self . filter ( value. as_val_ref ( ) ) ? {
265+ return Ok ( Some ( self . map ( value, None ) ) ) ;
266+ }
267+ }
223268 }
224269 // Otherwise probe the index with a row from the probe side.
225270 while let Some ( row) = self . probe_side . next ( ) ? {
@@ -229,9 +274,12 @@ impl<'a, Rhs: RelOps> RelOps for IndexSemiJoin<'a, Rhs> {
229274 let col_id = self . index_col ;
230275 let value = value. clone ( ) ;
231276 let mut index_iter = self . db . iter_by_col_eq ( self . ctx , self . tx , table_id, col_id, value) ?;
232- if let Some ( value) = index_iter. next ( ) {
233- self . index_iter = Some ( index_iter) ;
234- return Ok ( Some ( value. to_rel_value ( ) ) ) ;
277+ while let Some ( value) = index_iter. next ( ) {
278+ let value = value. to_rel_value ( ) ;
279+ if self . filter ( value. as_val_ref ( ) ) ? {
280+ self . index_iter = Some ( index_iter) ;
281+ return Ok ( Some ( self . map ( value, Some ( row) ) ) ) ;
282+ }
235283 }
236284 }
237285 }
0 commit comments