This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch sql in repository https://gitbox.apache.org/repos/asf/camel.git
commit d1f1bd23c0a2c4b74cf5e4be5a4264c3d5bcdbac Author: Claus Ibsen <[email protected]> AuthorDate: Sat Nov 1 12:33:47 2025 +0100 CAMEL-22637: camel-sql - Allow to specify DataSource dynamic in producer --- .../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 {
