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 b539152f3524 CAMEL-26231: camel-influxdb - fail the exchange on a 
query error, do not change the Map body, and accept the operation in any case 
and check it at startup (#29073)
b539152f3524 is described below

commit b539152f3524ef8d456835ed0752a3774c6a4a68
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:12:00 2026 +0100

    CAMEL-26231: camel-influxdb - fail the exchange on a query error, do not 
change the Map body, and accept the operation in any case and check it at 
startup (#29073)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/catalog/components/influxdb.json  |   4 +-
 .../camel/catalog/docs/influxdb-component.adoc     |  12 ++
 .../apache/camel/component/influxdb/influxdb.json  |   4 +-
 .../src/main/docs/influxdb-component.adoc          |  12 ++
 .../camel/component/influxdb/InfluxDbEndpoint.java |  20 ++-
 .../camel/component/influxdb/InfluxDbProducer.java |  31 ++++-
 .../converters/CamelInfluxDbConverters.java        |  12 +-
 .../influxdb/InfluxDbProducerOperationTest.java    | 102 +++++++++++++++
 .../influxdb/InfluxDbProducerQueryErrorTest.java   | 138 +++++++++++++++++++++
 .../converters/CamelInfluxDbConverterTest.java     |  27 ++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  11 ++
 .../dsl/InfluxDbEndpointBuilderFactory.java        |   3 +-
 12 files changed, 359 insertions(+), 17 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb.json
index 33c950f81281..90c4aef1b0e9 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb.json
@@ -35,7 +35,7 @@
   },
   "bodyTypes": {
     "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"batch", "value": "true", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.BatchPoints" }, { "option": "operation", "value": "query", 
"carriers": [ "any" ] }, { "option": "operation", "value": "ping", "carriers": 
[ "any" ] } ], "description": "The org.influxdb.dto.Point to write, or a Map of 
the field names and their values with the measurement name under the 
camelInfluxDB.MeasurementName key. Text, bytes o [...]
-    "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "query", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.QueryResult" }, { "option": "operation", "value": "ping", 
"carriers": [ "pojo" ], "javaType": "org.influxdb.dto.Pong" } ], "description": 
"An insert leaves the body unchanged. A query gives the QueryResult of 
InfluxDB, which carries an error the server reports (such as an unknown 
database) instead of failing the exchange; ping gi [...]
+    "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "query", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.QueryResult" }, { "option": "operation", "value": "ping", 
"carriers": [ "pojo" ], "javaType": "org.influxdb.dto.Pong" } ], "description": 
"An insert leaves the body unchanged. A query gives the QueryResult of 
InfluxDB; an error the server reports in it (such as an unknown database) fails 
the exchange instead. Ping gives the Pong  [...]
   },
   "properties": {
     "connectionBean": { "index": 0, "kind": "path", "displayName": "Connection 
Bean", "group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Connection to the influx 
database, of class InfluxDB.class" },
@@ -43,7 +43,7 @@
     "batch": { "index": 2, "kind": "parameter", "displayName": "Batch", 
"group": "producer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "Define if this operation is a 
batch operation or not" },
     "checkDatabaseExistence": { "index": 3, "kind": "parameter", 
"displayName": "Check Database Existence", "group": "producer", "label": "", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Define if we want to check the database existence while 
starting the endpoint" },
     "databaseName": { "index": 4, "kind": "parameter", "displayName": 
"Database Name", "group": "producer", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The name of the database where the time 
series will be stored" },
-    "operation": { "index": 5, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "insert", "description": "Define if 
this operation is an insert or a query" },
+    "operation": { "index": 5, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": "java.lang.String", "enum": [ "insert", "query", "ping" ], 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"insert", "description": "Define if this operation is an insert, a query or a 
ping. The value is not case-sensitive." },
     "query": { "index": 6, "kind": "parameter", "displayName": "Query", 
"group": "producer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Define the query in case of operation query" },
     "retentionPolicy": { "index": 7, "kind": "parameter", "displayName": 
"Retention Policy", "group": "producer", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "default", "description": 
"The string that defines the retention policy to the data created by the 
endpoint" },
     "lazyStartProducer": { "index": 8, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb-component.adoc
index 7b170d9b0601..bd9e954fa582 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb-component.adoc
@@ -17,6 +17,7 @@ a time series database.
 
 The native body type for this component is `Point` (the native influxdb class).
 However, it can also accept `Map<String, Object>` as message body, and it will 
get converted to `Point.class`, please note that the map must contain an 
element with `camelInfluxDB.MeasurementName` as key.
+The other entries of the map are the fields of the point; the map itself is 
not changed, so it can be immutable.
 
 Additionally, you may register your own Converters to your data type to 
`Point`, or use the
 (un)marshalling tools provided by Camel.
@@ -51,6 +52,17 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Operations
+
+The `operation` option selects what the producer does with each message: 
`insert` (the default) writes the body,
+`query` runs the query of the `camelInfluxDB.query` header or of the `query` 
option and gives its `QueryResult`, and
+`ping` gives the `Pong` of the server. The value is not case-sensitive, and an 
endpoint with another operation fails
+to start.
+
+InfluxDB answers a query it cannot run, such as a query on a database that 
does not exist, with the error in the
+result instead of an error status, so the client returns that result. An error 
in the result of the query, or of one
+of its statements, fails the exchange with a `CamelInfluxDbException` whose 
message holds the error of the server.
+
 == Example
 
 Below is an example route that stores a point into the db (taking the db name 
from the URI)
diff --git 
a/components/camel-influxdb/src/generated/resources/META-INF/org/apache/camel/component/influxdb/influxdb.json
 
b/components/camel-influxdb/src/generated/resources/META-INF/org/apache/camel/component/influxdb/influxdb.json
index 33c950f81281..90c4aef1b0e9 100644
--- 
a/components/camel-influxdb/src/generated/resources/META-INF/org/apache/camel/component/influxdb/influxdb.json
+++ 
b/components/camel-influxdb/src/generated/resources/META-INF/org/apache/camel/component/influxdb/influxdb.json
@@ -35,7 +35,7 @@
   },
   "bodyTypes": {
     "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"batch", "value": "true", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.BatchPoints" }, { "option": "operation", "value": "query", 
"carriers": [ "any" ] }, { "option": "operation", "value": "ping", "carriers": 
[ "any" ] } ], "description": "The org.influxdb.dto.Point to write, or a Map of 
the field names and their values with the measurement name under the 
camelInfluxDB.MeasurementName key. Text, bytes o [...]
-    "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "query", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.QueryResult" }, { "option": "operation", "value": "ping", 
"carriers": [ "pojo" ], "javaType": "org.influxdb.dto.Pong" } ], "description": 
"An insert leaves the body unchanged. A query gives the QueryResult of 
InfluxDB, which carries an error the server reports (such as an unknown 
database) instead of failing the exchange; ping gi [...]
+    "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "query", "carriers": [ "pojo" ], "javaType": 
"org.influxdb.dto.QueryResult" }, { "option": "operation", "value": "ping", 
"carriers": [ "pojo" ], "javaType": "org.influxdb.dto.Pong" } ], "description": 
"An insert leaves the body unchanged. A query gives the QueryResult of 
InfluxDB; an error the server reports in it (such as an unknown database) fails 
the exchange instead. Ping gives the Pong  [...]
   },
   "properties": {
     "connectionBean": { "index": 0, "kind": "path", "displayName": "Connection 
Bean", "group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Connection to the influx 
database, of class InfluxDB.class" },
@@ -43,7 +43,7 @@
     "batch": { "index": 2, "kind": "parameter", "displayName": "Batch", 
"group": "producer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "Define if this operation is a 
batch operation or not" },
     "checkDatabaseExistence": { "index": 3, "kind": "parameter", 
"displayName": "Check Database Existence", "group": "producer", "label": "", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Define if we want to check the database existence while 
starting the endpoint" },
     "databaseName": { "index": 4, "kind": "parameter", "displayName": 
"Database Name", "group": "producer", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The name of the database where the time 
series will be stored" },
-    "operation": { "index": 5, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "insert", "description": "Define if 
this operation is an insert or a query" },
+    "operation": { "index": 5, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": "java.lang.String", "enum": [ "insert", "query", "ping" ], 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"insert", "description": "Define if this operation is an insert, a query or a 
ping. The value is not case-sensitive." },
     "query": { "index": 6, "kind": "parameter", "displayName": "Query", 
"group": "producer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Define the query in case of operation query" },
     "retentionPolicy": { "index": 7, "kind": "parameter", "displayName": 
"Retention Policy", "group": "producer", "label": "", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "default", "description": 
"The string that defines the retention policy to the data created by the 
endpoint" },
     "lazyStartProducer": { "index": 8, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
diff --git a/components/camel-influxdb/src/main/docs/influxdb-component.adoc 
b/components/camel-influxdb/src/main/docs/influxdb-component.adoc
index 7b170d9b0601..bd9e954fa582 100644
--- a/components/camel-influxdb/src/main/docs/influxdb-component.adoc
+++ b/components/camel-influxdb/src/main/docs/influxdb-component.adoc
@@ -17,6 +17,7 @@ a time series database.
 
 The native body type for this component is `Point` (the native influxdb class).
 However, it can also accept `Map<String, Object>` as message body, and it will 
get converted to `Point.class`, please note that the map must contain an 
element with `camelInfluxDB.MeasurementName` as key.
+The other entries of the map are the fields of the point; the map itself is 
not changed, so it can be immutable.
 
 Additionally, you may register your own Converters to your data type to 
`Point`, or use the
 (un)marshalling tools provided by Camel.
@@ -51,6 +52,17 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Operations
+
+The `operation` option selects what the producer does with each message: 
`insert` (the default) writes the body,
+`query` runs the query of the `camelInfluxDB.query` header or of the `query` 
option and gives its `QueryResult`, and
+`ping` gives the `Pong` of the server. The value is not case-sensitive, and an 
endpoint with another operation fails
+to start.
+
+InfluxDB answers a query it cannot run, such as a query on a database that 
does not exist, with the error in the
+result instead of an error status, so the client returns that result. An error 
in the result of the query, or of one
+of its statements, fails the exchange with a `CamelInfluxDbException` whose 
message holds the error of the server.
+
 == Example
 
 Below is an example route that stores a point into the db (taking the db name 
from the URI)
diff --git 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbEndpoint.java
 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbEndpoint.java
index c1b34c4edb55..5d5a59dd30fb 100644
--- 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbEndpoint.java
+++ 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbEndpoint.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.component.influxdb;
 
+import java.util.Locale;
+import java.util.Set;
+
 import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
@@ -59,14 +62,16 @@ import org.slf4j.LoggerFactory;
                                            @BodyType.When(option = 
"operation", value = "ping", carriers = Carrier.POJO,
                                                           javaType = 
"org.influxdb.dto.Pong") },
                                    description = "An insert leaves the body 
unchanged. A query gives the QueryResult of"
-                                                 + " InfluxDB, which carries 
an error the server reports (such as an"
-                                                 + " unknown database) instead 
of failing the exchange; ping gives the Pong"
-                                                 + " with the version of the 
server"))
+                                                 + " InfluxDB; an error the 
server reports in it (such as an unknown"
+                                                 + " database) fails the 
exchange instead. Ping gives the Pong with the"
+                                                 + " version of the server"))
 public class InfluxDbEndpoint extends DefaultEndpoint {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(InfluxDbEndpoint.class);
     private static final String CREATE_DATABASE = "CREATE DATABASE ";
     private static final String SHOW_DATABASES = "SHOW DATABASES";
+    private static final Set<String> OPERATIONS
+            = Set.of(InfluxDbOperations.INSERT, InfluxDbOperations.QUERY, 
InfluxDbOperations.PING);
 
     private InfluxDB influxDB;
 
@@ -79,7 +84,7 @@ public class InfluxDbEndpoint extends DefaultEndpoint {
     private String retentionPolicy = "default";
     @UriParam(defaultValue = "false")
     private boolean batch;
-    @UriParam(defaultValue = InfluxDbOperations.INSERT)
+    @UriParam(defaultValue = InfluxDbOperations.INSERT, enums = 
"insert,query,ping")
     private String operation = InfluxDbOperations.INSERT;
     @UriParam
     private String query;
@@ -106,6 +111,11 @@ public class InfluxDbEndpoint extends DefaultEndpoint {
     protected void doInit() throws Exception {
         super.doInit();
 
+        // an operation that is not supported fails the start of the endpoint, 
instead of its first message
+        if (operation == null || 
!OPERATIONS.contains(operation.toLowerCase(Locale.ROOT))) {
+            throw new IllegalArgumentException(
+                    "The operation " + operation + " is not supported: use one 
of insert, query or ping");
+        }
         if (checkDatabaseExistence) {
             ensureDatabaseExists();
         }
@@ -172,7 +182,7 @@ public class InfluxDbEndpoint extends DefaultEndpoint {
     }
 
     /**
-     * Define if this operation is an insert or a query
+     * Define if this operation is an insert, a query or a ping. The value is 
not case-sensitive.
      */
     public void setOperation(String operation) {
         this.operation = operation;
diff --git 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbProducer.java
 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbProducer.java
index de98bff06eeb..ba11d7912178 100644
--- 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbProducer.java
+++ 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/InfluxDbProducer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.component.influxdb;
 
+import java.util.Locale;
+
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.support.DefaultProducer;
@@ -56,7 +58,8 @@ public class InfluxDbProducer extends DefaultProducer {
     public void process(Exchange exchange) throws Exception {
         String dataBaseName = calculateDatabaseName(exchange);
         String retentionPolicy = calculateRetentionPolicy(exchange);
-        switch (endpoint.getOperation()) {
+        // the operation is not case-sensitive, and the endpoint fails to 
start with one that is not supported
+        switch (endpoint.getOperation().toLowerCase(Locale.ROOT)) {
             case InfluxDbOperations.INSERT:
                 doInsert(exchange, dataBaseName, retentionPolicy);
                 break;
@@ -99,10 +102,36 @@ public class InfluxDbProducer extends DefaultProducer {
         String query = calculateQuery(exchange);
         Query influxdbQuery = new Query(query, dataBaseName);
         QueryResult resultSet = connection.query(influxdbQuery);
+        String error = errorOf(resultSet);
+        if (error != null) {
+            throw new CamelInfluxDbException("The query failed in InfluxDB: " 
+ error);
+        }
         MessageHelper.copyHeaders(exchange.getIn(), exchange.getOut(), true);
         exchange.getMessage().setBody(resultSet);
     }
 
+    /**
+     * The error that InfluxDB reports in the result of a query, or null. 
InfluxDB answers a query that it cannot run,
+     * for example on a database that does not exist, with the error in the 
result of the query or of one of its
+     * statements instead of an error status, so the client returns that 
result instead of throwing.
+     */
+    private static String errorOf(QueryResult resultSet) {
+        if (resultSet == null) {
+            return null;
+        }
+        if (resultSet.hasError()) {
+            return resultSet.getError();
+        }
+        if (resultSet.getResults() != null) {
+            for (QueryResult.Result result : resultSet.getResults()) {
+                if (result != null && result.hasError()) {
+                    return result.getError();
+                }
+            }
+        }
+        return null;
+    }
+
     private void doPing(Exchange exchange) {
         Pong result = connection.ping();
         MessageHelper.copyHeaders(exchange.getIn(), exchange.getOut(), true);
diff --git 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverters.java
 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverters.java
index e3d2aafaffb3..673e1af15820 100644
--- 
a/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverters.java
+++ 
b/components/camel-influxdb/src/main/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverters.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.influxdb.converters;
 
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.camel.Converter;
@@ -37,12 +38,11 @@ public final class CamelInfluxDbConverters {
             throw new CamelInfluxDbException(format);
         }
 
-        String measurenmentNameString = measurementName.toString();
-        Point.Builder pointBuilder = Point.measurement(measurenmentNameString);
-        map.remove(InfluxDbConstants.MEASUREMENT_NAME);
-        pointBuilder.fields(map);
-        map.put(InfluxDbConstants.MEASUREMENT_NAME, measurementName);
-        return pointBuilder.build();
+        // the fields are a copy of the map without the measurement name, so 
the map of the caller is not changed (it can
+        // be immutable or shared with other code)
+        Map<String, Object> fields = new LinkedHashMap<>(map);
+        fields.remove(InfluxDbConstants.MEASUREMENT_NAME);
+        return 
Point.measurement(measurementName.toString()).fields(fields).build();
     }
 
 }
diff --git 
a/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerOperationTest.java
 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerOperationTest.java
new file mode 100644
index 000000000000..c14ed68d9db9
--- /dev/null
+++ 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerOperationTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.influxdb;
+
+import java.util.List;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.util.ObjectHelper;
+import org.influxdb.dto.Point;
+import org.influxdb.dto.Pong;
+import org.influxdb.dto.Query;
+import org.influxdb.dto.QueryResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * The operation is one of insert, query or ping, in any case, and an endpoint 
with another operation fails to start
+ * instead of failing its first message.
+ */
+class InfluxDbProducerOperationTest extends AbstractInfluxDbTest {
+
+    @Test
+    void queryInUpperCaseRunsTheQuery() throws Exception {
+        QueryResult queryResult = new QueryResult();
+        queryResult.setResults(List.of(new QueryResult.Result()));
+        
when(mockedDbConnection.query(any(Query.class))).thenReturn(queryResult);
+        
addRouteTo("influxdb:influxDbBean?databaseName={{influxdb.testDb}}&operation=QUERY");
+
+        Exchange exchange = template.send("direct:start",
+                e -> e.getIn().setHeader(InfluxDbConstants.INFLUXDB_QUERY, 
"select * from cpu"));
+
+        assertNull(exchange.getException());
+        assertSame(queryResult, exchange.getMessage().getBody());
+    }
+
+    @Test
+    void pingInMixedCasePings() throws Exception {
+        Pong pong = new Pong();
+        pong.setVersion("1.8.10");
+        when(mockedDbConnection.ping()).thenReturn(pong);
+        
addRouteTo("influxdb:influxDbBean?databaseName={{influxdb.testDb}}&operation=Ping");
+
+        Exchange exchange = template.send("direct:start", e -> 
e.getIn().setBody("test"));
+
+        assertNull(exchange.getException());
+        assertSame(pong, exchange.getMessage().getBody());
+    }
+
+    @Test
+    void insertInUpperCaseWritesThePoint() throws Exception {
+        
addRouteTo("influxdb:influxDbBean?databaseName={{influxdb.testDb}}&operation=INSERT");
+
+        Point point = Point.measurement("cpu").addField("idle", 90L).build();
+
+        Exchange exchange = template.send("direct:start", e -> 
e.getIn().setBody(point));
+
+        assertNull(exchange.getException());
+        verify(mockedDbConnection).write("myTestTimeSeries", "default", point);
+    }
+
+    @Test
+    void unsupportedOperationFailsTheStartOfTheRoute() {
+        Exception failure = assertThrows(Exception.class,
+                () -> 
addRouteTo("influxdb:influxDbBean?databaseName={{influxdb.testDb}}&operation=delete"));
+
+        IllegalArgumentException cause = 
ObjectHelper.getException(IllegalArgumentException.class, failure);
+        assertNotNull(cause, "IllegalArgumentException in the causes of " + 
failure);
+        assertEquals("The operation delete is not supported: use one of 
insert, query or ping", cause.getMessage());
+    }
+
+    private void addRouteTo(String uri) throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").to(uri);
+            }
+        });
+    }
+}
diff --git 
a/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerQueryErrorTest.java
 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerQueryErrorTest.java
new file mode 100644
index 000000000000..c54f64ef157e
--- /dev/null
+++ 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/InfluxDbProducerQueryErrorTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.influxdb;
+
+import java.util.List;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.influxdb.dto.Query;
+import org.influxdb.dto.QueryResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * InfluxDB answers a query it cannot run, such as one on a database that does 
not exist, with the error in the result
+ * instead of an error status, and the client returns that result: such an 
error must fail the exchange.
+ */
+class InfluxDbProducerQueryErrorTest extends AbstractInfluxDbTest {
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:query")
+                        
.to("influxdb:influxDbBean?databaseName={{influxdb.testDb}}&operation=query")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    void errorOfTheStatementFailsTheExchange() throws Exception {
+        // the answer of InfluxDB to a query on a database that does not exist
+        answer(queryResult(null, result("database not found: 
myTestTimeSeries")));
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(0);
+
+        Exchange exchange = query();
+
+        CamelInfluxDbException failure = 
assertInstanceOf(CamelInfluxDbException.class, exchange.getException(),
+                "exception of an exchange whose query failed");
+        assertEquals("The query failed in InfluxDB: database not found: 
myTestTimeSeries", failure.getMessage());
+        result.assertIsSatisfied();
+    }
+
+    @Test
+    void errorOfTheQueryFailsTheExchange() throws Exception {
+        answer(queryResult("query interrupted"));
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(0);
+
+        Exchange exchange = query();
+
+        CamelInfluxDbException failure = 
assertInstanceOf(CamelInfluxDbException.class, exchange.getException(),
+                "exception of an exchange whose query failed");
+        assertEquals("The query failed in InfluxDB: query interrupted", 
failure.getMessage());
+        result.assertIsSatisfied();
+    }
+
+    @Test
+    void errorOfTheSecondStatementFailsTheExchange() throws Exception {
+        answer(queryResult(null, result(null), result("measurement not 
found")));
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(0);
+
+        Exchange exchange = query();
+
+        CamelInfluxDbException failure = 
assertInstanceOf(CamelInfluxDbException.class, exchange.getException(),
+                "exception of an exchange whose second statement failed");
+        assertEquals("The query failed in InfluxDB: measurement not found", 
failure.getMessage());
+        result.assertIsSatisfied();
+    }
+
+    @Test
+    void resultWithoutErrorIsTheBody() throws Exception {
+        QueryResult answer = queryResult(null, result(null));
+        answer(answer);
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedMessageCount(1);
+
+        Exchange exchange = query();
+
+        assertNull(exchange.getException());
+        assertSame(answer, exchange.getMessage().getBody());
+        result.assertIsSatisfied();
+    }
+
+    private Exchange query() {
+        return template.send("direct:query",
+                e -> e.getIn().setHeader(InfluxDbConstants.INFLUXDB_QUERY, 
"select * from cpu"));
+    }
+
+    private void answer(QueryResult queryResult) {
+        
when(mockedDbConnection.query(any(Query.class))).thenReturn(queryResult);
+    }
+
+    private static QueryResult queryResult(String error, QueryResult.Result... 
results) {
+        QueryResult queryResult = new QueryResult();
+        queryResult.setError(error);
+        queryResult.setResults(List.of(results));
+        return queryResult;
+    }
+
+    private static QueryResult.Result result(String error) {
+        QueryResult.Result result = new QueryResult.Result();
+        result.setError(error);
+        if (error == null) {
+            QueryResult.Series series = new QueryResult.Series();
+            series.setName("cpu");
+            series.setColumns(List.of("time", "idle"));
+            series.setValues(List.of(List.of("2026-11-03T00:00:00Z", 90)));
+            result.setSeries(List.of(series));
+        }
+        return result;
+    }
+}
diff --git 
a/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverterTest.java
 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverterTest.java
index ac405932bff7..4b0c8890e7fa 100644
--- 
a/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverterTest.java
+++ 
b/components/camel-influxdb/src/test/java/org/apache/camel/component/influxdb/converters/CamelInfluxDbConverterTest.java
@@ -17,6 +17,8 @@
 package org.apache.camel.component.influxdb.converters;
 
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.camel.component.influxdb.InfluxDbConstants;
@@ -25,6 +27,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -87,6 +90,30 @@ public class CamelInfluxDbConverterTest {
 
     }
 
+    @Test
+    void convertsAnImmutableMap() {
+        Map<String, Object> pointInMapFormat = 
Map.of(InfluxDbConstants.MEASUREMENT_NAME, "testCPU", "busy", 99);
+
+        Point p = CamelInfluxDbConverters.fromMapToPoint(pointInMapFormat);
+
+        assertEquals("testCPU busy=99i", p.lineProtocol());
+    }
+
+    @Test
+    void doesNotChangeTheMapOfTheCaller() {
+        Map<String, Object> pointInMapFormat = new LinkedHashMap<>();
+        pointInMapFormat.put(InfluxDbConstants.MEASUREMENT_NAME, "testCPU");
+        pointInMapFormat.put("busy", 99);
+        Map<String, Object> before = new LinkedHashMap<>(pointInMapFormat);
+
+        Point p = CamelInfluxDbConverters.fromMapToPoint(pointInMapFormat);
+
+        assertEquals("testCPU busy=99i", p.lineProtocol());
+        assertEquals(before, pointInMapFormat);
+        // the entries are in the same order: the measurement name was not 
removed and put back at the end
+        assertEquals(List.copyOf(before.keySet()), 
List.copyOf(pointInMapFormat.keySet()));
+    }
+
     @Test
     public void canAddByte() {
         Map<String, Object> pointInMapFormat = new HashMap<>();
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 488977773fba..d13479088c32 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -4758,6 +4758,17 @@ message whose data coding is 8-bit is now split by its 
length in bytes.
 
 `SmppSmCommand.getShortMessage(Message)` now declares `throws SmppException`.
 
+=== camel-influxdb - a query error fails the exchange, and the operation is 
checked at startup
+
+InfluxDB answers a query it cannot run, such as a query on a database that 
does not exist, with the error inside the
+result. The producer used to set that `QueryResult` as the body, and the route 
continued as if the query had
+succeeded. An error in the result of the query, or of one of its statements, 
now fails the exchange with a
+`CamelInfluxDbException` whose message holds the error of the server.
+
+The `operation` option now lists its values (`insert`, `query` and `ping`) and 
is not case-sensitive, so
+`operation=QUERY` runs a query. An endpoint with another operation now fails 
to start, where it used to start and fail
+every message with an `IllegalArgumentException`.
+
 === camel-paho, camel-paho-mqtt5 - a body that does not convert to bytes
 
 The `paho` and `paho-mqtt5` producers now fail with an 
`InvalidPayloadException` that names the type of the body for a
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/InfluxDbEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/InfluxDbEndpointBuilderFactory.java
index 8e4f55f4f802..cdf8420e6a42 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/InfluxDbEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/InfluxDbEndpointBuilderFactory.java
@@ -151,7 +151,8 @@ public interface InfluxDbEndpointBuilderFactory {
             return this;
         }
         /**
-         * Define if this operation is an insert or a query.
+         * Define if this operation is an insert, a query or a ping. The value
+         * is not case-sensitive.
          * 
          * The option is a: <code>java.lang.String</code> type.
          * 

Reply via email to