@@ -23,7 +23,7 @@ use spacetimedb_client_api_messages::websocket::{
2323use spacetimedb_data_structures:: map:: { Entry , IntMap } ;
2424use spacetimedb_lib:: metrics:: ExecutionMetrics ;
2525use spacetimedb_lib:: { AlgebraicValue , ConnectionId , Identity , ProductValue } ;
26- use spacetimedb_primitives:: { ColId , TableId } ;
26+ use spacetimedb_primitives:: { ColId , IndexId , TableId } ;
2727use spacetimedb_subscription:: { SubscriptionPlan , TableName } ;
2828use std:: collections:: BTreeSet ;
2929use std:: sync:: atomic:: { AtomicBool , Ordering } ;
@@ -78,6 +78,15 @@ impl Plan {
7878 self . plans [ 0 ] . subscribed_table_name ( )
7979 }
8080
81+ /// Returns the index ids from which this subscription reads
82+ pub fn index_ids ( & self ) -> impl Iterator < Item = ( TableId , IndexId ) > {
83+ self . plans
84+ . iter ( )
85+ . flat_map ( |plan| plan. index_ids ( ) )
86+ . collect :: < HashSet < _ > > ( )
87+ . into_iter ( )
88+ }
89+
8190 /// Returns the table ids from which this subscription reads
8291 pub fn table_ids ( & self ) -> impl Iterator < Item = TableId > + ' _ {
8392 self . plans
@@ -174,6 +183,11 @@ impl QueryState {
174183 itertools:: chain ( & self . legacy_subscribers , & self . subscriptions )
175184 }
176185
186+ /// Return the [`Query`] for this [`QueryState`]
187+ pub fn query ( & self ) -> & Query {
188+ & self . query
189+ }
190+
177191 /// Return the search arguments for this query
178192 fn search_args ( & self ) -> impl Iterator < Item = ( TableId , ColId , AlgebraicValue ) > {
179193 let mut args = HashSet :: new ( ) ;
@@ -305,6 +319,79 @@ impl SearchArguments {
305319
306320type ClientsMap = HashMap < ClientId , ClientInfo > ;
307321
322+ /// Keeps track of the indexes that are used in subscriptions.
323+ #[ derive( Debug , Default ) ]
324+ pub struct QueriedTableIndexIds {
325+ ids : HashMap < TableId , HashMap < IndexId , usize > > ,
326+ }
327+
328+ impl FromIterator < ( TableId , IndexId ) > for QueriedTableIndexIds {
329+ fn from_iter < T : IntoIterator < Item = ( TableId , IndexId ) > > ( iter : T ) -> Self {
330+ let mut index_ids = Self :: default ( ) ;
331+ for ( table_id, index_id) in iter {
332+ index_ids. insert_index_id ( table_id, index_id) ;
333+ }
334+ index_ids
335+ }
336+ }
337+
338+ impl QueriedTableIndexIds {
339+ /// Returns the index ids that are used in subscriptions for this table.
340+ /// Note, it does not return all of the index ids that are defined on this table.
341+ /// Only those that are used by at least one subscription query.
342+ pub fn index_ids_for_table ( & self , table_id : TableId ) -> impl Iterator < Item = IndexId > + ' _ {
343+ self . ids
344+ . get ( & table_id)
345+ . into_iter ( )
346+ . flat_map ( |index_ids| index_ids. keys ( ) )
347+ . copied ( )
348+ }
349+
350+ /// Insert a new `table_id` `index_id` pair into this container.
351+ /// Note, different queries may read from the same index.
352+ /// Hence we may already be tracking this index, in which case we bump its ref count.
353+ pub fn insert_index_id ( & mut self , table_id : TableId , index_id : IndexId ) {
354+ * self . ids . entry ( table_id) . or_default ( ) . entry ( index_id) . or_default ( ) += 1 ;
355+ }
356+
357+ /// Remove a `table_id` `index_id` pair from this container.
358+ /// Note, different queries may read from the same index.
359+ /// Hence we only remove this key from the map if its ref count goes to zero.
360+ pub fn delete_index_id ( & mut self , table_id : TableId , index_id : IndexId ) {
361+ if let Some ( ids) = self . ids . get_mut ( & table_id) {
362+ if let Some ( n) = ids. get_mut ( & index_id) {
363+ * n -= 1 ;
364+
365+ if * n == 0 {
366+ ids. remove ( & index_id) ;
367+
368+ if ids. is_empty ( ) {
369+ self . ids . remove ( & table_id) ;
370+ }
371+ }
372+ }
373+ }
374+ }
375+
376+ /// Insert the index ids from which a query reads into this mapping.
377+ /// Note, an index may already be tracked if another query is already using it.
378+ /// In this case we just bump its ref count.
379+ pub fn insert_index_ids_for_query ( & mut self , query : & Query ) {
380+ for ( table_id, index_id) in query. index_ids ( ) {
381+ self . insert_index_id ( table_id, index_id) ;
382+ }
383+ }
384+
385+ /// Delete the index ids from which a query reads from this mapping
386+ /// Note, we will not remove an index id from this mapping if another query is using it.
387+ /// Instead we decrement its ref count.
388+ pub fn delete_index_ids_for_query ( & mut self , query : & Query ) {
389+ for ( table_id, index_id) in query. index_ids ( ) {
390+ self . delete_index_id ( table_id, index_id) ;
391+ }
392+ }
393+ }
394+
308395/// Responsible for the efficient evaluation of subscriptions.
309396/// It performs basic multi-query optimization,
310397/// in that if a query has N subscribers,
@@ -318,17 +405,21 @@ pub struct SubscriptionManager {
318405 /// in order to dispatch messages to clients.
319406 clients : Arc < RwLock < ClientsMap > > ,
320407
321- // Queries for which there is at least one subscriber.
408+ /// Queries for which there is at least one subscriber.
322409 queries : HashMap < QueryHash , QueryState > ,
323410
324- // If a query reads from a table,
325- // but does not have a simple equality filter on that table,
326- // we map the table to the query in this inverted index.
411+ /// If a query reads from a table,
412+ /// but does not have a simple equality filter on that table,
413+ /// we map the table to the query in this inverted index.
327414 tables : IntMap < TableId , HashSet < QueryHash > > ,
328415
329- // If a query reads from a table,
330- // and has a simple equality filter on that table,
331- // we map the filter values to the query in this lookup table.
416+ /// Tracks the indices used across all subscriptions
417+ /// to enable building the appropriate indexes for row updates.
418+ indexes : QueriedTableIndexIds ,
419+
420+ /// If a query reads from a table,
421+ /// and has a simple equality filter on that table,
422+ /// we map the filter values to the query in this lookup table.
332423 search_args : SearchArguments ,
333424
334425 /// Transmit side of a channel to the manager's [`Self::send_worker`] task.
@@ -418,6 +509,7 @@ impl SubscriptionManager {
418509 Self {
419510 clients : Default :: default ( ) ,
420511 queries : Default :: default ( ) ,
512+ indexes : Default :: default ( ) ,
421513 tables : Default :: default ( ) ,
422514 search_args : Default :: default ( ) ,
423515 send_worker_tx,
@@ -506,6 +598,7 @@ impl SubscriptionManager {
506598 if !query_state. has_subscribers ( ) {
507599 SubscriptionManager :: remove_query_from_tables (
508600 & mut self . tables ,
601+ & mut self . indexes ,
509602 & mut self . search_args ,
510603 & query_state. query ,
511604 ) ;
@@ -572,6 +665,7 @@ impl SubscriptionManager {
572665 if !query_state. has_subscribers ( ) {
573666 SubscriptionManager :: remove_query_from_tables (
574667 & mut self . tables ,
668+ & mut self . indexes ,
575669 & mut self . search_args ,
576670 & query_state. query ,
577671 ) ;
@@ -639,7 +733,7 @@ impl SubscriptionManager {
639733 . entry ( hash)
640734 . or_insert_with ( || QueryState :: new ( query. clone ( ) ) ) ;
641735
642- Self :: insert_query ( & mut self . tables , & mut self . search_args , query_state) ;
736+ Self :: insert_query ( & mut self . tables , & mut self . indexes , & mut self . search_args , query_state) ;
643737
644738 let entry = ci. subscription_ref_count . entry ( hash) . or_insert ( 0 ) ;
645739 * entry += 1 ;
@@ -687,7 +781,7 @@ impl SubscriptionManager {
687781 . queries
688782 . entry ( hash)
689783 . or_insert_with ( || QueryState :: new ( unit. clone ( ) ) ) ;
690- Self :: insert_query ( & mut self . tables , & mut self . search_args , query_state) ;
784+ Self :: insert_query ( & mut self . tables , & mut self . indexes , & mut self . search_args , query_state) ;
691785 query_state. legacy_subscribers . insert ( client_id) ;
692786 }
693787 }
@@ -697,11 +791,13 @@ impl SubscriptionManager {
697791 // This takes a ref to the table map instead of `self` to avoid borrowing issues.
698792 fn remove_query_from_tables (
699793 tables : & mut IntMap < TableId , HashSet < QueryHash > > ,
794+ index_ids : & mut QueriedTableIndexIds ,
700795 search_args : & mut SearchArguments ,
701796 query : & Query ,
702797 ) {
703798 let hash = query. hash ( ) ;
704799 search_args. remove_query ( & hash) ;
800+ index_ids. delete_index_ids_for_query ( query) ;
705801 for table_id in query. table_ids ( ) {
706802 if let Entry :: Occupied ( mut entry) = tables. entry ( table_id) {
707803 let hashes = entry. get_mut ( ) ;
@@ -717,11 +813,13 @@ impl SubscriptionManager {
717813 // This takes a ref to the table map instead of `self` to avoid borrowing issues.
718814 fn insert_query (
719815 tables : & mut IntMap < TableId , HashSet < QueryHash > > ,
816+ index_ids : & mut QueriedTableIndexIds ,
720817 search_args : & mut SearchArguments ,
721818 query_state : & QueryState ,
722819 ) {
723820 // If this is new, we need to update the table to query mapping.
724821 if !query_state. has_subscribers ( ) {
822+ index_ids. insert_index_ids_for_query ( query_state. query ( ) ) ;
725823 let hash = query_state. query . hash ( ) ;
726824 let mut table_ids = query_state. query . table_ids ( ) . collect :: < HashSet < _ > > ( ) ;
727825 for ( table_id, col_id, arg) in query_state. search_args ( ) {
@@ -760,6 +858,7 @@ impl SubscriptionManager {
760858 queries_to_remove. push ( * query_hash) ;
761859 SubscriptionManager :: remove_query_from_tables (
762860 & mut self . tables ,
861+ & mut self . indexes ,
763862 & mut self . search_args ,
764863 & query_state. query ,
765864 ) ;
@@ -825,6 +924,11 @@ impl SubscriptionManager {
825924 queries. into_iter ( )
826925 }
827926
927+ /// Returns the index ids that are used in subscription queries
928+ pub fn index_ids_for_subscriptions ( & self ) -> & QueriedTableIndexIds {
929+ & self . indexes
930+ }
931+
828932 /// This method takes a set of delta tables,
829933 /// evaluates only the necessary queries for those delta tables,
830934 /// and then sends the results to each client.
0 commit comments