@@ -395,6 +395,41 @@ impl Connection {
395395 Ok ( results)
396396 }
397397
398+ /// Look up a single output of a derivation from the most recent
399+ /// successful buildstep.
400+ pub async fn resolve_drv_output (
401+ & mut self ,
402+ store_dir : & StoreDir ,
403+ drv_path : & StorePath ,
404+ output_name : & OutputName ,
405+ ) -> sqlx:: Result < Option < StorePath > > {
406+ let drv_display = store_dir. display ( drv_path) . to_string ( ) ;
407+ let output_name_str: & str = output_name. as_ref ( ) ;
408+ let row: Option < ( String , ) > = sqlx:: query_as (
409+ r"SELECT o.path
410+ FROM buildsteps s
411+ JOIN buildstepoutputs o
412+ ON s.build = o.build AND s.stepnr = o.stepnr
413+ WHERE s.drvPath = $1
414+ AND o.name = $2
415+ AND o.path IS NOT NULL
416+ AND s.status = 0
417+ ORDER BY s.build DESC
418+ LIMIT 1" ,
419+ )
420+ . bind ( & drv_display)
421+ . bind ( output_name_str)
422+ . fetch_optional ( & mut * self . conn )
423+ . await ?;
424+
425+ row. map ( |( path, ) | {
426+ store_dir
427+ . parse ( & path)
428+ . map_err ( |e| sqlx:: Error :: Decode ( Box :: new ( e) ) )
429+ } )
430+ . transpose ( )
431+ }
432+
398433 #[ tracing:: instrument( skip( self ) , err) ]
399434 pub async fn get_status ( & mut self ) -> sqlx:: Result < Option < serde_json:: Value > > {
400435 Ok (
@@ -1461,4 +1496,142 @@ mod tests {
14611496 ]
14621497 ) ;
14631498 }
1499+
1500+ // -- resolve_drv_output (depth-1) tests ------------------------------------
1501+
1502+ #[ tokio:: test]
1503+ async fn resolve_drv_output_basic ( ) {
1504+ let ( _pg, mut conn) = setup ( ) . await ;
1505+ insert_step ( & mut conn, 1 , 1 , & sp ( "foo.drv" ) ) . await ;
1506+ insert_output ( & mut conn, 1 , 1 , "out" , & sp ( "result" ) ) . await ;
1507+
1508+ let result = conn
1509+ . resolve_drv_output ( & test_store_dir ( ) , & sp ( "foo.drv" ) , & on ( "out" ) )
1510+ . await
1511+ . unwrap ( ) ;
1512+ assert_eq ! ( result, Some ( sp( "result" ) ) ) ;
1513+ }
1514+
1515+ #[ tokio:: test]
1516+ async fn resolve_drv_output_missing ( ) {
1517+ let ( _pg, mut conn) = setup ( ) . await ;
1518+ let result = conn
1519+ . resolve_drv_output ( & test_store_dir ( ) , & sp ( "nonexistent.drv" ) , & on ( "out" ) )
1520+ . await
1521+ . unwrap ( ) ;
1522+ assert_eq ! ( result, None ) ;
1523+ }
1524+
1525+ #[ tokio:: test]
1526+ async fn resolve_drv_output_picks_latest_build ( ) {
1527+ let ( _pg, mut conn) = setup ( ) . await ;
1528+ insert_step ( & mut conn, 1 , 1 , & sp ( "foo.drv" ) ) . await ;
1529+ insert_output ( & mut conn, 1 , 1 , "out" , & sp ( "old-result" ) ) . await ;
1530+ insert_step ( & mut conn, 5 , 1 , & sp ( "foo.drv" ) ) . await ;
1531+ insert_output ( & mut conn, 5 , 1 , "out" , & sp ( "new-result" ) ) . await ;
1532+
1533+ let result = conn
1534+ . resolve_drv_output ( & test_store_dir ( ) , & sp ( "foo.drv" ) , & on ( "out" ) )
1535+ . await
1536+ . unwrap ( ) ;
1537+ assert_eq ! ( result, Some ( sp( "new-result" ) ) ) ;
1538+ }
1539+
1540+ // -- Simulate the Rust-side loop that replaces the recursive SQL ----------
1541+ //
1542+ // These mirror the resolved-step tests from the DB-column approach,
1543+ // but use resolve_drv_output + an in-memory map instead of
1544+ // resolvedDrvPath in the SQL.
1545+
1546+ /// Helper: resolve a chain one level at a time using `resolve_drv_output`,
1547+ /// translating through `resolved_map` between levels.
1548+ async fn resolve_chain_with_map (
1549+ conn : & mut Connection ,
1550+ resolved_map : & std:: collections:: HashMap < StorePath , StorePath > ,
1551+ root : & StorePath ,
1552+ outputs : & [ & OutputName ] ,
1553+ ) -> Option < StorePath > {
1554+ let sd = test_store_dir ( ) ;
1555+ let mut current = root. clone ( ) ;
1556+ for output_name in outputs {
1557+ let translated = resolved_map. get ( & current) . cloned ( ) . unwrap_or ( current) ;
1558+ current = conn
1559+ . resolve_drv_output ( & sd, & translated, output_name)
1560+ . await
1561+ . unwrap ( ) ?;
1562+ }
1563+ Some ( current)
1564+ }
1565+
1566+ /// Depth-1: unresolved.drv was resolved to resolved.drv, which has
1567+ /// the outputs. The in-memory map translates before lookup.
1568+ #[ tokio:: test]
1569+ async fn resolve_with_map_depth_1 ( ) {
1570+ let ( _pg, mut conn) = setup ( ) . await ;
1571+
1572+ // resolved.drv was built successfully
1573+ insert_step ( & mut conn, 2 , 1 , & sp ( "resolved.drv" ) ) . await ;
1574+ insert_output ( & mut conn, 2 , 1 , "out" , & sp ( "result" ) ) . await ;
1575+
1576+ let mut map = std:: collections:: HashMap :: new ( ) ;
1577+ map. insert ( sp ( "unresolved.drv" ) , sp ( "resolved.drv" ) ) ;
1578+
1579+ let result =
1580+ resolve_chain_with_map ( & mut conn, & map, & sp ( "unresolved.drv" ) , & [ & on ( "out" ) ] ) . await ;
1581+ assert_eq ! ( result, Some ( sp( "result" ) ) ) ;
1582+ }
1583+
1584+ /// Depth-2: unresolved.drv was resolved to resolved.drv, whose output
1585+ /// is an intermediate.drv that has the final output.
1586+ #[ tokio:: test]
1587+ async fn resolve_with_map_depth_2 ( ) {
1588+ let ( _pg, mut conn) = setup ( ) . await ;
1589+
1590+ insert_step ( & mut conn, 2 , 1 , & sp ( "resolved.drv" ) ) . await ;
1591+ insert_output ( & mut conn, 2 , 1 , "out" , & sp ( "intermediate.drv" ) ) . await ;
1592+ insert_step ( & mut conn, 3 , 1 , & sp ( "intermediate.drv" ) ) . await ;
1593+ insert_output ( & mut conn, 3 , 1 , "out" , & sp ( "final" ) ) . await ;
1594+
1595+ let mut map = std:: collections:: HashMap :: new ( ) ;
1596+ map. insert ( sp ( "unresolved.drv" ) , sp ( "resolved.drv" ) ) ;
1597+
1598+ let result = resolve_chain_with_map (
1599+ & mut conn,
1600+ & map,
1601+ & sp ( "unresolved.drv" ) ,
1602+ & [ & on ( "out" ) , & on ( "out" ) ] ,
1603+ )
1604+ . await ;
1605+ assert_eq ! ( result, Some ( sp( "final" ) ) ) ;
1606+ }
1607+
1608+ /// Depth-2 where the intermediate result was also resolved:
1609+ /// root.drv.drv (not resolved) → intermediate.drv (resolved) → final
1610+ #[ tokio:: test]
1611+ async fn resolve_with_map_intermediate_resolved ( ) {
1612+ let ( _pg, mut conn) = setup ( ) . await ;
1613+
1614+ // root.drv.drv^out → unresolved-intermediate.drv
1615+ insert_step ( & mut conn, 1 , 1 , & sp ( "root.drv.drv" ) ) . await ;
1616+ insert_output ( & mut conn, 1 , 1 , "out" , & sp ( "unresolved-intermediate.drv" ) ) . await ;
1617+
1618+ // resolved-intermediate.drv^out → final-result
1619+ insert_step ( & mut conn, 2 , 1 , & sp ( "resolved-intermediate.drv" ) ) . await ;
1620+ insert_output ( & mut conn, 2 , 1 , "out" , & sp ( "final-result" ) ) . await ;
1621+
1622+ let mut map = std:: collections:: HashMap :: new ( ) ;
1623+ map. insert (
1624+ sp ( "unresolved-intermediate.drv" ) ,
1625+ sp ( "resolved-intermediate.drv" ) ,
1626+ ) ;
1627+
1628+ let result = resolve_chain_with_map (
1629+ & mut conn,
1630+ & map,
1631+ & sp ( "root.drv.drv" ) ,
1632+ & [ & on ( "out" ) , & on ( "out" ) ] ,
1633+ )
1634+ . await ;
1635+ assert_eq ! ( result, Some ( sp( "final-result" ) ) ) ;
1636+ }
14641637}
0 commit comments