You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2025/12/10 13:20:24 UTC

(camel) branch ss created (now da140f175616)

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

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


      at da140f175616 CAMEL-22770: camel-sql - Stored producer should lookup named parameters like regular producer do

This branch includes the following new commits:

     new da140f175616 CAMEL-22770: camel-sql - Stored producer should lookup named parameters like regular producer do

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-22770: camel-sql - Stored producer should lookup named parameters like regular producer do

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit da140f17561643dda7d09b6e75d484d731874520
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Dec 10 14:20:09 2025 +0100

    CAMEL-22770: camel-sql - Stored producer should lookup named parameters like regular producer do
---
 .../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");
+            }
+        };
+    }
+
+}