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");
+ }
+ };
+ }
+
+}