@@ -63,7 +63,7 @@ use spacetimedb_lib::{bsatn, ConnectionId, TimeDuration, Timestamp};
6363use spacetimedb_primitives:: { ArgId , HttpHandlerId , ProcedureId , TableId , ViewFnPtr , ViewId } ;
6464use spacetimedb_query:: compile_subscription;
6565use spacetimedb_sats:: raw_identifier:: RawIdentifier ;
66- use spacetimedb_sats:: { AlgebraicType , AlgebraicTypeRef , ProductValue } ;
66+ use spacetimedb_sats:: { AlgebraicType , AlgebraicTypeRef , ProductValue , Typespace } ;
6767use spacetimedb_schema:: auto_migrate:: { AutoMigrateError , MigrationPolicy } ;
6868use spacetimedb_schema:: def:: { ModuleDef , ProcedureDef , ReducerDef , TableDef , ViewDef } ;
6969use spacetimedb_schema:: identifier:: Identifier ;
@@ -560,9 +560,50 @@ pub fn create_table_from_def(
560560 module_def : & ModuleDef ,
561561 table_def : & TableDef ,
562562) -> anyhow:: Result < ( ) > {
563- let schema = TableSchema :: from_module_def ( module_def, table_def, ( ) , TableId :: SENTINEL ) ;
563+ create_table_from_def_with_prefix ( stdb, tx, module_def, table_def, "" )
564+ }
565+
566+ /// Creates a mounted submodule table in `stdb`, applying the namespace to its canonical name.
567+ /// `name_prefix` is the dot-terminated namespace string (e.g. `"alias."`).
568+ pub fn create_table_from_def_with_prefix (
569+ stdb : & RelationalDB ,
570+ tx : & mut MutTxId ,
571+ owning_def : & ModuleDef ,
572+ table_def : & TableDef ,
573+ name_prefix : & str ,
574+ ) -> anyhow:: Result < ( ) > {
575+ let mut schema = TableSchema :: from_module_def ( owning_def, table_def, ( ) , TableId :: SENTINEL ) ;
576+ if !name_prefix. is_empty ( ) {
577+ // Use accessor_name so the canonical DB name matches what TypeScript looks up
578+ // as `namespace + table.sourceName`. '.' is not a valid XID char so namespaced
579+ // table names can never collide with user-defined tables.
580+ let prefixed_name = format ! ( "{}{}" , name_prefix, & * table_def. accessor_name) ;
581+ schema. table_name = TableName :: new_raw ( RawIdentifier :: from ( prefixed_name) ) ;
582+
583+ // No alias needed: the namespaced canonical name is already the unique lookup key.
584+ schema. alias = None ;
585+
586+ // Apply the namespace to the scheduled reducer/procedure name so the scheduler can
587+ // resolve it via reducer_by_name / procedure_by_name, both of which use '/' as the
588+ // namespace separator (e.g. "lib." prefix → "lib/reducerName").
589+ // name_prefix is always of the form "ns." or "ns1.ns2." with no internal dots in
590+ // individual segments, so replacing '.' with '/' is unambiguous.
591+ if let Some ( schedule) = & mut schema. schedule {
592+ let fn_prefix = name_prefix. replace ( '.' , "/" ) ;
593+ let prefixed_fn = format ! ( "{}{}" , fn_prefix, & * schedule. function_name) ;
594+ schedule. function_name = Identifier :: new_assume_valid ( RawIdentifier :: from ( prefixed_fn) ) ;
595+ }
596+
597+ // Apply the namespace to index canonical names and aliases for global uniqueness.
598+ for index in & mut schema. indexes {
599+ index. index_name = RawIdentifier :: from ( format ! ( "{}{}" , name_prefix, index. index_name) ) ;
600+ if let Some ( alias) = & index. alias {
601+ index. alias = Some ( RawIdentifier :: from ( format ! ( "{}{}" , name_prefix, alias) ) ) ;
602+ }
603+ }
604+ }
564605 stdb. create_table ( tx, schema)
565- . with_context ( || format ! ( "failed to create table {}" , & table_def. name ) ) ?;
606+ . with_context ( || format ! ( "failed to create table {}{} " , name_prefix , & * table_def. accessor_name ) ) ?;
566607 Ok ( ( ) )
567608}
568609
@@ -578,6 +619,20 @@ pub fn create_table_from_view_def(
578619 Ok ( ( ) )
579620}
580621
622+ /// Creates the table for a mounted `view_def` in `stdb`, applying the namespace prefix.
623+ /// `name_prefix` is the dot-terminated namespace string (e.g. `"lib."`).
624+ pub fn create_table_from_view_def_with_prefix (
625+ stdb : & RelationalDB ,
626+ tx : & mut MutTxId ,
627+ owning_def : & ModuleDef ,
628+ view_def : & ViewDef ,
629+ name_prefix : & str ,
630+ ) -> anyhow:: Result < ( ) > {
631+ stdb. create_view_with_prefix ( tx, owning_def, view_def, name_prefix)
632+ . with_context ( || format ! ( "failed to create table for view {}{}" , name_prefix, & view_def. name) ) ?;
633+ Ok ( ( ) )
634+ }
635+
581636/// Moves out the `trapped: bool` from `res`.
582637fn extract_trapped < T , E > ( res : Result < ( T , bool ) , E > ) -> ( Result < T , E > , bool ) {
583638 match res {
@@ -612,21 +667,35 @@ fn init_database_inner(
612667 let auth_ctx = AuthCtx :: for_current ( owner_identity) ;
613668 let ( tx, ( ) ) = stdb
614669 . with_auto_rollback ( tx, |tx| {
615- // Create all in-memory tables defined by the module,
616- // with IDs ordered lexicographically by the table names.
617- let mut table_defs: Vec < _ > = module_def. tables ( ) . collect ( ) ;
618- table_defs. sort_by_key ( |x| & x. name ) ;
619- for def in table_defs {
620- logger. info ( & format ! ( "Creating table `{}`" , & def. name) ) ;
621- create_table_from_def ( stdb, tx, module_def, def) ?;
670+ // Create all in-memory tables defined by the module (including mounted submodules),
671+ // with IDs ordered lexicographically by their full namespaced names.
672+ let mut table_defs = module_def. all_tables_with_prefix ( ) ;
673+ table_defs. sort_by ( |( p1, _, d1) , ( p2, _, d2) | {
674+ let n1 = format ! ( "{}{}" , p1, d1. name) ;
675+ let n2 = format ! ( "{}{}" , p2, d2. name) ;
676+ n1. cmp ( & n2)
677+ } ) ;
678+ for ( prefix, owning_def, def) in table_defs {
679+ let display_name = format ! ( "{}{}" , prefix, def. name) ;
680+ logger. info ( & format ! ( "Creating table `{}`" , display_name) ) ;
681+ create_table_from_def_with_prefix ( stdb, tx, owning_def, def, & prefix) ?;
622682 }
623683
624- // Create all in-memory views defined by the module.
625- let mut view_defs: Vec < _ > = module_def. views ( ) . collect ( ) ;
626- view_defs. sort_by_key ( |x| & x. name ) ;
627- for def in view_defs {
628- logger. info ( & format ! ( "Creating table for view `{}`" , & def. name) ) ;
629- create_table_from_view_def ( stdb, tx, module_def, def) ?;
684+ // Create all in-memory views defined by the module (root + mounted).
685+ let mut view_defs: Vec < ( String , & ModuleDef , & ViewDef ) > = module_def. all_views_with_prefix ( ) ;
686+ view_defs. sort_by ( |( p1, _, d1) , ( p2, _, d2) | {
687+ let n1 = format ! ( "{}{}" , p1, d1. name) ;
688+ let n2 = format ! ( "{}{}" , p2, d2. name) ;
689+ n1. cmp ( & n2)
690+ } ) ;
691+ for ( prefix, owning_def, def) in view_defs {
692+ let display_name = format ! ( "{}{}" , prefix, def. name) ;
693+ logger. info ( & format ! ( "Creating table for view `{}`" , display_name) ) ;
694+ if prefix. is_empty ( ) {
695+ create_table_from_view_def ( stdb, tx, owning_def, def) ?;
696+ } else {
697+ create_table_from_view_def_with_prefix ( stdb, tx, owning_def, def, & prefix) ?;
698+ }
630699 }
631700
632701 // Insert the late-bound row-level security expressions.
@@ -706,7 +775,7 @@ pub fn call_identity_connected(
706775 // abort the connection: we can't really recover.
707776 let tx = Some ( ScopeGuard :: into_inner ( mut_tx) ) ;
708777 let params = ModuleHost :: call_reducer_params (
709- module,
778+ & module. module_def ,
710779 caller_auth. claims . identity ,
711780 Some ( caller_connection_id) ,
712781 None ,
@@ -1087,6 +1156,10 @@ pub struct CallViewParams {
10871156 pub args : ArgsTuple ,
10881157 pub row_type : AlgebraicTypeRef ,
10891158 pub timestamp : Timestamp ,
1159+ /// The typespace of the module that owns this view.
1160+ /// For root views this equals the top-level typespace;
1161+ /// for mounted views this is the mount's own typespace.
1162+ pub view_typespace : Typespace ,
10901163}
10911164
10921165pub struct CallProcedureParams {
@@ -2083,7 +2156,7 @@ impl ModuleHost {
20832156 // that `st_client` is updated appropriately.
20842157 let tx = Some ( mut_tx) ;
20852158 let result = Self :: call_reducer_params (
2086- info,
2159+ & info. module_def ,
20872160 caller_identity,
20882161 Some ( caller_connection_id) ,
20892162 None ,
@@ -2170,7 +2243,7 @@ impl ModuleHost {
21702243 }
21712244
21722245 fn call_reducer_params (
2173- module : & ModuleInfo ,
2246+ owning_def : & ModuleDef ,
21742247 caller_identity : Identity ,
21752248 caller_connection_id : Option < ConnectionId > ,
21762249 client : Option < Arc < ClientConnectionSender > > ,
@@ -2181,7 +2254,7 @@ impl ModuleHost {
21812254 args : FunctionArgs ,
21822255 ) -> Result < CallReducerParams , InvalidReducerArguments > {
21832256 let args = args
2184- . into_tuple_for_def ( & module . module_def , reducer_def)
2257+ . into_tuple_for_def ( owning_def , reducer_def)
21852258 . map_err ( InvalidReducerArguments ) ?;
21862259 let caller_connection_id = caller_connection_id. unwrap_or ( ConnectionId :: ZERO ) ;
21872260 Ok ( CallReducerParams {
@@ -2206,10 +2279,10 @@ impl ModuleHost {
22062279 reducer_name : & str ,
22072280 args : FunctionArgs ,
22082281 ) -> Result < ( & ' a ReducerDef , CallReducerParams ) , ReducerCallError > {
2209- let ( reducer_id, reducer_def) = self
2282+ let ( reducer_id, reducer_def, owning_def ) = self
22102283 . info
22112284 . module_def
2212- . reducer_full ( reducer_name)
2285+ . reducer_by_name_with_module ( reducer_name)
22132286 . ok_or ( ReducerCallError :: NoSuchReducer ) ?;
22142287 if let Some ( lifecycle) = reducer_def. lifecycle {
22152288 return Err ( ReducerCallError :: LifecycleReducer ( lifecycle) ) ;
@@ -2222,7 +2295,7 @@ impl ModuleHost {
22222295 Ok ( (
22232296 reducer_def,
22242297 Self :: call_reducer_params (
2225- & self . info ,
2298+ owning_def ,
22262299 caller_identity,
22272300 caller_connection_id,
22282301 client,
@@ -2248,7 +2321,17 @@ impl ModuleHost {
22482321
22492322 fn log_reducer_submit_error ( & self , reducer_name : & str , err : & ReducerCallError ) {
22502323 let log_message = match err {
2251- ReducerCallError :: NoSuchReducer => Some ( no_such_function_log_message ( "reducer" , reducer_name) ) ,
2324+ // Only log NoSuchReducer when the name is also not a known procedure.
2325+ // The HTTP /call/:reducer endpoint falls back to procedure on NoSuchReducer,
2326+ // so a valid procedure name would otherwise incorrectly produce an error log.
2327+ ReducerCallError :: NoSuchReducer => {
2328+ let module_def = & self . info ( ) . module_def ;
2329+ if module_def. procedure_by_name ( reducer_name) . is_none ( ) {
2330+ Some ( no_such_function_log_message ( "reducer" , reducer_name) )
2331+ } else {
2332+ None
2333+ }
2334+ }
22522335 ReducerCallError :: Args ( _) => Some ( args_error_log_message ( "reducer" , reducer_name) ) ,
22532336 _ => None ,
22542337 } ;
@@ -2719,18 +2802,18 @@ impl ModuleHost {
27192802 procedure_name : & str ,
27202803 args : FunctionArgs ,
27212804 ) -> Result < ( & ' a ProcedureDef , CallProcedureParams ) , ProcedureCallError > {
2722- let ( procedure_id, procedure_def) = self
2805+ let ( procedure_id, procedure_def, owning_def ) = self
27232806 . info
27242807 . module_def
2725- . procedure_full ( procedure_name)
2808+ . procedure_by_name_with_module ( procedure_name)
27262809 . ok_or ( ProcedureCallError :: NoSuchProcedure ) ?;
27272810
27282811 if procedure_def. visibility . is_private ( ) && !self . is_database_owner ( caller_identity) {
27292812 return Err ( ProcedureCallError :: NoSuchProcedure ) ;
27302813 }
27312814
27322815 let args = args
2733- . into_tuple_for_def ( & self . info . module_def , procedure_def)
2816+ . into_tuple_for_def ( owning_def , procedure_def)
27342817 . map_err ( InvalidProcedureArguments ) ?;
27352818 let caller_connection_id = caller_connection_id. unwrap_or ( ConnectionId :: ZERO ) ;
27362819
@@ -2838,7 +2921,7 @@ impl ModuleHost {
28382921 view_collector. collect_views ( & mut view_ids) ;
28392922 for view_id in view_ids {
28402923 let st_view_row = tx. lookup_st_view ( view_id) ?;
2841- let view_name = st_view_row. view_name . into ( ) ;
2924+ let view_name = Identifier :: new_assume_valid ( st_view_row. view_name . into ( ) ) ;
28422925 let view_id = st_view_row. view_id ;
28432926 let table_id = st_view_row. table_id . ok_or ( ViewCallError :: TableDoesNotExist ( view_id) ) ?;
28442927 let is_anonymous = st_view_row. is_anonymous ;
@@ -2904,11 +2987,13 @@ impl ModuleHost {
29042987 sender,
29052988 } in tx. views_for_refresh ( ) . cloned ( ) . collect :: < Vec < _ > > ( )
29062989 {
2907- let Some ( view_def) = module_def. get_view_by_id ( fn_ptr, sender. is_none ( ) ) else {
2990+ let Some ( ( view_def, owning_def) ) =
2991+ module_def. get_view_by_global_id_with_module ( fn_ptr, sender. is_none ( ) )
2992+ else {
29082993 outcome = ViewOutcome :: Failed ( format ! ( "view with fn_ptr `{fn_ptr}` not found" ) ) ;
29092994 break ;
29102995 } ;
2911- let args = match FunctionArgs :: Nullary . into_tuple_for_def ( module_def , view_def) {
2996+ let args = match FunctionArgs :: Nullary . into_tuple_for_def ( owning_def , view_def) {
29122997 Ok ( args) => args,
29132998 Err ( err) => {
29142999 outcome = ViewOutcome :: Failed ( format ! ( "failed to build view args: {err}" ) ) ;
@@ -2922,12 +3007,13 @@ impl ModuleHost {
29223007 & view_def. name ,
29233008 view_id,
29243009 table_id,
2925- view_def . fn_ptr ,
3010+ fn_ptr,
29263011 caller,
29273012 sender,
29283013 args,
29293014 view_def. product_type_ref ,
29303015 timestamp,
3016+ owning_def. typespace ( ) . clone ( ) ,
29313017 ) ;
29323018
29333019 // Increment execution stats
@@ -2990,15 +3076,17 @@ impl ModuleHost {
29903076 timestamp : Timestamp ,
29913077 ) -> Result < ( ViewCallResult , bool ) , ViewCallError > {
29923078 let module_def = & instance. common . info ( ) . module_def ;
2993- let view_def = module_def. view ( view_name) . ok_or ( ViewCallError :: NoSuchView ) ?;
2994- let fn_ptr = view_def. fn_ptr ;
3079+ let ( global_fn_ptr, view_def, owning_def) = module_def
3080+ . view_by_name_with_global_fn_ptr ( view_name. as_ref ( ) )
3081+ . ok_or ( ViewCallError :: NoSuchView ) ?;
29953082 let row_type = view_def. product_type_ref ;
29963083 let args = args
2997- . into_tuple_for_def ( module_def , view_def)
3084+ . into_tuple_for_def ( owning_def , view_def)
29983085 . map_err ( InvalidViewArguments ) ?;
29993086
30003087 Ok ( Self :: call_view_inner (
3001- instance, tx, view_name, view_id, table_id, fn_ptr, caller, sender, args, row_type, timestamp,
3088+ instance, tx, view_name, view_id, table_id, global_fn_ptr, caller, sender, args, row_type, timestamp,
3089+ owning_def. typespace ( ) . clone ( ) ,
30023090 ) )
30033091 }
30043092
@@ -3014,6 +3102,7 @@ impl ModuleHost {
30143102 args : ArgsTuple ,
30153103 row_type : AlgebraicTypeRef ,
30163104 timestamp : Timestamp ,
3105+ view_typespace : Typespace ,
30173106 ) -> ( ViewCallResult , bool ) {
30183107 let view_name = name. clone ( ) ;
30193108 let params = CallViewParams {
@@ -3026,6 +3115,7 @@ impl ModuleHost {
30263115 sender,
30273116 args,
30283117 row_type,
3118+ view_typespace,
30293119 } ;
30303120
30313121 instance. common . call_view_with_tx ( tx, params, instance. instance )
0 commit comments