@@ -472,13 +472,17 @@ async fn connect_overlay(
472472 Ok ( pg)
473473}
474474
475+ /// Captured FDW state from the overlay database.
476+ struct FdwState {
477+ has_extension : bool ,
478+ server_host : Option < String > ,
479+ server_dbname : Option < String > ,
480+ has_user_mapping : bool ,
481+ schemas_with_fts : HashSet < String > ,
482+ }
483+
475484/// Query the overlay database to determine the current FDW state.
476- ///
477- /// Returns `(has_extension, server_host_if_exists, has_user_mapping, schemas_with_foreign_tables)`.
478- async fn check_fdw_state (
479- pg : & tokio_postgres:: Client ,
480- server_name : & str ,
481- ) -> Result < ( bool , Option < String > , bool , HashSet < String > ) > {
485+ async fn check_fdw_state ( pg : & tokio_postgres:: Client , server_name : & str ) -> Result < FdwState > {
482486 let has_extension = pg
483487 . query_opt (
484488 "SELECT 1 FROM pg_extension WHERE extname = 'postgres_fdw'" ,
@@ -488,16 +492,32 @@ async fn check_fdw_state(
488492 . is_some ( ) ;
489493 debug ! ( has_extension, "checked postgres_fdw extension" ) ;
490494
491- let server_host : Option < String > = pg
492- . query_opt (
493- "SELECT option_value FROM pg_options_to_table( \
495+ let server_opts : Vec < ( String , String ) > = pg
496+ . query (
497+ "SELECT option_name, option_value FROM pg_options_to_table( \
494498 (SELECT srvoptions FROM pg_foreign_server WHERE srvname = $1) \
495- ) WHERE option_name = 'host' ",
499+ )",
496500 & [ & server_name] ,
497501 )
498502 . await ?
499- . map ( |row| row. get ( 0 ) ) ;
500- debug ! ( server = server_name, host = ?server_host, "checked FDW server" ) ;
503+ . iter ( )
504+ . map ( |row| ( row. get ( 0 ) , row. get ( 1 ) ) )
505+ . collect ( ) ;
506+
507+ let server_host = server_opts
508+ . iter ( )
509+ . find ( |( k, _) | k == "host" )
510+ . map ( |( _, v) | v. clone ( ) ) ;
511+ let server_dbname = server_opts
512+ . iter ( )
513+ . find ( |( k, _) | k == "dbname" )
514+ . map ( |( _, v) | v. clone ( ) ) ;
515+ debug ! (
516+ server = server_name,
517+ host = ?server_host,
518+ dbname = ?server_dbname,
519+ "checked FDW server options"
520+ ) ;
501521
502522 let has_user_mapping = pg
503523 . query_opt (
@@ -526,12 +546,13 @@ async fn check_fdw_state(
526546 "checked schemas with foreign tables"
527547 ) ;
528548
529- Ok ( (
549+ Ok ( FdwState {
530550 has_extension,
531551 server_host,
552+ server_dbname,
532553 has_user_mapping,
533554 schemas_with_fts,
534- ) )
555+ } )
535556}
536557
537558/// Find all FDW servers owned by this operator (matching the `fdw_` prefix)
@@ -566,13 +587,62 @@ async fn drop_stale_fdw_servers(
566587 Ok ( ( ) )
567588}
568589
590+ /// Connect to the restore's `postgres` database and find the largest
591+ /// non-system database by size. This is the database whose schemas we
592+ /// import via FDW into the overlay's `app` database.
593+ async fn discover_restore_database (
594+ restore_host : & str ,
595+ fdw_user : & str ,
596+ fdw_password : & str ,
597+ ) -> Result < String > {
598+ let connstr = format ! (
599+ "host={restore_host} port=5432 dbname=postgres user={fdw_user} password={fdw_password} connect_timeout=10" ,
600+ ) ;
601+ debug ! (
602+ host = restore_host,
603+ "connecting to restore postgres database for database discovery"
604+ ) ;
605+ let ( pg, conn) = tokio_postgres:: connect ( & connstr, NoTls ) . await ?;
606+ tokio:: spawn ( async move {
607+ if let Err ( e) = conn. await {
608+ warn ! ( error = %e, "restore database connection error during discovery" ) ;
609+ }
610+ } ) ;
611+
612+ let row = pg
613+ . query_opt (
614+ "SELECT datname FROM pg_database \
615+ WHERE datname NOT IN ('postgres', 'template0', 'template1') \
616+ ORDER BY pg_database_size(datname) DESC \
617+ LIMIT 1",
618+ & [ ] ,
619+ )
620+ . await ?;
621+
622+ match row {
623+ Some ( r) => {
624+ let name: String = r. get ( 0 ) ;
625+ info ! (
626+ host = restore_host,
627+ database = %name,
628+ "discovered main database in restore by size"
629+ ) ;
630+ Ok ( name)
631+ }
632+ None => Err ( Error :: MissingField (
633+ "no non-system databases found in restore" . into ( ) ,
634+ ) ) ,
635+ }
636+ }
637+
569638/// Resolve which schemas to import via FDW.
570639///
571640/// Uses the explicit `schema_mapping` from the spec if present, otherwise
572641/// discovers user schemas from the restore database.
573642async fn resolve_fdw_schemas (
574643 replica : & PostgresPhysicalReplica ,
575644 restore_host : & str ,
645+ restore_dbname : & str ,
576646 fdw_user : & str ,
577647 fdw_password : & str ,
578648 replica_name : & str ,
@@ -599,13 +669,16 @@ async fn resolve_fdw_schemas(
599669 info ! (
600670 replica = replica_name,
601671 restore_host = restore_host,
672+ restore_dbname = restore_dbname,
602673 "no explicit schema mapping, discovering schemas from restore database"
603674 ) ;
604675 let restore_connstr = format ! (
605- "host={restore_host} port=5432 dbname=app user={fdw_user} password={fdw_password} connect_timeout=10" ,
676+ "host={restore_host} port=5432 dbname={} user={fdw_user} password={fdw_password} connect_timeout=10" ,
677+ restore_dbname,
606678 ) ;
607679 debug ! (
608680 host = restore_host,
681+ dbname = restore_dbname,
609682 "connecting to restore database for schema discovery"
610683 ) ;
611684 let ( restore_pg, restore_conn) = tokio_postgres:: connect ( & restore_connstr, NoTls ) . await ?;
@@ -678,27 +751,33 @@ pub async fn reconcile_fdw(
678751
679752 let overlay_pg = connect_overlay ( & cluster_name, namespace, & su_secret) . await ?;
680753
754+ // Discover the main database in the restore (largest by size)
755+ let restore_dbname = discover_restore_database ( & restore_host, & fdw_user, & fdw_password) . await ?;
756+
681757 // Check current state
682- let ( has_extension, server_host, has_user_mapping, schemas_with_fts) =
683- check_fdw_state ( & overlay_pg, & server_name) . await ?;
758+ let state = check_fdw_state ( & overlay_pg, & server_name) . await ?;
684759
685- let server_host_correct = server_host. as_deref ( ) == Some ( & restore_host) ;
760+ let server_host_correct = state. server_host . as_deref ( ) == Some ( restore_host. as_str ( ) ) ;
761+ let server_dbname_correct = state. server_dbname . as_deref ( ) == Some ( restore_dbname. as_str ( ) ) ;
686762
687763 info ! (
688764 replica = %replica_name,
689- has_extension,
690- server_exists = server_host. is_some( ) ,
765+ has_extension = state . has_extension ,
766+ server_exists = state . server_host. is_some( ) ,
691767 server_host_correct,
692- has_user_mapping,
693- foreign_table_schemas = schemas_with_fts. len( ) ,
768+ server_dbname = ?state. server_dbname,
769+ expected_dbname = %restore_dbname,
770+ server_dbname_correct,
771+ has_user_mapping = state. has_user_mapping,
772+ foreign_table_schemas = state. schemas_with_fts. len( ) ,
694773 "current FDW state in overlay database"
695774 ) ;
696775
697776 // Drop stale servers from previous restores
698777 drop_stale_fdw_servers ( & overlay_pg, & server_name, & replica_name) . await ?;
699778
700779 // Ensure _pgro schema and extension
701- if !has_extension {
780+ if !state . has_extension {
702781 info ! ( replica = %replica_name, "creating _pgro schema and postgres_fdw extension" ) ;
703782 overlay_pg
704783 . batch_execute ( "CREATE SCHEMA IF NOT EXISTS _pgro" )
@@ -710,45 +789,68 @@ pub async fn reconcile_fdw(
710789 debug ! ( replica = %replica_name, "postgres_fdw extension already present" ) ;
711790 }
712791
713- // Ensure FDW server with correct host
714- if server_host. is_none ( ) {
792+ // Ensure FDW server with correct host and dbname
793+ if state . server_host . is_none ( ) {
715794 info ! (
716795 replica = %replica_name,
717796 server = %server_name,
718797 host = %restore_host,
798+ dbname = %restore_dbname,
719799 "creating FDW server"
720800 ) ;
721801 overlay_pg
722802 . batch_execute ( & format ! (
723803 "CREATE SERVER {server_name} FOREIGN DATA WRAPPER postgres_fdw \
724- OPTIONS (host {}, port '5432', dbname 'postgres')",
725- quote_literal( & restore_host) ,
726- ) )
727- . await ?;
728- } else if !server_host_correct {
729- info ! (
730- replica = %replica_name,
731- server = %server_name,
732- old_host = ?server_host,
733- new_host = %restore_host,
734- "updating FDW server host"
735- ) ;
736- overlay_pg
737- . batch_execute ( & format ! (
738- "ALTER SERVER {server_name} OPTIONS (SET host {})" ,
804+ OPTIONS (host {}, port '5432', dbname {})",
739805 quote_literal( & restore_host) ,
806+ quote_literal( & restore_dbname) ,
740807 ) )
741808 . await ?;
742809 } else {
743- debug ! (
744- replica = %replica_name,
745- server = %server_name,
746- "FDW server already correct"
747- ) ;
810+ // Server exists — fix any options that are wrong
811+ let mut alter_parts = Vec :: new ( ) ;
812+ if !server_host_correct {
813+ info ! (
814+ replica = %replica_name,
815+ server = %server_name,
816+ old_host = ?state. server_host,
817+ new_host = %restore_host,
818+ "FDW server host needs update"
819+ ) ;
820+ alter_parts. push ( format ! ( "SET host {}" , quote_literal( & restore_host) ) ) ;
821+ }
822+ if !server_dbname_correct {
823+ info ! (
824+ replica = %replica_name,
825+ server = %server_name,
826+ old_dbname = ?state. server_dbname,
827+ new_dbname = %restore_dbname,
828+ "FDW server dbname needs update"
829+ ) ;
830+ let verb = if state. server_dbname . is_some ( ) {
831+ "SET"
832+ } else {
833+ "ADD"
834+ } ;
835+ alter_parts. push ( format ! ( "{verb} dbname {}" , quote_literal( & restore_dbname) ) ) ;
836+ }
837+ if alter_parts. is_empty ( ) {
838+ debug ! (
839+ replica = %replica_name,
840+ server = %server_name,
841+ "FDW server already correct"
842+ ) ;
843+ } else {
844+ let opts = alter_parts. join ( ", " ) ;
845+ debug ! ( server = %server_name, alter = %opts, "altering FDW server options" ) ;
846+ overlay_pg
847+ . batch_execute ( & format ! ( "ALTER SERVER {server_name} OPTIONS ({opts})" ) )
848+ . await ?;
849+ }
748850 }
749851
750852 // Ensure user mapping (always recreate to pick up credential changes)
751- if has_user_mapping {
853+ if state . has_user_mapping {
752854 debug ! ( server = %server_name, "dropping existing user mapping before recreation" ) ;
753855 overlay_pg
754856 . batch_execute ( & format ! (
@@ -770,6 +872,7 @@ pub async fn reconcile_fdw(
770872 let schemas = resolve_fdw_schemas (
771873 replica,
772874 & restore_host,
875+ & restore_dbname,
773876 & fdw_user,
774877 & fdw_password,
775878 & replica_name,
@@ -778,7 +881,7 @@ pub async fn reconcile_fdw(
778881
779882 let mut imported_count = 0u32 ;
780883 for ( remote, local) in & schemas {
781- if schemas_with_fts. contains ( local) {
884+ if state . schemas_with_fts . contains ( local) {
782885 debug ! (
783886 local = %local,
784887 remote = %remote,
0 commit comments