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 7b24bad2aa78 CAMEL-22770: camel-sql - Stored producer should lookup 
named parameters like regular producer do (#20337)
7b24bad2aa78 is described below

commit 7b24bad2aa78700335e145ce71a63b925c4b329e
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Dec 10 15:13:58 2025 +0100

    CAMEL-22770: camel-sql - Stored producer should lookup named parameters 
like regular producer do (#20337)
---
 .../src/main/docs/sql-stored-component.adoc        | 13 +++-
 .../sql/DefaultSqlPrepareStatementStrategy.java    | 56 +-------------
 .../org/apache/camel/component/sql/SqlHelper.java  | 53 +++++++++++++
 .../sql/stored/template/ast/InOutParameter.java    |  5 +-
 .../sql/stored/template/ast/InParameter.java       |  5 +-
 .../camel/component/sql/stored/ParserTest.java     | 21 ++++--
 .../sql/stored/ProducerNamedParameterTest.java     | 87 ++++++++++++++++++++++
 7 files changed, 171 insertions(+), 69 deletions(-)

diff --git a/components/camel-sql/src/main/docs/sql-stored-component.adoc 
b/components/camel-sql/src/main/docs/sql-stored-component.adoc
index 35a01c7c15bd..da251c0f519f 100644
--- a/components/camel-sql/src/main/docs/sql-stored-component.adoc
+++ b/components/camel-sql/src/main/docs/sql-stored-component.adoc
@@ -59,9 +59,9 @@ template, as show:
 [source,sql]
 ----
 SUBNUMBERS(
-  INTEGER ${headers.num1},
-  INTEGER ${headers.num2},
-  INOUT INTEGER ${headers.num3} out1,
+  INTEGER :#num,
+  INTEGER :#num2,
+  INOUT INTEGER :#num3 out1,
   OUT INTEGER out2
 )
 ----
@@ -121,6 +121,13 @@ It can be either a Simple expression or header location 
i.e. `:#<header name>`.
 the Simple expression `${header.val}` would mean that parameter value will be 
read from the header `val`.
 Header location expression `:#val` would have identical effect.
 
+When using named parameters, Camel will look up the names in the given 
precedence:
+
+1. from a xref:languages:simple-language.adoc[Simple] expressions
+2. from message body if it is a `java.util.Map`
+3. from message headers
+4. from exchange variables
+
 [source,xml]
 ----
 <to uri="sql-stored:MYFUNC('param1' org.example.Types.INTEGER(10) 
${header.srcValue})"/>
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java
index 1c188a9bc132..69c3b8cf00ab 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java
@@ -29,9 +29,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.camel.Exchange;
-import org.apache.camel.Expression;
 import org.apache.camel.RuntimeExchangeException;
-import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.util.StringQuoteHelper;
 import org.slf4j.Logger;
@@ -71,7 +69,7 @@ public class DefaultSqlPrepareStatementStrategy implements 
SqlPrepareStatementSt
                 Matcher matcher = REPLACE_IN_PATTERN.matcher(query);
                 while (matcher.find()) {
                     String found = matcher.group(1);
-                    Object parameter = lookupParameter(found, exchange, null);
+                    Object parameter = SqlHelper.lookupParameter(found, 
exchange, null);
                     if (parameter != null) {
                         Iterator<?> it = createInParameterIterator(parameter);
                         StringJoiner replaceBuilder = new StringJoiner(",");
@@ -266,54 +264,6 @@ public class DefaultSqlPrepareStatementStrategy implements 
SqlPrepareStatementSt
         }
     }
 
-    protected static Object lookupParameter(String nextParam, Exchange 
exchange, Object batchBody) {
-        Object body = batchBody != null ? batchBody : 
exchange.getMessage().getBody();
-        Map<?, ?> bodyMap = 
safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, 
exchange, body));
-        Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders());
-        Map<?, ?> variablesMap = safeMap(exchange.getVariables());
-
-        Object answer = null;
-        if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) 
&& nextParam.endsWith("}")) {
-            if (batchBody != null) {
-                // in batch mode then need to work on a copy of the original 
exchange and set the batch body
-                exchange = ExchangeHelper.createCopy(exchange, true);
-                exchange.getMessage().setBody(batchBody);
-            }
-            Expression exp = 
exchange.getContext().resolveLanguage("simple").createExpression(nextParam);
-            answer = exp.evaluate(exchange, Object.class);
-        } else if (bodyMap.containsKey(nextParam)) {
-            answer = bodyMap.get(nextParam);
-        } else if (headersMap.containsKey(nextParam)) {
-            answer = headersMap.get(nextParam);
-        } else if (variablesMap.containsKey(nextParam)) {
-            answer = variablesMap.get(nextParam);
-        }
-
-        return answer;
-    }
-
-    protected static boolean hasParameter(String nextParam, Exchange exchange, 
Object body) {
-        Map<?, ?> bodyMap = 
safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, body));
-        Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders());
-        Map<?, ?> variablesMap = safeMap(exchange.getVariables());
-
-        if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) 
&& nextParam.endsWith("}")) {
-            return true;
-        } else if (bodyMap.containsKey(nextParam)) {
-            return true;
-        } else if (headersMap.containsKey(nextParam)) {
-            return true;
-        } else if (variablesMap.containsKey(nextParam)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    private static Map<?, ?> safeMap(Map<?, ?> map) {
-        return (map == null || map.isEmpty()) ? Collections.emptyMap() : map;
-    }
-
     @SuppressWarnings("unchecked")
     protected static CompositeIterator<?> createInParameterIterator(Object 
value) {
         Iterator<?> it;
@@ -367,9 +317,9 @@ public class DefaultSqlPrepareStatementStrategy implements 
SqlPrepareStatementSt
 
             Object next = null;
             try {
-                boolean hasNext = hasParameter(nextParam, exchange, body);
+                boolean hasNext = SqlHelper.hasParameter(nextParam, exchange, 
body);
                 if (hasNext) {
-                    next = lookupParameter(nextParam, exchange, body);
+                    next = SqlHelper.lookupParameter(nextParam, exchange, 
body);
                     if (in && next != null) {
                         // if SQL IN we need to return an iterator that can 
iterate the parameter values
                         next = createInParameterIterator(next);
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java
index 28df085074f2..b1f6765908fe 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java
@@ -18,10 +18,15 @@ package org.apache.camel.component.sql;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
 import java.util.StringJoiner;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
 import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.ResourceHelper;
 
 public final class SqlHelper {
@@ -61,4 +66,52 @@ public final class SqlHelper {
         answer = sj.toString();
         return answer;
     }
+
+    public static Object lookupParameter(String nextParam, Exchange exchange, 
Object batchBody) {
+        Object body = batchBody != null ? batchBody : 
exchange.getMessage().getBody();
+        Map<?, ?> bodyMap = 
safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, 
exchange, body));
+        Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders());
+        Map<?, ?> variablesMap = safeMap(exchange.getVariables());
+
+        Object answer = null;
+        if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) 
&& nextParam.endsWith("}")) {
+            if (batchBody != null) {
+                // in batch mode then need to work on a copy of the original 
exchange and set the batch body
+                exchange = ExchangeHelper.createCopy(exchange, true);
+                exchange.getMessage().setBody(batchBody);
+            }
+            Expression exp = 
exchange.getContext().resolveLanguage("simple").createExpression(nextParam);
+            answer = exp.evaluate(exchange, Object.class);
+        } else if (bodyMap.containsKey(nextParam)) {
+            answer = bodyMap.get(nextParam);
+        } else if (headersMap.containsKey(nextParam)) {
+            answer = headersMap.get(nextParam);
+        } else if (variablesMap.containsKey(nextParam)) {
+            answer = variablesMap.get(nextParam);
+        }
+
+        return answer;
+    }
+
+    public static boolean hasParameter(String nextParam, Exchange exchange, 
Object body) {
+        Map<?, ?> bodyMap = 
safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, body));
+        Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders());
+        Map<?, ?> variablesMap = safeMap(exchange.getVariables());
+
+        if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) 
&& nextParam.endsWith("}")) {
+            return true;
+        } else if (bodyMap.containsKey(nextParam)) {
+            return true;
+        } else if (headersMap.containsKey(nextParam)) {
+            return true;
+        } else if (variablesMap.containsKey(nextParam)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static Map<?, ?> safeMap(Map<?, ?> map) {
+        return (map == null || map.isEmpty()) ? Collections.emptyMap() : map;
+    }
 }
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java
index 91c7a9018041..97bfbcd18888 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java
@@ -16,9 +16,8 @@
  */
 package org.apache.camel.component.sql.stored.template.ast;
 
-import java.util.Map;
-
 import org.apache.camel.Expression;
+import org.apache.camel.component.sql.SqlHelper;
 import 
org.apache.camel.component.sql.stored.template.generated.SSPTParserConstants;
 import org.apache.camel.component.sql.stored.template.generated.Token;
 
@@ -52,7 +51,7 @@ public class InOutParameter {
         } else if (SSPTParserConstants.PARAMETER_POS_TOKEN == 
valueSrcToken.kind) {
             //remove leading :#
             final String mapKey = valueSrcToken.toString().substring(2);
-            this.valueExtractor = (exchange, container) -> ((Map) 
container).get(mapKey);
+            this.valueExtractor = (exchange, container) -> 
SqlHelper.lookupParameter(mapKey, exchange, container);
         }
     }
 
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java
index 5d0de673cdad..b94af655e383 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java
@@ -16,9 +16,8 @@
  */
 package org.apache.camel.component.sql.stored.template.ast;
 
-import java.util.Map;
-
 import org.apache.camel.Expression;
+import org.apache.camel.component.sql.SqlHelper;
 import 
org.apache.camel.component.sql.stored.template.generated.SSPTParserConstants;
 import org.apache.camel.component.sql.stored.template.generated.Token;
 
@@ -52,7 +51,7 @@ public class InParameter {
         } else if (SSPTParserConstants.PARAMETER_POS_TOKEN == 
valueSrcToken.kind) {
             //remove leading :#
             final String mapKey = valueSrcToken.toString().substring(2);
-            this.valueExtractor = (exchange, container) -> ((Map) 
container).get(mapKey);
+            this.valueExtractor = (exchange, container) -> 
SqlHelper.lookupParameter(mapKey, exchange, container);
         }
     }
 
diff --git 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java
 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java
index da3aee86d580..3d0ba740bdf1 100644
--- 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java
+++ 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java
@@ -21,6 +21,7 @@ import java.sql.Types;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.component.sql.stored.template.TemplateParser;
 import org.apache.camel.component.sql.stored.template.ast.InOutParameter;
@@ -28,7 +29,9 @@ import 
org.apache.camel.component.sql.stored.template.ast.InParameter;
 import org.apache.camel.component.sql.stored.template.ast.OutParameter;
 import 
org.apache.camel.component.sql.stored.template.ast.ParseRuntimeException;
 import org.apache.camel.component.sql.stored.template.ast.Template;
+import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.support.DefaultExchange;
 import org.apache.camel.test.junit5.CamelTestSupport;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -153,13 +156,17 @@ public class ParserTest extends CamelTestSupport {
     }
 
     @Test
-    public void nableIssueSyntax() {
-        Map<String, String> params = new HashMap<>();
-        params.put("P_STR_IN", "a");
-        Template template
-                = 
parser.parseTemplate("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR(VARCHAR 
:#P_STR_IN,OUT VARCHAR P_STR_OUT)");
-        assertEquals("a", ((InParameter) 
template.getParameterList().get(0)).getValueExtractor().eval(null, params));
-        assertEquals("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR", 
template.getProcedureName());
+    public void nableIssueSyntax() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            Exchange dummy = new DefaultExchange(context);
+
+            Map<String, String> params = new HashMap<>();
+            params.put("P_STR_IN", "a");
+            Template template
+                    = 
parser.parseTemplate("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR(VARCHAR 
:#P_STR_IN,OUT VARCHAR P_STR_OUT)");
+            assertEquals("a", ((InParameter) 
template.getParameterList().get(0)).getValueExtractor().eval(dummy, params));
+            assertEquals("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR", 
template.getProcedureName());
+        }
     }
 
     @Test
diff --git 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java
 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java
new file mode 100644
index 000000000000..f7dd6f444ad0
--- /dev/null
+++ 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sql.stored;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class ProducerNamedParameterTest extends CamelTestSupport {
+
+    EmbeddedDatabase db;
+
+    @Override
+
+    public void doPreSetup() throws Exception {
+        db = new EmbeddedDatabaseBuilder()
+                .setName(getClass().getSimpleName())
+                .setType(EmbeddedDatabaseType.DERBY)
+                .addScript("sql/storedProcedureTest.sql").build();
+
+    }
+
+    @Override
+    public void doPostTearDown() throws Exception {
+        if (db != null) {
+            db.shutdown();
+        }
+    }
+
+    @Test
+    public void shouldExecuteStoredProcedure() throws InterruptedException {
+        MockEndpoint mock = getMockEndpoint("mock:query");
+        mock.expectedMessageCount(1);
+
+        Map<String, Object> headers = new HashMap<>();
+        headers.put("num1", 1);
+        headers.put("num2", 2);
+        template.requestBodyAndHeaders("direct:query", null, headers);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        Exchange exchange = mock.getExchanges().get(0);
+
+        assertEquals(Integer.valueOf(-1), 
exchange.getIn().getBody(Map.class).get("resultofsub"));
+        
assertNotNull(exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // required for the sql component
+                getContext().getComponent("sql-stored", 
SqlStoredComponent.class).setDataSource(db);
+
+                from("direct:query").to("sql-stored:SUBNUMBERS(INTEGER 
:#num1,INTEGER :#num2,OUT INTEGER resultofsub)")
+                        .to("mock:query");
+            }
+        };
+    }
+
+}

Reply via email to