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 53b6666cd4a1 CAMEL-22637: camel-sql - Allow to specify DataSource
dynamic in producer (#19779)
53b6666cd4a1 is described below
commit 53b6666cd4a1a364e74c14d170674afef0a97351
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 1 13:10:23 2025 +0100
CAMEL-22637: camel-sql - Allow to specify DataSource dynamic in producer
(#19779)
---
.../org/apache/camel/catalog/components/sql.json | 3 +-
.../org/apache/camel/component/sql/sql.json | 3 +-
.../camel-sql/src/main/docs/sql-component.adoc | 21 +++
.../apache/camel/component/sql/SqlConstants.java | 5 +
.../apache/camel/component/sql/SqlProducer.java | 14 +-
.../sql/SqlProducerCustomDataSourceTest.java | 144 +++++++++++++++++++++
.../sql/createAndPopulateDatabaseCopy.sql | 27 ++++
.../test/resources/sql/selectProjectsInCopy.sql | 22 ++++
.../endpoint/dsl/SqlEndpointBuilderFactory.java | 13 ++
9 files changed, 248 insertions(+), 4 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
index 35f28c135cde..b4df41767937 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
@@ -44,7 +44,8 @@
"CamelSqlGeneratedColumns": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"String[] or int[]", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set it to specify the expected
generated columns", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_COLUMNS" },
"CamelSqlGeneratedKeysRowCount": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The number of rows in the header that
contains generated keys.", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_KEYS_ROW_COUNT" },
"CamelSqlGeneratedKeyRows": { "index": 6, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"List<Map<String, Object>>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Rows that contains the
generated keys (a list of maps of keys).", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_KEYS_DATA" },
- "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The SQL parameters when using the option
useMessageBodyForSql", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" }
+ "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The SQL parameters when using the option
useMessageBodyForSql", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" },
+ "CamelSqlDataSource": { "index": 8, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"javax.sql.DataSource", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "To use a specific
DataSource to operate on another database than the pre configured DataSource on
the component", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_DATA_SOURCE" }
},
"properties": {
"query": { "index": 0, "kind": "path", "displayName": "Query", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "supportFileReference": true, "largeInput": true,
"inputLanguage": "sql", "description": "Sets the SQL query to perform. You can
externalize the query by using file: or classpath: as prefix and specify the
location of the file." },
diff --git
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
index 35f28c135cde..b4df41767937 100644
---
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
+++
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
@@ -44,7 +44,8 @@
"CamelSqlGeneratedColumns": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"String[] or int[]", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set it to specify the expected
generated columns", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_COLUMNS" },
"CamelSqlGeneratedKeysRowCount": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The number of rows in the header that
contains generated keys.", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_KEYS_ROW_COUNT" },
"CamelSqlGeneratedKeyRows": { "index": 6, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"List<Map<String, Object>>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Rows that contains the
generated keys (a list of maps of keys).", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_KEYS_DATA" },
- "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The SQL parameters when using the option
useMessageBodyForSql", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" }
+ "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The SQL parameters when using the option
useMessageBodyForSql", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" },
+ "CamelSqlDataSource": { "index": 8, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"javax.sql.DataSource", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "To use a specific
DataSource to operate on another database than the pre configured DataSource on
the component", "constantName":
"org.apache.camel.component.sql.SqlConstants#SQL_DATA_SOURCE" }
},
"properties": {
"query": { "index": 0, "kind": "path", "displayName": "Query", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "supportFileReference": true, "largeInput": true,
"inputLanguage": "sql", "description": "Sets the SQL query to perform. You can
externalize the query by using file: or classpath: as prefix and specify the
location of the file." },
diff --git a/components/camel-sql/src/main/docs/sql-component.adoc
b/components/camel-sql/src/main/docs/sql-component.adoc
index 4a3eae27a4f4..37b4565fe529 100644
--- a/components/camel-sql/src/main/docs/sql-component.adoc
+++ b/components/camel-sql/src/main/docs/sql-component.adoc
@@ -371,6 +371,27 @@ where project in (:#in:${body})
order by id
----
+=== Using another database / DataSource
+
+The producer supports to use a different `javax.sql.DataSource` than the
default configured on the component,
+to make it possible to dynamic query another database. This requires to
specify a header in the `Exchange` that contains
+the custom data source.
+
+For example in the code snippet below, we set the header `CamelSqlDataSource`
with a custom `javax.sql.DataSource` instance.
+
+[source,java]
+----
+DataSource custom = ...
+
+from("direct:query")
+ .setHeader("CamelSqlDataSource", custom)
+ .to("sql:query.sql")
+ .to("bean:processData");
+----
+
+NOTE: Remember to remove the header if you use SQL component later during
routing and don't want to keep using the custom
+data source.
+
=== Using the JDBC-based idempotent repository
In this section, we will use the JDBC-based idempotent repository.
diff --git
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
index de28beb373a0..d6e81bb8bec1 100644
---
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
+++
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
@@ -80,6 +80,11 @@ public final class SqlConstants {
@Metadata(label = "producer", javaType = "Iterator")
public static final String SQL_PARAMETERS = "CamelSqlParameters";
+ @Metadata(label = "producer",
+ description = "To use a specific DataSource to operate on
another database than the pre configured DataSource on the component",
+ javaType = "javax.sql.DataSource")
+ public static final String SQL_DATA_SOURCE = "CamelSqlDataSource";
+
private SqlConstants() {
// Utility class
}
diff --git
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
index 53c21d34285e..32e5a1dba5b4 100644
---
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
+++
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
@@ -26,6 +26,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import javax.sql.DataSource;
+
import org.apache.camel.Exchange;
import org.apache.camel.support.DefaultProducer;
import org.apache.camel.support.ResourceHelper;
@@ -149,7 +151,7 @@ public class SqlProducer extends DefaultProducer {
Exchange exchange, PreparedStatementCreator statementCreator,
String sql, String preparedQuery, Boolean
shouldRetrieveGeneratedKeys) {
LOG.trace("jdbcTemplate.execute: {}", preparedQuery);
- return jdbcTemplate.execute(statementCreator, new
PreparedStatementCallback<Object>() {
+ return fetchJdbcTemplate(exchange).execute(statementCreator, new
PreparedStatementCallback<Object>() {
public Object doInPreparedStatement(PreparedStatement ps) throws
SQLException {
Object data = null;
ResultSet rs = null;
@@ -249,7 +251,7 @@ public class SqlProducer extends DefaultProducer {
ResultSet rs = null;
try {
- con = jdbcTemplate.getDataSource().getConnection();
+ con = fetchJdbcTemplate(exchange).getDataSource().getConnection();
ps = statementCreator.createPreparedStatement(con);
ResultSetIterator iterator = null;
@@ -320,6 +322,14 @@ public class SqlProducer extends DefaultProducer {
}
}
+ protected JdbcTemplate fetchJdbcTemplate(Exchange exchange) {
+ DataSource ds =
exchange.getMessage().getHeader(SqlConstants.SQL_DATA_SOURCE, DataSource.class);
+ if (ds != null) {
+ return new JdbcTemplate(ds);
+ }
+ return jdbcTemplate;
+ }
+
public void setParametersCount(int parametersCount) {
this.parametersCount = parametersCount;
}
diff --git
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerCustomDataSourceTest.java
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerCustomDataSourceTest.java
new file mode 100644
index 000000000000..34c098d5a360
--- /dev/null
+++
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerCustomDataSourceTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+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;
+
+public class SqlProducerCustomDataSourceTest extends CamelTestSupport {
+
+ EmbeddedDatabase db;
+ EmbeddedDatabase db2;
+
+ @Override
+
+ public void doPreSetup() throws Exception {
+ db = new EmbeddedDatabaseBuilder()
+ .setName(getClass().getSimpleName())
+ .setType(EmbeddedDatabaseType.H2)
+ .addScript("sql/createAndPopulateDatabase.sql").build();
+ db2 = new EmbeddedDatabaseBuilder()
+ .setName(getClass().getSimpleName())
+ .setType(EmbeddedDatabaseType.H2)
+ .addScript("sql/createAndPopulateDatabaseCopy.sql").build();
+
+ }
+
+ @Override
+ public void doPostTearDown() throws Exception {
+ if (db != null) {
+ db.shutdown();
+ }
+ if (db2 != null) {
+ db2.shutdown();
+ }
+ }
+
+ @Test
+ public void testCustomDataSource() throws InterruptedException {
+ MockEndpoint mock = getMockEndpoint("mock:query");
+ mock.expectedMessageCount(1);
+
+ List<String> names = new ArrayList<>();
+ names.add("Camel");
+ names.add("AMQ");
+
+ // use default data-source
+
+ template.requestBodyAndHeader("direct:query", "Hi there!", "names",
names);
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ List list =
mock.getReceivedExchanges().get(0).getIn().getBody(List.class);
+ assertEquals(2, list.size());
+ Map row = (Map) list.get(0);
+ assertEquals("Camel", row.get("PROJECT"));
+ row = (Map) list.get(1);
+ assertEquals("AMQ", row.get("PROJECT"));
+
+ // use custom data-source to use another database
+
+ MockEndpoint.resetMocks(context);
+
+ mock = getMockEndpoint("mock:query");
+ mock.expectedMessageCount(1);
+
+ template.requestBodyAndHeaders("direct:queryCopy", "Hi there!",
+ Map.of("names", names, SqlConstants.SQL_DATA_SOURCE, db2));
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ list = mock.getReceivedExchanges().get(0).getIn().getBody(List.class);
+ assertEquals(8, list.size());
+ row = (Map) list.get(0);
+ assertEquals("Camel", row.get("PROJECT"));
+ assertEquals("ASF", row.get("LICENSE"));
+ row = (Map) list.get(1);
+ assertEquals("Camel", row.get("PROJECT"));
+ assertEquals("XXX", row.get("LICENSE"));
+ row = (Map) list.get(2);
+ assertEquals("Camel", row.get("PROJECT"));
+ assertEquals("YYY", row.get("LICENSE"));
+ row = (Map) list.get(3);
+ assertEquals("Camel", row.get("PROJECT"));
+ assertEquals("ZZZ", row.get("LICENSE"));
+ row = (Map) list.get(4);
+ assertEquals("AMQ", row.get("PROJECT"));
+ assertEquals("ASF", row.get("LICENSE"));
+ row = (Map) list.get(5);
+ assertEquals("AMQ", row.get("PROJECT"));
+ assertEquals("XXX", row.get("LICENSE"));
+ row = (Map) list.get(6);
+ assertEquals("AMQ", row.get("PROJECT"));
+ assertEquals("YYY", row.get("LICENSE"));
+ row = (Map) list.get(7);
+ assertEquals("AMQ", row.get("PROJECT"));
+ assertEquals("ZZZ", row.get("LICENSE"));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // required for the sql component
+ getContext().getComponent("sql",
SqlComponent.class).setDataSource(db);
+
+ from("direct:query")
+ .to("sql:classpath:sql/selectProjectsIn.sql")
+ .to("log:query")
+ .to("mock:query");
+
+ from("direct:queryCopy")
+ .to("sql:classpath:sql/selectProjectsInCopy.sql")
+ .to("log:query")
+ .to("mock:query");
+ }
+ };
+ }
+}
diff --git
a/components/camel-sql/src/test/resources/sql/createAndPopulateDatabaseCopy.sql
b/components/camel-sql/src/test/resources/sql/createAndPopulateDatabaseCopy.sql
new file mode 100644
index 000000000000..77b1a609f92a
--- /dev/null
+++
b/components/camel-sql/src/test/resources/sql/createAndPopulateDatabaseCopy.sql
@@ -0,0 +1,27 @@
+--
+-- 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.
+--
+
+create table copy_projects (id integer primary key, project varchar(10),
license varchar(5));
+insert into copy_projects values (1, 'Camel', 'ASF');
+insert into copy_projects values (2, 'Camel', 'XXX');
+insert into copy_projects values (3, 'Camel', 'YYY');
+insert into copy_projects values (4, 'Camel', 'ZZZ');
+insert into copy_projects values (5, 'AMQ', 'ASF');
+insert into copy_projects values (6, 'AMQ', 'XXX');
+insert into copy_projects values (7, 'AMQ', 'YYY');
+insert into copy_projects values (8, 'AMQ', 'ZZZ');
+insert into copy_projects values (9, 'Linux', 'XXX');
diff --git
a/components/camel-sql/src/test/resources/sql/selectProjectsInCopy.sql
b/components/camel-sql/src/test/resources/sql/selectProjectsInCopy.sql
new file mode 100644
index 000000000000..39c14cc5ad17
--- /dev/null
+++ b/components/camel-sql/src/test/resources/sql/selectProjectsInCopy.sql
@@ -0,0 +1,22 @@
+--
+-- 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.
+--
+
+-- this is a comment
+select *
+from copy_projects
+where project in (:#in:names)
+order by id
\ No newline at end of file
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
index a3ba95b6beab..3f1703abdf86 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
@@ -2730,6 +2730,19 @@ public interface SqlEndpointBuilderFactory {
public String sqlParameters() {
return "CamelSqlParameters";
}
+ /**
+ * To use a specific DataSource to operate on another database than the
+ * pre configured DataSource on the component.
+ *
+ * The option is a: {@code javax.sql.DataSource} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code SqlDataSource}.
+ */
+ public String sqlDataSource() {
+ return "CamelSqlDataSource";
+ }
}
static SqlEndpointBuilder endpointBuilder(String componentName, String
path) {
class SqlEndpointBuilderImpl extends AbstractEndpointBuilder
implements SqlEndpointBuilder, AdvancedSqlEndpointBuilder {