11use crate :: db:: MetricsRecorderQueue ;
22use crate :: error:: { DBError , DatabaseError , RestoreSnapshotError } ;
3+ use crate :: host:: ArgsTuple ;
34use crate :: messages:: control_db:: HostType ;
45use crate :: subscription:: ExecutionCounters ;
56use crate :: util:: { asyncify, spawn_rayon} ;
67use crate :: worker_metrics:: WORKER_METRICS ;
78use anyhow:: { anyhow, Context } ;
9+ use bytes:: Bytes ;
810use enum_map:: EnumMap ;
911use fs2:: FileExt ;
12+ use log:: trace;
1013use spacetimedb_commitlog as commitlog;
1114use spacetimedb_commitlog:: repo:: OnNewSegmentFn ;
1215use spacetimedb_data_structures:: map:: IntSet ;
1316use spacetimedb_datastore:: db_metrics:: DB_METRICS ;
14- use spacetimedb_datastore:: error:: { DatastoreError , TableError } ;
17+ use spacetimedb_datastore:: error:: { DatastoreError , TableError , ViewError } ;
1518use spacetimedb_datastore:: execution_context:: { ReducerContext , Workload , WorkloadType } ;
1619use spacetimedb_datastore:: locking_tx_datastore:: datastore:: TxMetrics ;
1720use spacetimedb_datastore:: locking_tx_datastore:: state_view:: {
1821 IterByColEqMutTx , IterByColRangeMutTx , IterMutTx , IterTx , StateView ,
1922} ;
2023use spacetimedb_datastore:: locking_tx_datastore:: { MutTxId , TxId } ;
21- use spacetimedb_datastore:: system_tables:: { system_tables, StModuleRow } ;
24+ use spacetimedb_datastore:: system_tables:: ST_VIEW_ID ;
25+ use spacetimedb_datastore:: system_tables:: { system_tables, StModuleRow , StViewRow } ;
2226use spacetimedb_datastore:: system_tables:: { StFields , StVarFields , StVarName , StVarRow , ST_MODULE_ID , ST_VAR_ID } ;
2327use spacetimedb_datastore:: traits:: {
2428 InsertFlags , IsolationLevel , Metadata , MutTx as _, MutTxDatastore , Program , RowTypeForTable , Tx as _, TxDatastore ,
@@ -32,16 +36,18 @@ use spacetimedb_datastore::{
3236 traits:: TxData ,
3337} ;
3438use spacetimedb_durability as durability;
39+ use spacetimedb_lib:: bsatn:: ToBsatn ;
3540use spacetimedb_lib:: db:: auth:: StAccess ;
3641use spacetimedb_lib:: db:: raw_def:: v9:: { btree, RawModuleDefV9Builder , RawSql } ;
42+ use spacetimedb_lib:: de:: DeserializeSeed as _;
3743use spacetimedb_lib:: st_var:: StVarValue ;
38- use spacetimedb_lib:: ConnectionId ;
3944use spacetimedb_lib:: Identity ;
45+ use spacetimedb_lib:: { bsatn, ConnectionId } ;
4046use spacetimedb_paths:: server:: { CommitLogDir , ReplicaDir , SnapshotsPath } ;
4147use spacetimedb_primitives:: * ;
4248use spacetimedb_sats:: algebraic_type:: fmt:: fmt_algebraic_type;
4349use spacetimedb_sats:: memory_usage:: MemoryUsage ;
44- use spacetimedb_sats:: { AlgebraicType , AlgebraicValue , ProductType , ProductValue } ;
50+ use spacetimedb_sats:: { product , AlgebraicType , AlgebraicValue , ProductType , ProductValue , Typespace } ;
4551use spacetimedb_schema:: def:: { ModuleDef , TableDef , ViewDef } ;
4652use spacetimedb_schema:: schema:: {
4753 ColumnSchema , IndexSchema , RowLevelSecuritySchema , Schema , SequenceSchema , TableSchema ,
@@ -1403,6 +1409,20 @@ impl RelationalDB {
14031409 Ok ( rows_deleted)
14041410 }
14051411
1412+ /// Clear all rows from all view tables without dropping them.
1413+ pub fn clear_all_views ( & self , tx : & mut MutTx ) -> Result < ( ) , DBError > {
1414+ for table_id in tx
1415+ . iter ( ST_VIEW_ID ) ?
1416+ . map ( StViewRow :: try_from)
1417+ . collect :: < Result < Vec < _ > , _ > > ( ) ?
1418+ . into_iter ( )
1419+ . filter_map ( |row| row. table_id )
1420+ {
1421+ tx. clear_table ( table_id) ?;
1422+ }
1423+ Ok ( ( ) )
1424+ }
1425+
14061426 pub fn create_sequence ( & self , tx : & mut MutTx , sequence_schema : SequenceSchema ) -> Result < SequenceId , DBError > {
14071427 Ok ( self . inner . create_sequence_mut_tx ( tx, sequence_schema) ?)
14081428 }
@@ -1498,8 +1518,106 @@ impl RelationalDB {
14981518 . into ( )
14991519 } )
15001520 }
1501- }
15021521
1522+ /// Materialize View backing table.
1523+ ///
1524+ /// # Process
1525+ /// 1. Serializes view arguments into `ST_VIEW_ARG_ID`
1526+ /// 2. Deletes stale rows matching the view arguments
1527+ /// 3. Deserializes the new view execution results
1528+ /// 4. Inserts fresh rows with the corresponding arg_id
1529+ ///
1530+ /// # Arguments
1531+ /// * `tx` - Mutable transaction context
1532+ /// * `view` - Name of the view to update
1533+ /// * `args` - Arguments passed to the view call
1534+ /// * `return_type` - Expected return type of the view
1535+ /// * `bytes` - Serialized (bsatn encoded) return value from view execution
1536+ /// * `typespace` - Type information for deserialization
1537+ /// * `caller_identity` - Identity of the caller (for non-anonymous views)
1538+ #[ allow( clippy:: too_many_arguments) ]
1539+ pub fn materialize_view (
1540+ & self ,
1541+ tx : & mut MutTxId ,
1542+ view : & str ,
1543+ args : ArgsTuple ,
1544+ return_type : AlgebraicType ,
1545+ bytes : Bytes ,
1546+ typespace : & Typespace ,
1547+ caller_identity : Identity ,
1548+ ) -> Result < ( ) , DBError > {
1549+ // Fetch view metadata
1550+ let st_view_row = tx. lookup_st_view_by_name ( view) ?;
1551+ let table_id = st_view_row. table_id . expect ( "View table must exist for materialization" ) ;
1552+ let is_anonymous = st_view_row. is_anonymous ;
1553+
1554+ let arg_id = tx. get_or_insert_st_view_arg ( args. get_bsatn ( ) ) ?;
1555+
1556+ // Build the filter key for identifying rows to update
1557+ let input_args = product ! [
1558+ if is_anonymous {
1559+ AlgebraicValue :: OptionNone ( )
1560+ } else {
1561+ AlgebraicValue :: OptionSome ( caller_identity. into( ) )
1562+ } ,
1563+ AlgebraicValue :: U64 ( arg_id)
1564+ ] ;
1565+
1566+ // Remove stale View entries
1567+ let rows_to_delete: Vec < _ > = self
1568+ . iter_by_col_eq_mut ( tx, table_id, [ 0 , 1 ] , & input_args. clone ( ) . into ( ) ) ?
1569+ . map ( |res| res. pointer ( ) )
1570+ . collect ( ) ;
1571+
1572+ let deleted_count = self . delete ( tx, table_id, rows_to_delete) ;
1573+ trace ! ( "Deleted {deleted_count} stale rows from view table {table_id} for arg_id {arg_id}" ) ;
1574+
1575+ // Deserialize the return value
1576+ let seed = spacetimedb_sats:: WithTypespace :: new ( typespace, & return_type) ;
1577+ let return_val = seed
1578+ . deserialize ( bsatn:: Deserializer :: new ( & mut & bytes[ ..] ) )
1579+ . map_err ( |e| DatastoreError :: from ( ViewError :: DeserializeReturn ( e. to_string ( ) ) ) ) ?;
1580+
1581+ // Extract products from return value (must be array or option)
1582+ let products: Vec < ProductValue > = if return_type. is_array ( ) {
1583+ let arr = return_val
1584+ . into_array ( )
1585+ . expect ( "return_type.is_array() ensures this is an array" ) ;
1586+
1587+ arr. into_iter ( ) . map ( |v| v. into_product ( ) . unwrap ( ) ) . collect ( )
1588+ } else if return_type. is_option ( ) {
1589+ let opt = return_val
1590+ . into_option ( )
1591+ . expect ( "return_type.is_option() ensures this is an option" ) ;
1592+ opt. into_iter ( ) . map ( |v| v. into_product ( ) . unwrap ( ) ) . collect ( )
1593+ } else {
1594+ return Err ( DatastoreError :: from ( ViewError :: InvalidReturnType ( return_type) ) . into ( ) ) ;
1595+ } ;
1596+
1597+ // Insert fresh results into the view table
1598+ let mut elements: Vec < AlgebraicValue > =
1599+ Vec :: with_capacity ( input_args. elements . len ( ) + products. first ( ) . map_or ( 0 , |p| p. elements . len ( ) ) ) ;
1600+ for product in products {
1601+ elements. clear ( ) ;
1602+ // Build complete row by prepending filter key to product data
1603+ let mut elements = Vec :: with_capacity ( input_args. elements . len ( ) + product. elements . len ( ) ) ;
1604+ elements. extend_from_slice ( & input_args. elements ) ;
1605+ elements. extend_from_slice ( & product. elements ) ;
1606+
1607+ let row = ProductValue {
1608+ elements : elements. into_boxed_slice ( ) ,
1609+ } ;
1610+
1611+ let row_bytes = row
1612+ . to_bsatn_vec ( )
1613+ . map_err ( |_| DatastoreError :: from ( ViewError :: SerializeRow ) ) ?;
1614+
1615+ self . insert ( tx, table_id, & row_bytes) ?;
1616+ }
1617+
1618+ Ok ( ( ) )
1619+ }
1620+ }
15031621#[ allow( unused) ]
15041622#[ derive( Clone ) ]
15051623struct LockFile {
0 commit comments