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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1e6c1ce34892 CAMEL-25020: observability must not report the 
CamelSqlQuery header when useMessageBodyForSql wins (#26885)
1e6c1ce34892 is described below

commit 1e6c1ce34892defecd04c1476250cbe196793f94
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 28 10:08:26 2026 +0200

    CAMEL-25020: observability must not report the CamelSqlQuery header when 
useMessageBodyForSql wins (#26885)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../telemetry/decorators/SqlQueryHeaderHelper.java | 14 ++++++--
 .../telemetry/decorators/SqlSpanDecoratorTest.java | 38 ++++++++++++++++++----
 .../tracing/decorators/SqlQueryHeaderHelper.java   | 14 ++++++--
 .../tracing/decorators/SqlSpanDecoratorTest.java   | 38 ++++++++++++++++++----
 .../camel/impl/console/SqlTraceDevConsole.java     | 12 +++++--
 .../console/SqlTraceDevConsoleQueryHeaderTest.java | 38 +++++++++++++++++-----
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  6 +++-
 7 files changed, 127 insertions(+), 33 deletions(-)

diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
index 0e489a5f78ee..8b28e9bae68f 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
@@ -30,13 +30,17 @@ import org.apache.camel.support.DefaultEndpoint;
  * surfacing the header value would attribute a statement to the exchange that 
never ran, and would place
  * sender-controlled text into telemetry.
  * <p/>
- * The option is read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
- * tracing modules must not depend on camel-sql. An endpoint that does not 
declare the option at all (jdbc, for
- * instance) never honours the header.
+ * {@code useMessageBodyForSql} takes precedence over both: the producer reads 
the statement from the message body
+ * before it ever looks at the header, so the header is not honoured when that 
option is enabled either.
+ * <p/>
+ * The options are read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not 
declare them at all (jdbc, for instance)
+ * never honours the header.
  */
 final class SqlQueryHeaderHelper {
 
     private static final String ALLOW_QUERY_FROM_HEADER = 
"allowQueryFromHeader";
+    private static final String USE_MESSAGE_BODY_FOR_SQL = 
"useMessageBodyForSql";
 
     private SqlQueryHeaderHelper() {
     }
@@ -50,6 +54,10 @@ final class SqlQueryHeaderHelper {
             return false;
         }
         if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            // the body wins over the header in SqlProducer, so the header 
never reaches the database
+            if (Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
USE_MESSAGE_BODY_FOR_SQL, true))) {
+                return false;
+            }
             return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
ALLOW_QUERY_FROM_HEADER, true));
         }
         return false;
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
index bcc08664f91d..00790bafc9e6 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
@@ -56,6 +56,16 @@ public class SqlSpanDecoratorTest {
         assertNull(span.tags().get(TagConstants.DB_STATEMENT));
     }
 
+    @Test
+    public void testPreIgnoresTheQueryHeaderWhenUseMessageBodyForSqlWins() {
+        MockSpanAdapter span = decorate(endpointWithOptions(true, true));
+
+        // SqlProducer reads the statement from the body before it looks at 
the header, so even with
+        // allowQueryFromHeader enabled the header is not what reached the 
database
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
     @Test
     public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
         // e.g. jdbc, which takes its query from the message body
@@ -88,8 +98,13 @@ public class SqlSpanDecoratorTest {
     }
 
     private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+        return endpointWithOptions(allow, false);
+    }
+
+    private static Endpoint endpointWithOptions(boolean allowQueryFromHeader, 
boolean useMessageBodyForSql) {
         Component component = Mockito.mock(Component.class);
-        Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new 
AllowQueryFromHeaderConfigurer(allow));
+        Mockito.when(component.getEndpointPropertyConfigurer())
+                .thenReturn(new SqlOptionsConfigurer(allowQueryFromHeader, 
useMessageBodyForSql));
 
         DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
         Mockito.when(endpoint.getComponent()).thenReturn(component);
@@ -99,12 +114,14 @@ public class SqlSpanDecoratorTest {
     /**
      * Stands in for the generated {@code SqlEndpointConfigurer}, which 
camel-telemetry cannot depend on.
      */
-    private static class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+    private static class SqlOptionsConfigurer implements PropertyConfigurer, 
PropertyConfigurerGetter {
 
-        private final boolean allow;
+        private final boolean allowQueryFromHeader;
+        private final boolean useMessageBodyForSql;
 
-        AllowQueryFromHeaderConfigurer(boolean allow) {
-            this.allow = allow;
+        SqlOptionsConfigurer(boolean allowQueryFromHeader, boolean 
useMessageBodyForSql) {
+            this.allowQueryFromHeader = allowQueryFromHeader;
+            this.useMessageBodyForSql = useMessageBodyForSql;
         }
 
         @Override
@@ -114,12 +131,19 @@ public class SqlSpanDecoratorTest {
 
         @Override
         public Class<?> getOptionType(String name, boolean ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+            return switch (name) {
+                case "allowQueryFromHeader", "useMessageBodyForSql" -> 
boolean.class;
+                default -> null;
+            };
         }
 
         @Override
         public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? allow : null;
+            return switch (name) {
+                case "allowQueryFromHeader" -> allowQueryFromHeader;
+                case "useMessageBodyForSql" -> useMessageBodyForSql;
+                default -> null;
+            };
         }
     }
 }
diff --git 
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
index d3e21c0ebeec..b1ce02d42c76 100644
--- 
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
+++ 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
@@ -30,13 +30,17 @@ import org.apache.camel.support.DefaultEndpoint;
  * surfacing the header value would attribute a statement to the exchange that 
never ran, and would place
  * sender-controlled text into telemetry.
  * <p/>
- * The option is read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
- * tracing modules must not depend on camel-sql. An endpoint that does not 
declare the option at all (jdbc, for
- * instance) never honours the header.
+ * {@code useMessageBodyForSql} takes precedence over both: the producer reads 
the statement from the message body
+ * before it ever looks at the header, so the header is not honoured when that 
option is enabled either.
+ * <p/>
+ * The options are read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not 
declare them at all (jdbc, for instance)
+ * never honours the header.
  */
 final class SqlQueryHeaderHelper {
 
     private static final String ALLOW_QUERY_FROM_HEADER = 
"allowQueryFromHeader";
+    private static final String USE_MESSAGE_BODY_FOR_SQL = 
"useMessageBodyForSql";
 
     private SqlQueryHeaderHelper() {
     }
@@ -50,6 +54,10 @@ final class SqlQueryHeaderHelper {
             return false;
         }
         if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            // the body wins over the header in SqlProducer, so the header 
never reaches the database
+            if (Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
USE_MESSAGE_BODY_FOR_SQL, true))) {
+                return false;
+            }
             return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
ALLOW_QUERY_FROM_HEADER, true));
         }
         return false;
diff --git 
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
 
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
index f06b6780d2a2..fdd406c1cc13 100644
--- 
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
+++ 
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
@@ -57,6 +57,16 @@ public class SqlSpanDecoratorTest {
         assertNull(span.tags().get(TagConstants.DB_STATEMENT));
     }
 
+    @Test
+    public void testPreIgnoresTheQueryHeaderWhenUseMessageBodyForSqlWins() {
+        MockSpanAdapter span = decorate(endpointWithOptions(true, true));
+
+        // SqlProducer reads the statement from the body before it looks at 
the header, so even with
+        // allowQueryFromHeader enabled the header is not what reached the 
database
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
     @Test
     public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
         // e.g. jdbc, which takes its query from the message body
@@ -89,8 +99,13 @@ public class SqlSpanDecoratorTest {
     }
 
     private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+        return endpointWithOptions(allow, false);
+    }
+
+    private static Endpoint endpointWithOptions(boolean allowQueryFromHeader, 
boolean useMessageBodyForSql) {
         Component component = Mockito.mock(Component.class);
-        Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new 
AllowQueryFromHeaderConfigurer(allow));
+        Mockito.when(component.getEndpointPropertyConfigurer())
+                .thenReturn(new SqlOptionsConfigurer(allowQueryFromHeader, 
useMessageBodyForSql));
 
         DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
         Mockito.when(endpoint.getComponent()).thenReturn(component);
@@ -100,12 +115,14 @@ public class SqlSpanDecoratorTest {
     /**
      * Stands in for the generated {@code SqlEndpointConfigurer}, which 
camel-tracing cannot depend on.
      */
-    private static class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+    private static class SqlOptionsConfigurer implements PropertyConfigurer, 
PropertyConfigurerGetter {
 
-        private final boolean allow;
+        private final boolean allowQueryFromHeader;
+        private final boolean useMessageBodyForSql;
 
-        AllowQueryFromHeaderConfigurer(boolean allow) {
-            this.allow = allow;
+        SqlOptionsConfigurer(boolean allowQueryFromHeader, boolean 
useMessageBodyForSql) {
+            this.allowQueryFromHeader = allowQueryFromHeader;
+            this.useMessageBodyForSql = useMessageBodyForSql;
         }
 
         @Override
@@ -115,12 +132,19 @@ public class SqlSpanDecoratorTest {
 
         @Override
         public Class<?> getOptionType(String name, boolean ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+            return switch (name) {
+                case "allowQueryFromHeader", "useMessageBodyForSql" -> 
boolean.class;
+                default -> null;
+            };
         }
 
         @Override
         public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? allow : null;
+            return switch (name) {
+                case "allowQueryFromHeader" -> allowQueryFromHeader;
+                case "useMessageBodyForSql" -> useMessageBodyForSql;
+                default -> null;
+            };
         }
     }
 }
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
index ca8a4578f10a..cc6b9afcb86e 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
@@ -243,9 +243,11 @@ public class SqlTraceDevConsole extends AbstractDevConsole 
{
      * <p/>
      * camel-sql gates that header behind the {@code allowQueryFromHeader} 
option, disabled by default; when it is
      * disabled the endpoint-configured query runs instead, so reporting the 
header would show a statement that never
-     * executed. The option is read through the generated property configurer 
rather than by casting, because
-     * camel-console must not depend on camel-sql. Endpoints that do not 
declare the option at all - jdbc, which takes
-     * its query from the body - never honour the header.
+     * executed. {@code useMessageBodyForSql} takes precedence over both - the 
producer reads the statement from the
+     * message body before it looks at the header - so the header is not 
honoured when that option is enabled either.
+     * The options are read through the generated property configurer rather 
than by casting, because camel-console must
+     * not depend on camel-sql. Endpoints that do not declare them at all - 
jdbc, which takes its query from the body -
+     * never honour the header.
      */
     private static boolean isQueryHeaderHonoured(Endpoint endpoint) {
         if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
@@ -256,6 +258,10 @@ public class SqlTraceDevConsole extends AbstractDevConsole 
{
             return false;
         }
         if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            // the body wins over the header in SqlProducer, so the header 
never reaches the database
+            if (Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
"useMessageBodyForSql", true))) {
+                return false;
+            }
             return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
"allowQueryFromHeader", true));
         }
         return false;
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
index f26d3a703d6d..232eb163044d 100644
--- 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
@@ -60,8 +60,19 @@ public class SqlTraceDevConsoleQueryHeaderTest extends 
ContextTestSupport {
         Assertions.assertEquals(HEADER_QUERY, tracedQuery(true));
     }
 
+    @Test
+    public void testHeaderQueryIsIgnoredWhenUseMessageBodyForSqlWins() throws 
Exception {
+        // SqlProducer reads the statement from the body before it looks at 
the header, so the header
+        // is not the statement that ran even though allowQueryFromHeader is 
enabled
+        Assertions.assertEquals(ENDPOINT_QUERY, tracedQuery(true, true));
+    }
+
     private String tracedQuery(boolean allowQueryFromHeader) throws Exception {
-        context.addComponent("sql", new 
FakeSqlComponent(allowQueryFromHeader));
+        return tracedQuery(allowQueryFromHeader, false);
+    }
+
+    private String tracedQuery(boolean allowQueryFromHeader, boolean 
useMessageBodyForSql) throws Exception {
+        context.addComponent("sql", new FakeSqlComponent(allowQueryFromHeader, 
useMessageBodyForSql));
 
         DevConsole con = 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole("sql-trace");
         Assertions.assertNotNull(con);
@@ -83,8 +94,8 @@ public class SqlTraceDevConsoleQueryHeaderTest extends 
ContextTestSupport {
 
         private final PropertyConfigurer configurer;
 
-        private FakeSqlComponent(boolean allowQueryFromHeader) {
-            this.configurer = new 
AllowQueryFromHeaderConfigurer(allowQueryFromHeader);
+        private FakeSqlComponent(boolean allowQueryFromHeader, boolean 
useMessageBodyForSql) {
+            this.configurer = new SqlOptionsConfigurer(allowQueryFromHeader, 
useMessageBodyForSql);
         }
 
         @Override
@@ -120,12 +131,14 @@ public class SqlTraceDevConsoleQueryHeaderTest extends 
ContextTestSupport {
         }
     }
 
-    private static final class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+    private static final class SqlOptionsConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
 
-        private final boolean allow;
+        private final boolean allowQueryFromHeader;
+        private final boolean useMessageBodyForSql;
 
-        private AllowQueryFromHeaderConfigurer(boolean allow) {
-            this.allow = allow;
+        private SqlOptionsConfigurer(boolean allowQueryFromHeader, boolean 
useMessageBodyForSql) {
+            this.allowQueryFromHeader = allowQueryFromHeader;
+            this.useMessageBodyForSql = useMessageBodyForSql;
         }
 
         @Override
@@ -135,12 +148,19 @@ public class SqlTraceDevConsoleQueryHeaderTest extends 
ContextTestSupport {
 
         @Override
         public Class<?> getOptionType(String name, boolean ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+            return switch (name) {
+                case "allowQueryFromHeader", "useMessageBodyForSql" -> 
boolean.class;
+                default -> null;
+            };
         }
 
         @Override
         public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
-            return "allowQueryFromHeader".equals(name) ? allow : null;
+            return switch (name) {
+                case "allowQueryFromHeader" -> allowQueryFromHeader;
+                case "useMessageBodyForSql" -> useMessageBodyForSql;
+                default -> null;
+            };
         }
     }
 }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 50a590a0a90b..b3546fb8648a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2808,7 +2808,11 @@ Observability follows the gate. The `db.statement` span 
tag set by the `sql` spa
 the deprecated camel-tracing) is now taken from the `CamelSqlQuery` header 
only when the endpoint sets
 `allowQueryFromHeader=true`; otherwise the tag is omitted rather than 
reporting a statement that was never
 executed. A route that reads `db.statement` from a `sql:` span and relies on 
the header value must set
-`allowQueryFromHeader=true`.
+`allowQueryFromHeader=true`. The same gate applies to the `sql-trace` 
developer console, which reports the
+endpoint-configured query instead of the header when the header is not 
honoured. Note that
+`useMessageBodyForSql=true` takes precedence over `allowQueryFromHeader`, 
because the producer reads the statement
+from the message body before it looks at the header; with both options enabled 
the header is therefore still not
+reported.
 
 === camel-core - the inheritErrorHandler attribute on circuitBreaker and 
failoverLoadBalancer is now a String
 

Reply via email to