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 fc6690a2b821 CAMEL-24801: camel-sql - sql-stored: allow parentheses
inside simple expressions in the template grammar
fc6690a2b821 is described below
commit fc6690a2b82161b4e413b4820cc3cf9436e891fe
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Sep 17 20:50:42 2026 +0200
CAMEL-24801: camel-sql - sql-stored: allow parentheses inside simple
expressions in the template grammar
SIMPLE_EXP_TOKEN in sspt.jj did not accept ( and ), so a template such
as MYPROC(TIMESTAMP :#startDate, INTEGER ${val(1)}) failed with a
ParseException: the lexer matched ${val as an IDENTIFIER and choked on
the parenthesis. Evaluation was never the problem, as the whole token
is passed to the simple language.
Add ( and ) to the token character set and regenerate the token
manager. Longest-match keeps this safe because the token must end
with }, so ${val(1)}) stops before the procedure's closing parenthesis.
Commas are deliberately not added: they separate the procedure
parameters, so multi-argument and nested functions stay unsupported
and the docs say so.
ParserTest covers IN/INOUT parameters with single-argument functions
and rejects a function with several arguments;
ProducerSimpleFunctionTest runs ${val(2)} against HSQLDB.
Closes #26560
Co-Authored-By: Claude <[email protected]>
---
.../camel/catalog/docs/sql-stored-component.adoc | 5 ++
.../template/generated/SSPTParserTokenManager.java | 2 +-
.../src/main/docs/sql-stored-component.adoc | 5 ++
.../component/sql/stored/template/grammar/sspt.jj | 2 +-
.../camel/component/sql/stored/ParserTest.java | 30 ++++++++
.../sql/stored/ProducerSimpleFunctionTest.java | 81 ++++++++++++++++++++++
6 files changed, 123 insertions(+), 2 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
index db0b164709ff..7a5b45e517d6 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
@@ -119,6 +119,11 @@ 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.
+The Simple expression may use functions with a single argument, such as
`${val(1)}` or `${bodyAs(String)}`.
+However, the template is not a full Simple expression: functions with
multiple, comma-separated arguments (such as `${replace(a,b)}`)
+and nested functions are not supported, as the comma is used to separate the
parameters of the stored procedure.
+If you need such expressions, then compute the value beforehand, for example
into a header, and refer to it with `${header.name}`.
+
When using named parameters, Camel will look up the names in the given
precedence:
1. from a xref:languages:simple-language.adoc[Simple] expressions
diff --git
a/components/camel-sql/src/generated/java/org/apache/camel/component/sql/stored/template/generated/SSPTParserTokenManager.java
b/components/camel-sql/src/generated/java/org/apache/camel/component/sql/stored/template/generated/SSPTParserTokenManager.java
index 5640f750c7c0..0a77969e6687 100644
---
a/components/camel-sql/src/generated/java/org/apache/camel/component/sql/stored/template/generated/SSPTParserTokenManager.java
+++
b/components/camel-sql/src/generated/java/org/apache/camel/component/sql/stored/template/generated/SSPTParserTokenManager.java
@@ -308,7 +308,7 @@ private int jjMoveNfa_0(int startState, int curPos)
{ jjCheckNAdd(4); }
break;
case 6:
- if ((0x7ff609d00000000L & l) != 0L)
+ if ((0x7ff639d00000000L & l) != 0L)
{ jjAddStates(6, 7); }
break;
case 9:
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 db0b164709ff..7a5b45e517d6 100644
--- a/components/camel-sql/src/main/docs/sql-stored-component.adoc
+++ b/components/camel-sql/src/main/docs/sql-stored-component.adoc
@@ -119,6 +119,11 @@ 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.
+The Simple expression may use functions with a single argument, such as
`${val(1)}` or `${bodyAs(String)}`.
+However, the template is not a full Simple expression: functions with
multiple, comma-separated arguments (such as `${replace(a,b)}`)
+and nested functions are not supported, as the comma is used to separate the
parameters of the stored procedure.
+If you need such expressions, then compute the value beforehand, for example
into a header, and refer to it with `${header.name}`.
+
When using named parameters, Camel will look up the names in the given
precedence:
1. from a xref:languages:simple-language.adoc[Simple] expressions
diff --git
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/grammar/sspt.jj
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/grammar/sspt.jj
index 85d5d1d87a67..ed47afeb44ee 100644
---
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/grammar/sspt.jj
+++
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/grammar/sspt.jj
@@ -230,7 +230,7 @@ TOKEN: {
}
TOKEN : {
- <SIMPLE_EXP_TOKEN: "${"(<LETTER>|<DIGIT> | <SPECIAL> | " ")+ "}">
+ <SIMPLE_EXP_TOKEN: "${"(<LETTER>|<DIGIT> | <SPECIAL> | "(" | ")" | " ")+
"}">
}
TOKEN : {
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 c2be990a25b4..101fb9e64b26 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
@@ -112,6 +112,36 @@ class ParserTest extends CamelTestSupport {
assertEquals(3, ((InParameter)
template.getParameterList().get(1)).getValueExtractor().eval(exchange, null));
}
+ @Test
+ public void simpleFunctionWithParentheses() {
+ Exchange exchange = createExchangeWithBody(42);
+ exchange.getIn().setHeader("bar", 3);
+ Template template = parser.parseTemplate(
+ "ADDNUMBERS2(INTEGER ${val(1)},VARCHAR ${bodyAs(String)},INOUT
INTEGER ${val(7)} inout1,OUT INTEGER out1)");
+
+ assertEquals(4, template.getParameterList().size());
+ assertEquals("1", ((InParameter)
template.getParameterList().get(0)).getValueExtractor().eval(exchange, null));
+ assertEquals("42", ((InParameter)
template.getParameterList().get(1)).getValueExtractor().eval(exchange, null));
+ assertEquals("7", ((InOutParameter)
template.getParameterList().get(2)).getValueExtractor().eval(exchange, null));
+ assertEquals("out1", ((OutParameter)
template.getParameterList().get(3)).getOutValueMapKey());
+ }
+
+ @Test
+ public void simpleFunctionAsLastParameter() {
+ // the closing parenthesis of the function must not be confused with
the end of the procedure
+ Exchange exchange = createExchangeWithBody(1);
+ Template template = parser.parseTemplate("ADDNUMBERS2(INTEGER
${val(1)} )");
+ assertEquals(1, template.getParameterList().size());
+ assertEquals("1", ((InParameter)
template.getParameterList().get(0)).getValueExtractor().eval(exchange, null));
+ }
+
+ @Test
+ public void multiArgFunctionShouldFail() {
+ // the comma separates the procedure parameters, so functions with
several arguments are not supported
+ assertThrows(ParseRuntimeException.class,
+ () -> parser.parseTemplate("ADDNUMBERS2(VARCHAR
${replace(a,b)})"));
+ }
+
@Test
public void vendorSpecificPositiveSqlType() {
Template template = parser.parseTemplate("ADDNUMBERS2(1342
${header.foo})");
diff --git
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerSimpleFunctionTest.java
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerSimpleFunctionTest.java
new file mode 100644
index 000000000000..07b00f9bef51
--- /dev/null
+++
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerSimpleFunctionTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.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;
+
+/**
+ * Simple functions with a single argument, such as ${val(1)}, can be used as
parameter value in the template.
+ */
+public class ProducerSimpleFunctionTest extends CamelTestSupport {
+
+ EmbeddedDatabase db;
+
+ @Override
+ public void doPreSetup() throws Exception {
+ db = new EmbeddedDatabaseBuilder()
+ .setName(getClass().getSimpleName())
+ .setType(EmbeddedDatabaseType.HSQL)
+ .addScript("sql/storedProcedureTest.sql").build();
+ }
+
+ @Override
+ public void doPostTearDown() throws Exception {
+ if (db != null) {
+ db.shutdown();
+ }
+ }
+
+ @Test
+ public void shouldExecuteStoredProcedureWithSimpleFunction() throws
InterruptedException {
+ MockEndpoint mock = getMockEndpoint("mock:query");
+ mock.expectedMessageCount(1);
+
+ template.requestBodyAndHeader("direct:query", null, "num1", 5);
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ Exchange exchange = mock.getExchanges().get(0);
+ assertEquals(Integer.valueOf(3),
exchange.getIn().getBody(Map.class).get("resultofsub"));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ getContext().getComponent("sql-stored",
SqlStoredComponent.class).setDataSource(db);
+
+ from("direct:query")
+ .to("sql-stored:SUBNUMBERS(INTEGER
${headers.num1},INTEGER ${val(2)},OUT INTEGER resultofsub)")
+ .to("mock:query");
+ }
+ };
+ }
+
+}