@@ -18,8 +18,8 @@ use graph::{
1818 components:: {
1919 server:: index_node:: VersionInfo ,
2020 store:: {
21- self , BlockPtrForNumber , BlockStore , DeploymentLocator , EnsLookup as EnsLookupTrait ,
22- PruneReporter , PruneRequest , SubgraphFork ,
21+ self , BlockPtrForNumber , BlockStore , DeploymentLocator , DeploymentSchemaVersion ,
22+ EnsLookup as EnsLookupTrait , PruneReporter , PruneRequest , SubgraphFork ,
2323 } ,
2424 } ,
2525 data:: {
@@ -42,7 +42,9 @@ use graph::{derive::CheapClone, futures03::future::join_all, prelude::alloy::pri
4242use crate :: {
4343 catalog:: Catalog ,
4444 deployment:: { OnSync , SubgraphHealth } ,
45- primary:: { self , DeploymentId , Mirror as PrimaryMirror , Primary , Site } ,
45+ primary:: {
46+ self , DeploymentId , Mirror as PrimaryMirror , Primary , RestoreAction , RestoreMode , Site ,
47+ } ,
4648 relational:: {
4749 self ,
4850 index:: { IndexList , Method } ,
@@ -735,6 +737,35 @@ impl Inner {
735737 }
736738 }
737739
740+ /// Determine the target node for a deployment using the configured
741+ /// deployment rules, ignoring the shard selection. Returns an error
742+ /// if no rule matches.
743+ async fn node_for_deployment (
744+ & self ,
745+ name : & SubgraphName ,
746+ network : & str ,
747+ ) -> Result < NodeId , StoreError > {
748+ let placement = self
749+ . placer
750+ . place ( name. as_str ( ) , network)
751+ . map_err ( |msg| internal_error ! ( "illegal indexer name in deployment rule: {}" , msg) ) ?;
752+
753+ match placement {
754+ Some ( ( _, nodes) ) if !nodes. is_empty ( ) => {
755+ if nodes. len ( ) == 1 {
756+ Ok ( nodes. into_iter ( ) . next ( ) . unwrap ( ) )
757+ } else {
758+ let mut pconn = self . primary_conn ( ) . await ?;
759+ // unwrap: nodes is not empty
760+ Ok ( pconn. least_assigned_node ( & nodes) . await ?. unwrap ( ) )
761+ }
762+ }
763+ _ => Err ( StoreError :: InternalError (
764+ "no deployment rule matches this deployment" . into ( ) ,
765+ ) ) ,
766+ }
767+ }
768+
738769 pub async fn copy_deployment (
739770 & self ,
740771 src : & DeploymentLocator ,
@@ -1446,6 +1477,114 @@ impl Inner {
14461477
14471478 store. dump ( site, directory) . await
14481479 }
1480+
1481+ pub async fn restore (
1482+ & self ,
1483+ dir : & std:: path:: Path ,
1484+ shard : Shard ,
1485+ name : Option < SubgraphName > ,
1486+ mode : RestoreMode ,
1487+ ) -> Result < ( ) , StoreError > {
1488+ use crate :: relational:: dump:: Metadata ;
1489+
1490+ let metadata_path = dir. join ( "metadata.json" ) ;
1491+ let metadata = Metadata :: from_file ( & metadata_path) ?;
1492+
1493+ // Validate that the target shard exists before making any DB changes
1494+ self . stores
1495+ . get ( & shard)
1496+ . ok_or_else ( || StoreError :: UnknownShard ( shard. to_string ( ) ) ) ?;
1497+
1498+ // Resolve the subgraph name for deployment rule matching. If not
1499+ // supplied, look up an existing name from the DB; error if none.
1500+ let name = match name {
1501+ Some ( n) => n,
1502+ None => {
1503+ let names = self
1504+ . mirror
1505+ . subgraphs_by_deployment_hash ( metadata. deployment . as_str ( ) )
1506+ . await ?;
1507+ let ( name, _) = names. into_iter ( ) . next ( ) . ok_or_else ( || {
1508+ StoreError :: InternalError (
1509+ "no subgraph name found for this deployment; use --name to specify one"
1510+ . into ( ) ,
1511+ )
1512+ } ) ?;
1513+ SubgraphName :: new ( name) . map_err ( |n| {
1514+ StoreError :: InternalError ( format ! ( "invalid subgraph name `{n}` in database" ) )
1515+ } ) ?
1516+ }
1517+ } ;
1518+
1519+ // Use deployment rules to determine which node should index this
1520+ // deployment. The rules also return candidate shards, but we ignore
1521+ // those since the shard is user-specified for restore.
1522+ let node = self . node_for_deployment ( & name, & metadata. network ) . await ?;
1523+
1524+ let mut pconn = self . primary_conn ( ) . await ?;
1525+ let action = pconn
1526+ . plan_restore ( & shard, & metadata. deployment , & mode)
1527+ . await ?;
1528+
1529+ // Determine schema_version the same way allocate_site does
1530+ let schema_version = match metadata. graft_base . as_ref ( ) {
1531+ Some ( graft_base) => {
1532+ let base_site = pconn. find_active_site ( graft_base) . await ?. ok_or_else ( || {
1533+ StoreError :: DeploymentNotFound ( "graft_base not found" . to_string ( ) )
1534+ } ) ?;
1535+ base_site. schema_version
1536+ }
1537+ None => DeploymentSchemaVersion :: LATEST ,
1538+ } ;
1539+
1540+ let site = match action {
1541+ RestoreAction :: Create { active } => {
1542+ pconn
1543+ . create_site (
1544+ shard,
1545+ metadata. deployment . clone ( ) ,
1546+ metadata. network . clone ( ) ,
1547+ schema_version,
1548+ active,
1549+ )
1550+ . await ?
1551+ }
1552+ RestoreAction :: Replace { existing } => {
1553+ let was_active = existing. active ;
1554+ let existing = Arc :: new ( existing) ;
1555+ let store = self . for_site ( & existing) ?;
1556+ store. drop_deployment ( & existing) . await ?;
1557+ pconn. drop_site ( & existing) . await ?;
1558+ // Drop and re-acquire the primary connection to avoid pool
1559+ // deadlock: drop_deployment above used a separate connection
1560+ // from the same pool, and create_site below needs one too.
1561+ drop ( pconn) ;
1562+ let mut pconn = self . primary_conn ( ) . await ?;
1563+ pconn
1564+ . create_site (
1565+ shard,
1566+ metadata. deployment . clone ( ) ,
1567+ metadata. network . clone ( ) ,
1568+ schema_version,
1569+ was_active,
1570+ )
1571+ . await ?
1572+ }
1573+ } ;
1574+
1575+ let site = Arc :: new ( site) ;
1576+ let store = self . for_site ( & site) ?;
1577+ store. restore ( site. cheap_clone ( ) , dir, & metadata) . await ?;
1578+
1579+ // Assign the restored deployment to the node determined by
1580+ // deployment rules
1581+ let mut pconn = self . primary_conn ( ) . await ?;
1582+ let changes = pconn. assign_subgraph ( & site, & node) . await ?;
1583+ let event = StoreEvent :: new ( changes) ;
1584+ pconn. send_store_event ( & self . sender , & event) . await ?;
1585+
1586+ Ok ( ( ) )
1587+ }
14491588}
14501589
14511590const STATE_ENS_NOT_CHECKED : u8 = 0 ;
0 commit comments