This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 2dbf4cf35dc [v3-3-test] Reuse the explicit dag_run join on the 
previous-TI lookup (#72811) (#72944)
2dbf4cf35dc is described below

commit 2dbf4cf35dcdb6c9e2693ffd890cc925afda5acc
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 11 17:24:38 2026 +0530

    [v3-3-test] Reuse the explicit dag_run join on the previous-TI lookup 
(#72811) (#72944)
    
    * Reuse the explicit dag_run join on the previous-TI lookup
    
    The previous-TI query in ``get_previous_task_instance`` explicitly joins
    ``dag_run`` for the ``ORDER BY dag_run.logical_date`` and asked for a second
    ``joinedload(TI.dag_run)`` for hydration. ``TaskInstance.dag_run`` is 
already
    ``lazy="joined"`` on the mapper, so the eager-load actually attached a 
parallel
    ``JOIN dag_run AS dag_run_1`` next to the explicit join. Both joins pull the
    same rows over the same FK pair.
    
    Switch to ``contains_eager(TI.dag_run)`` so the eager-load reuses the 
explicit
    join instead of adding a second one, and add a regression that fails when 
the
    compiled SQL references ``dag_run`` more than once in the FROM clause.
    
    * Drop unused load_only import
    
    * Assert the eager-load claim by watching for stray dag_run SELECTs
    
    * Swap the stray-select capture for a request-level query count
    (cherry picked from commit d076a57dcb24303c69d8505ea4816417003ffb34)
    
    Co-authored-by: Pierre Jeambrun <[email protected]>
---
 .../api_fastapi/execution_api/routes/task_instances.py |  4 ++--
 .../execution_api/versions/head/test_task_instances.py | 18 ++++++++++++++----
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py 
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
index f8b8d7e7de1..a7eef46571b 100644
--- 
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
+++ 
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
@@ -36,7 +36,7 @@ from pydantic import JsonValue
 from sqlalchemy import and_, func, or_, tuple_, update
 from sqlalchemy.engine import CursorResult
 from sqlalchemy.exc import DataError, NoResultFound, SQLAlchemyError
-from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import contains_eager, joinedload
 from sqlalchemy.sql import select
 from structlog.contextvars import bind_contextvars
 
@@ -1194,7 +1194,7 @@ def get_previous_task_instance(
     query = (
         select(TI)
         .join(DR, (TI.dag_id == DR.dag_id) & (TI.run_id == DR.run_id))
-        .options(joinedload(TI.dag_run))
+        .options(contains_eager(TI.dag_run).load_only(DR.logical_date))
         .where(TI.dag_id == dag_id, TI.task_id == task_id, TI.map_index == 
map_index)
         .order_by(DR.logical_date.desc())
     )
diff --git 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
index 7d4d5cc7433..9796399f8ca 100644
--- 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
+++ 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
@@ -58,7 +58,7 @@ from airflow.sdk import Asset, TaskGroup, TriggerRule, task, 
task_group
 from airflow.state.metastore import MetastoreBackend
 from airflow.utils.state import DagRunState, State, TaskInstanceState, 
TerminalTIState
 
-from tests_common.test_utils.asserts import capture_orm_selects
+from tests_common.test_utils.asserts import assert_queries_count, 
capture_orm_selects
 from tests_common.test_utils.config import conf_vars
 from tests_common.test_utils.db import (
     clear_db_assets,
@@ -3747,7 +3747,8 @@ class TestGetPreviousTI:
         assert data["state"] == State.SUCCESS
 
     def test_get_previous_ti_query_is_bounded(self, client, session, 
create_task_instance):
-        """The single-row previous-TI lookup must ask the DB for one row."""
+        """The single-row previous-TI lookup must ask the DB for one row, join 
``dag_run`` once,
+        and surface the eager-loaded ``logical_date`` in the response."""
         for i in range(5):
             create_task_instance(
                 task_id="test_task",
@@ -3757,17 +3758,26 @@ class TestGetPreviousTI:
             )
         session.commit()
 
-        with capture_orm_selects("task_instance") as statements:
+        with (
+            capture_orm_selects("task_instance") as statements,
+            assert_queries_count(1),
+        ):
             response = client.get(
                 "/execution/task-instances/previous/dag/test_task",
                 params={"logical_date": "2025-01-05T00:00:00Z"},
             )
 
         assert response.status_code == 200
-        assert response.json()["run_id"] == "run4"
+        data = response.json()
+        assert data["run_id"] == "run4"
+        assert data["logical_date"] == "2025-01-04T00:00:00Z"
         assert statements, "expected the endpoint to query the task_instance 
table"
         for sql in statements:
             assert re.search(r"\bLIMIT 1\b", sql), f"previous-TI lookup is not 
bounded to one row: {sql}"
+            dag_run_join_count = len(re.findall(r"JOIN dag_run(\s|$)", sql))
+            assert dag_run_join_count == 1, (
+                f"previous-TI query joins dag_run {dag_run_join_count} times, 
expected once: {sql}"
+            )
 
 
 class TestGetTaskStates:

Reply via email to