@@ -45,6 +45,7 @@ use si_frontend_mv_types::{
4545 management:: ManagementFuncKind ,
4646 prop_schema:: PropSchemaV1 as CachedPropSchemaV1 ,
4747} ;
48+ use si_pkg:: SiPkgError ;
4849use thiserror:: Error ;
4950use utoipa:: {
5051 self ,
@@ -67,6 +68,7 @@ pub mod list_schemas;
6768pub mod search_schemas;
6869pub mod unlock_schema;
6970pub mod update_schema_variant;
71+ pub mod upgrade_schema;
7072
7173#[ remain:: sorted]
7274#[ derive( Debug , Error ) ]
@@ -87,8 +89,12 @@ pub enum SchemaError {
8789 MaterializedViews ( #[ from] dal_materialized_views:: Error ) ,
8890 #[ error( "schema missing asset func id: {0}" ) ]
8991 MissingVariantFunc ( SchemaVariantId ) ,
92+ #[ error( "module error: {0}" ) ]
93+ Module ( #[ from] dal:: module:: ModuleError ) ,
9094 #[ error( "changes not permitted on HEAD change set" ) ]
9195 NotPermittedOnHead ,
96+ #[ error( "pkg error: {0}" ) ]
97+ Pkg ( #[ from] dal:: pkg:: PkgError ) ,
9298 #[ error( "prop error: {0}" ) ]
9399 Prop ( #[ from] Box < PropError > ) ,
94100 #[ error( "schema error: {0}" ) ]
@@ -103,10 +109,18 @@ pub enum SchemaError {
103109 SchemaVariantNotFound ( SchemaVariantId ) ,
104110 #[ error( "schema variant {0} not a variant for the schema {1} error" ) ]
105111 SchemaVariantNotMemberOfSchema ( SchemaId , SchemaVariantId ) ,
112+ #[ error( "sipkg error: {0}" ) ]
113+ SiPkg ( #[ from] SiPkgError ) ,
106114 #[ error( "slow runtime error: {0}" ) ]
107115 SlowRuntime ( #[ from] dal:: slow_rt:: SlowRuntimeError ) ,
108116 #[ error( "transactions error: {0}" ) ]
109117 Transactions ( #[ from] TransactionsError ) ,
118+ #[ error( "Cannot upgrade schema {0} - has unlocked variants that need to be locked first" ) ]
119+ UnlockedVariantFoundForSchema ( SchemaId ) ,
120+ #[ error( "No cached module found for schema {0}" ) ]
121+ UpgradableModuleNotFound ( SchemaId ) ,
122+ #[ error( "No schema variants imported for schema {0}" ) ]
123+ UpgradeFailed ( SchemaId ) ,
110124 #[ error( "validation error: {0}" ) ]
111125 Validation ( String ) ,
112126 #[ error( "variant authoring error: {0}" ) ]
@@ -144,6 +158,10 @@ impl crate::service::v1::common::ErrorIntoResponse for SchemaError {
144158 SchemaError :: SchemaNotFound ( _) => ( StatusCode :: NOT_FOUND , self . to_string ( ) ) ,
145159 SchemaError :: SchemaNotFoundByName ( _) => ( StatusCode :: NOT_FOUND , self . to_string ( ) ) ,
146160 SchemaError :: SchemaVariantNotFound ( _) => ( StatusCode :: NOT_FOUND , self . to_string ( ) ) ,
161+ SchemaError :: UpgradableModuleNotFound ( _) => ( StatusCode :: NOT_FOUND , self . to_string ( ) ) ,
162+ SchemaError :: UnlockedVariantFoundForSchema ( _) => {
163+ ( StatusCode :: PRECONDITION_FAILED , self . to_string ( ) )
164+ }
147165 SchemaError :: SchemaVariantNotMemberOfSchema ( _, _) => {
148166 ( StatusCode :: PRECONDITION_REQUIRED , self . to_string ( ) )
149167 }
@@ -187,6 +205,7 @@ pub fn routes() -> Router<AppState> {
187205 Router :: new ( )
188206 . route ( "/" , get ( get_schema:: get_schema) )
189207 . route ( "/unlock" , post ( unlock_schema:: unlock_schema) )
208+ . route ( "/upgrade" , post ( upgrade_schema:: upgrade_schema) )
190209 . nest (
191210 "/variant" ,
192211 Router :: new ( )
@@ -632,6 +651,8 @@ pub struct GetSchemaV1Response {
632651 pub default_variant_id : SchemaVariantId ,
633652 #[ schema( value_type = Vec <String >, example = json!( [ "01H9ZQD35JPMBGHH69BT0Q79VZ" , "01H9ZQD35JPMBGHH69BT0Q79VY" ] ) ) ]
634653 pub variant_ids : Vec < SchemaVariantId > ,
654+ #[ schema( value_type = Option <bool >) ]
655+ pub upgrade_available : Option < bool > ,
635656}
636657
637658#[ derive( Serialize , Debug , ToSchema ) ]
@@ -698,21 +719,58 @@ pub struct SchemaResponse {
698719 pub installed : bool ,
699720}
700721
722+ /// Check if an upgrade is available for a given schema.
723+ ///
724+ /// Returns:
725+ /// - `Some(true)` if an upgrade is available (installed hash is in past hashes)
726+ /// - `Some(false)` if no upgrade is available (hashes match or no newer version)
727+ /// - `None` if upgrade check is not applicable (no cached module or no installed module)
728+ pub async fn check_schema_upgrade_available (
729+ ctx : & DalContext ,
730+ schema_id : SchemaId ,
731+ ) -> SchemaResult < Option < bool > > {
732+ // Get the latest cached module
733+ let Some ( cached_module) = CachedModule :: find_latest_for_schema_id ( ctx, schema_id) . await ? else {
734+ return Ok ( None ) ;
735+ } ;
736+
737+ // Get the installed module
738+ let Some ( installed_module) =
739+ dal:: module:: Module :: find_for_module_schema_id ( ctx, schema_id. into ( ) ) . await ?
740+ else {
741+ return Ok ( None ) ;
742+ } ;
743+
744+ // Check if hashes differ
745+ if cached_module. latest_hash == installed_module. root_hash ( ) {
746+ return Ok ( Some ( false ) ) ;
747+ }
748+
749+ // Check if installed hash is in past hashes (means upgrade available)
750+ let past_hashes = CachedModule :: list_for_schema_id ( ctx, schema_id)
751+ . await ?
752+ . iter ( )
753+ . map ( |cm| cm. latest_hash . to_owned ( ) )
754+ . collect :: < HashSet < String > > ( ) ;
755+
756+ Ok ( Some ( past_hashes. contains ( installed_module. root_hash ( ) ) ) )
757+ }
758+
701759pub async fn get_full_schema_list ( ctx : & DalContext ) -> SchemaResult < Vec < SchemaResponse > > {
702760 let schema_ids = dal:: Schema :: list_ids ( ctx) . await ?;
703761 let installed_schema_ids: HashSet < _ > = schema_ids. iter ( ) . collect ( ) ;
704762
705763 // Get cached modules with their metadata
706764 let cached_modules = CachedModule :: latest_modules ( ctx) . await ?;
707- // Create a map of schema ID to cached module data
708765 let mut cached_module_map: HashMap < SchemaId , CachedModule > = HashMap :: new ( ) ;
709766 for module in cached_modules {
710767 cached_module_map. insert ( module. schema_id , module) ;
711768 }
712769
713770 // Combine both sources to create a complete list
714771 let mut all_schemas: Vec < SchemaResponse > = Vec :: new ( ) ;
715- // First add installed schemas from Schema::list_ids
772+
773+ // First add installed schemas
716774 for schema_id in & schema_ids {
717775 if let Some ( module) = cached_module_map. get ( schema_id) {
718776 // Schema is both installed and in cache
0 commit comments