Croway commented on code in PR #29282:
URL: https://github.com/apache/camel/pull/29282#discussion_r4486429650


##########
components/camel-jdbc/src/main/java/org/apache/camel/component/jdbc/DefaultJdbcPrepareStatementStrategy.java:
##########
@@ -118,10 +119,26 @@ public void remove() {
             }
 
         } else {
-            // just use a regular iterator
-            return 
exchange.getContext().getTypeConverter().convertTo(Iterator.class,
-                    headerMap != null ? headerMap.values() : null);
+            // plain ? placeholders are bound by position, so the values are 
only taken from the JDBC_PARAMETERS header
+            // (and not from all the message headers, as their order does not 
relate to the query)
+            return createPositionalIterator(query, exchange);
+        }
+    }
+
+    private Iterator<?> createPositionalIterator(String query, Exchange 
exchange) {
+        Object parameters = 
exchange.getIn().getHeader(JdbcConstants.JDBC_PARAMETERS);
+        if (parameters == null) {
+            throw new RuntimeExchangeException(
+                    "Cannot set the ? placeholders in query [" + query + "]: 
set their values, in order, as a List in the "
+                                               + JdbcConstants.JDBC_PARAMETERS
+                                               + " header, or use named 
parameters (:?name)",
+                    exchange);
+        } else if (parameters instanceof Map<?, ?> map) {
+            return map.values().iterator();
         }
+        // a List, Iterable or array gives its values in order, and any other 
value (a String is not split) is
+        // the one value
+        return ObjectHelper.createIterator(parameters, null, true);

Review Comment:
   **Medium: a `byte[]` for one `?` binds only its first byte, and the exchange 
succeeds**
   
   `ObjectHelper.createIterator` iterates a primitive array element by element, 
so a `byte[]` becomes a sequence of `Byte` values. `populateStatement` stops 
after `expectedParams` values and its count check passes, so the rest of the 
array is dropped without an error.
   
   Reproduced on H2 (the camel-jdbc test setup) with this PR:
   
   ```java
   // create table blobs (id int auto_increment primary key, data 
varbinary(100))
   template.request("direct:start", e -> {   // 
jdbc:testdb?useHeadersAsParameters=true
       e.getIn().setBody("insert into blobs (data) values (?)");
       e.getIn().setHeader(JdbcConstants.JDBC_PARAMETERS, new byte[] { 1, 2, 3, 
4, 5 });
   });
   // select length(data), data from blobs  ->  len=1, data=[1], no exception
   ```
   
   On `main` the same exchange fails with `Number of parameters mismatch. 
Expected: 1, was:0`. So this turns a loud failure into silent data loss for a 
BLOB/VARBINARY insert, and a `where hash = ?` lookup compares against a single 
byte.
   
   Suggestion: only an `Object[]` or a `Collection`/`Iterable` gives several 
values. A primitive array such as `byte[]` is the one value.



##########
components/camel-jdbc/src/main/java/org/apache/camel/component/jdbc/DefaultJdbcPrepareStatementStrategy.java:
##########
@@ -118,10 +119,26 @@ public void remove() {
             }
 
         } else {
-            // just use a regular iterator
-            return 
exchange.getContext().getTypeConverter().convertTo(Iterator.class,
-                    headerMap != null ? headerMap.values() : null);
+            // plain ? placeholders are bound by position, so the values are 
only taken from the JDBC_PARAMETERS header
+            // (and not from all the message headers, as their order does not 
relate to the query)
+            return createPositionalIterator(query, exchange);
+        }
+    }
+
+    private Iterator<?> createPositionalIterator(String query, Exchange 
exchange) {

Review Comment:
   **Low-medium: more values than placeholders are dropped silently**
   
   `List.of("cust1", "jstrachan", "unused")` for `select * from customer where 
id = ? and name = ?` returns the row with no error (reproduced). The PR 
description keeps this because `populateStatement` is shared with the named 
path. But `createPopulateIterator` already receives `expectedParams`, so the 
positional path can check the size itself without touching `populateStatement`:
   
   ```java
   private Iterator<?> createPositionalIterator(String query, int 
expectedParams, Exchange exchange) {
       ...
       List<?> values = ...; // the Map values, the Collection, the Object[], 
or the single value
       if (values.size() != expectedParams) {
           throw new RuntimeExchangeException("Query [" + query + "] has " + 
expectedParams + " ? placeholders but the "
                                              + JdbcConstants.JDBC_PARAMETERS + 
" header has " + values.size() + " values",
                   exchange);
       }
       return values.iterator();
   }
   ```
   
   Positional values now come only from an explicit header, so a size mismatch 
is a mistake in the route (a list built for another version of the query, an 
off-by-one). Failing is the safer default, and this check would also have 
caught the `byte[]` case above (5 values for 1 placeholder).



##########
components/camel-jdbc/src/main/java/org/apache/camel/component/jdbc/DefaultJdbcPrepareStatementStrategy.java:
##########
@@ -55,19 +56,19 @@ public Iterator<?> createPopulateIterator(
             final String query, final String preparedQuery, final int 
expectedParams,
             final Exchange exchange, final Object value)
             throws SQLException {
-        Map<?, ?> map = null;
-        if (exchange.getIn().hasHeaders()) {
-            if (exchange.getIn().getHeader(JdbcConstants.JDBC_PARAMETERS) != 
null) {
-                // header JDBC_PARAMETERS takes precedence over regular headers
-                map = 
exchange.getIn().getHeader(JdbcConstants.JDBC_PARAMETERS, Map.class);
-            } else {
-                map = exchange.getIn().getHeaders();
+        if (hasNamedParameters(query)) {
+            Map<?, ?> map = null;
+            if (exchange.getIn().hasHeaders()) {
+                if (exchange.getIn().getHeader(JdbcConstants.JDBC_PARAMETERS) 
!= null) {
+                    // header JDBC_PARAMETERS takes precedence over regular 
headers
+                    map = 
exchange.getIn().getHeader(JdbcConstants.JDBC_PARAMETERS, Map.class);

Review Comment:
   **Low-medium: the `List` that positional queries now need breaks a later 
named query in the same route**
   
   The producer leaves `CamelJdbcParameters` on the message. When a later jdbc 
call in the same route uses named parameters, this line converts the `List` to 
a `Map` and gets `null` (there is no List-to-Map converter). The lookup then 
fails, although the header it needs is set. Reproduced:
   
   ```java
   from("direct:twoCalls")
       .setBody(constant("select * from customer where id = ?"))
       .to("jdbc:testdb?useHeadersAsParameters=true")
       .setBody(constant("select * from customer where id = :?customer"))
       .to("jdbc:testdb?useHeadersAsParameters=true");
   
   // headers: CamelJdbcParameters = List.of("cust1"), customer = "cust2"
   // -> RuntimeExchangeException: Cannot find key [customer] in message body 
or headers to use when setting
   //    named parameter in query [select * from customer where id = :?customer]
   ```
   
   Before this PR a positional query took its values from the headers, so no 
`List` was left in this header to trip over. Suggestion: on the named path, use 
`CamelJdbcParameters` only when it is a `Map`. Otherwise fall back to the 
message headers, or fail with a message that says the header is not a `Map`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to