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