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 {

Reply via email to