@@ -348,6 +348,9 @@ struct CreateWorkspaceFork {
348348 color : Option < String > ,
349349 #[ serde( default ) ]
350350 datatable_behaviors : Option < HashMap < String , DataTableForkBehavior > > ,
351+ /// For resource-type datatables: map of datatable name -> target resource path
352+ #[ serde( default ) ]
353+ datatable_target_resources : Option < HashMap < String , String > > ,
351354}
352355
353356#[ derive( Deserialize ) ]
@@ -1145,24 +1148,41 @@ async fn list_ducklakes(
11451148 Ok ( Json ( ducklakes) )
11461149}
11471150
1151+ #[ derive( Serialize ) ]
1152+ struct DataTableInfo {
1153+ name : String ,
1154+ resource_type : String ,
1155+ resource_path : String ,
1156+ }
1157+
11481158async fn list_datatables (
11491159 _authed : ApiAuthed ,
11501160 Extension ( db) : Extension < DB > ,
11511161 Path ( w_id) : Path < String > ,
1152- ) -> JsonResult < Vec < String > > {
1153- let datatables = sqlx:: query_scalar!(
1154- r#"
1155- SELECT jsonb_object_keys(ws.datatable->'datatables') AS datatable_name
1156- FROM workspace_settings ws
1157- WHERE ws.workspace_id = $1
1158- "# ,
1162+ ) -> JsonResult < Vec < DataTableInfo > > {
1163+ let datatable_config = sqlx:: query_scalar!(
1164+ "SELECT datatable FROM workspace_settings WHERE workspace_id = $1" ,
11591165 & w_id
11601166 )
1161- . fetch_all ( & db)
1162- . await ?
1163- . into_iter ( )
1164- . filter_map ( |s| s)
1165- . collect ( ) ;
1167+ . fetch_one ( & db)
1168+ . await ?;
1169+
1170+ let datatables: Vec < DataTableInfo > = match datatable_config {
1171+ Some ( config) => {
1172+ let settings: DataTableSettings = serde_json:: from_value ( config)
1173+ . unwrap_or ( DataTableSettings { datatables : HashMap :: new ( ) } ) ;
1174+ settings
1175+ . datatables
1176+ . into_iter ( )
1177+ . map ( |( name, dt) | DataTableInfo {
1178+ name,
1179+ resource_type : dt. database . resource_type . as_ref ( ) . to_string ( ) ,
1180+ resource_path : dt. database . resource_path ,
1181+ } )
1182+ . collect ( )
1183+ }
1184+ None => vec ! [ ] ,
1185+ } ;
11661186
11671187 Ok ( Json ( datatables) )
11681188}
@@ -1643,6 +1663,7 @@ async fn fork_all_datatables(
16431663 source_workspace_id : & str ,
16441664 target_workspace_id : & str ,
16451665 datatable_behaviors : & Option < HashMap < String , DataTableForkBehavior > > ,
1666+ datatable_target_resources : & Option < HashMap < String , String > > ,
16461667) -> Result < ( ) > {
16471668 if !target_workspace_id. starts_with ( "wm-fork" ) {
16481669 return Err ( Error :: BadRequest (
@@ -1671,19 +1692,49 @@ async fn fork_all_datatables(
16711692 . and_then ( |m| m. get ( name) . copied ( ) )
16721693 . unwrap_or ( DataTableForkBehavior :: SchemaOnly ) ;
16731694
1674- if behavior == DataTableForkBehavior :: KeepOriginal {
1695+ // Resource-type datatables: only action is replacing the resource path if a target is provided
1696+ if dt. database . resource_type != DataTableCatalogResourceType :: Instance {
1697+ let target_resource = datatable_target_resources
1698+ . as_ref ( )
1699+ . and_then ( |m| m. get ( name) ) ;
1700+
1701+ if let Some ( target_path) = target_resource {
1702+ let new_datatable = DataTable {
1703+ database : DataTableDatabase {
1704+ resource_type : DataTableCatalogResourceType :: Postgresql ,
1705+ resource_path : target_path. clone ( ) ,
1706+ } ,
1707+ } ;
1708+ let datatable_value: serde_json:: Value = serde_json:: to_value ( & new_datatable)
1709+ . map_err ( |err| Error :: internal_err ( err. to_string ( ) ) ) ?;
1710+
1711+ sqlx:: query!(
1712+ r#"UPDATE workspace_settings
1713+ SET datatable = jsonb_set(
1714+ COALESCE(datatable, '{"datatables":{}}'::jsonb),
1715+ ARRAY['datatables', $1],
1716+ $2
1717+ )
1718+ WHERE workspace_id = $3"# ,
1719+ name,
1720+ datatable_value,
1721+ target_workspace_id
1722+ )
1723+ . execute ( db)
1724+ . await ?;
1725+
1726+ tracing:: info!(
1727+ "Forked resource datatable '{}': replaced resource with '{}'" ,
1728+ name,
1729+ target_path
1730+ ) ;
1731+ }
1732+ // If no target resource provided, config already copied as-is (keep original)
16751733 continue ;
16761734 }
16771735
1678- // Resource-type datatables cannot be forked (would require creating an instance DB
1679- // from an external resource). Skip gracefully.
1680- if dt. database . resource_type != DataTableCatalogResourceType :: Instance {
1681- tracing:: warn!(
1682- "Skipping fork of datatable '{}' in '{}': resource type '{}' is not supported for forking" ,
1683- name,
1684- source_workspace_id,
1685- dt. database. resource_type. as_ref( )
1686- ) ;
1736+ // Instance-type datatables below
1737+ if behavior == DataTableForkBehavior :: KeepOriginal {
16871738 continue ;
16881739 }
16891740
@@ -3926,6 +3977,7 @@ async fn create_workspace_fork(
39263977 & parent_workspace_id,
39273978 & forked_id,
39283979 & nw. datatable_behaviors ,
3980+ & nw. datatable_target_resources ,
39293981 )
39303982 . await ?;
39313983
0 commit comments