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

vatsrahul1001 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 30006eb9f8c Fix Dag run duration stats crash on PostgreSQL 14+ (#70833)
30006eb9f8c is described below

commit 30006eb9f8c76b42beb349b5c1d859678a641253
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Mon Aug 3 07:31:37 2026 +0200

    Fix Dag run duration stats crash on PostgreSQL 14+ (#70833)
    
    PostgreSQL 14 changed EXTRACT(epoch FROM ...) to return numeric rather than 
double precision, so DagRun.duration is hydrated as Decimal on Postgres 14+. 
The duration-stats percentile interpolation then multiplies a Decimal by a 
float, which Python disallows, so the Dag run stats endpoint raises a TypeError 
for any Dag that has completed runs.
---
 .../api_fastapi/core_api/services/ui/dag_run.py    |  4 ++-
 .../core_api/services/ui/test_dag_run.py           | 41 ++++++++++++++++++++++
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py 
b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
index a9ba75417c8..2a3ab183252 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
@@ -35,7 +35,9 @@ def compute_duration_stats(durations: list[float]) -> 
DurationStats | None:
     if not durations:
         return None
 
-    sorted_d = sorted(durations)
+    # On Postgres 14+, DagRun.duration comes back as Decimal (EXTRACT(epoch 
...) returns
+    # numeric); coerce to float so the percentile interpolation below (Decimal 
* float) works.
+    sorted_d = sorted(float(d) for d in durations)
 
     counts = Counter(round(d) for d in sorted_d)
     max_count = max(counts.values())
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py 
b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
new file mode 100644
index 00000000000..afa9c5277e2
--- /dev/null
+++ b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from decimal import Decimal
+
+import pytest
+
+from airflow.api_fastapi.core_api.services.ui.dag_run import 
compute_duration_stats
+
+
[email protected]("cast", [float, Decimal], ids=["float", "decimal"])
+def test_compute_duration_stats_handles_float_and_decimal(cast):
+    """Durations arrive as Decimal on Postgres 14+ (EXTRACT(epoch ...) returns 
numeric) and as
+    float/int on other backends; the stats must compute for either without 
raising TypeError."""
+    durations = [cast(v) for v in (10, 20, 20, 30, 40)]
+
+    stats = compute_duration_stats(durations)
+
+    assert stats is not None
+    assert stats.mean == 24.0
+    assert stats.mode == 20.0
+    assert stats.p50 == 20.0
+    assert stats.p90 == 36.0
+    assert stats.p95 == 38.0
+    assert stats.p99 == 39.6

Reply via email to