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 9cab3498630a CAMEL-26272: camel-duckdb - honour the
CamelDuckDbDatabasePath header only with allowDatabasePathFromHeader=true, and
document the headers that override the endpoint (#29081)
9cab3498630a is described below
commit 9cab3498630a3ed1a7776bcf1ab606782959c851
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:11:05 2026 +0100
CAMEL-26272: camel-duckdb - honour the CamelDuckDbDatabasePath header only
with allowDatabasePathFromHeader=true, and document the headers that override
the endpoint (#29081)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/components/duckdb.json | 5 +-
.../camel/catalog/docs/duckdb-component.adoc | 21 +++++-
.../component/duckdb/DuckDbEndpointConfigurer.java | 6 ++
.../component/duckdb/DuckDbEndpointUriFactory.java | 3 +-
.../org/apache/camel/component/duckdb/duckdb.json | 5 +-
.../src/main/docs/duckdb-component.adoc | 21 +++++-
.../camel/component/duckdb/DuckDbConstants.java | 6 +-
.../camel/component/duckdb/DuckDbEndpoint.java | 23 +++++--
.../camel/component/duckdb/DuckDbProducer.java | 4 +-
.../camel/component/duckdb/DuckDbProducerTest.java | 78 ++++++++++++++++++++--
.../java/org/apache/camel/util/SecurityUtils.java | 3 +
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 11 +++
.../endpoint/dsl/DuckDbEndpointBuilderFactory.java | 46 ++++++++++++-
13 files changed, 207 insertions(+), 25 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/duckdb.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/duckdb.json
index a3915689bcac..6cf60bdcd0a0 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/duckdb.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/duckdb.json
@@ -32,7 +32,7 @@
},
"headers": {
"CamelDuckDbOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the operation configured on the endpoint.",
"constantName": "org.apache.camel.component.duckdb.DuckDbConstants#OPERATION" },
- "CamelDuckDbDatabasePath": { "index": 1, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the database path configured on the
endpoint.", "constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#DATABASE_PATH" },
+ "CamelDuckDbDatabasePath": { "index": 1, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the database of the endpoint for one message:
a short-lived connection to the DuckDB file at this path is opened, and DuckDB
creates the file when it does not exist. This header is ignored unless the
endpoint enables allowDataba [...]
"CamelDuckDbTable": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the target table configured on the endpoint.",
"constantName": "org.apache.camel.component.duckdb.DuckDbConstants#TABLE" },
"CamelDuckDbQuery": { "index": 3, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the SQL for query or execute operations. Takes
precedence over the query option and the message body.", "constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#QUERY" },
"CamelDuckDbRowsWritten": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The number of rows written or affected.",
"constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#ROWS_WRITTEN" },
@@ -53,6 +53,7 @@
"readOnly": { "index": 6, "kind": "parameter", "displayName": "Read Only",
"group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "description": "Open the embedded database file
in read-only mode. Only applies to connections created by this endpoint from
databasePath or jdbcUrl, and requires an existing database file." },
"resultFormat": { "index": 7, "kind": "parameter", "displayName": "Result
Format", "group": "producer", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.duckdb.DuckDbResultFormat", "enum": [
"LIST_MAP", "JSON" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "LIST_MAP", "description": "How query results are returned:
LIST_MAP (List of Map) or JSON array string." },
"table": { "index": 8, "kind": "parameter", "displayName": "Table",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Target table for insert and copy operations.
Can be overridden per message with the CamelDuckDbTable header." },
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "allowDatabasePathFromHeader": { "index": 10, "kind": "parameter",
"displayName": "Allow Database Path From Header", "group": "security", "label":
"producer,security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false,
"description": "Whether to allow overriding the database of the endpoint with
the CamelDuckDbDatabasePath header, which opens [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/duckdb-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/duckdb-component.adoc
index 727bc281c342..5dd2297012c0 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/duckdb-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/duckdb-component.adoc
@@ -66,9 +66,24 @@ A statement that returns rows fails with `execute`, and a
statement that returns
`query`; the error says which operation to use instead. DuckDB has run the
statement when the error is raised.
The endpoint reuses one JDBC connection for its configured `databasePath` /
`jdbcUrl` and serializes access for
-thread safety. Prefer a shared `DataSource` when you need concurrent
producers. Set the
-`CamelDuckDbDatabasePath` header to open a short-lived connection to a
different database file for a single
-message.
+thread safety. Prefer a shared `DataSource` when you need concurrent producers.
+
+== Headers that override the endpoint
+
+These headers take the place of an endpoint option for one message:
+
+* `CamelDuckDbOperation` replaces the `operation` option.
+* `CamelDuckDbQuery` replaces the `query` option and the message body as the
SQL of `execute` and `query`.
+* `CamelDuckDbTable` replaces the `table` option of `insert` and `copy`.
+
+So a message that carries them chooses the operation, the SQL that runs and
the table that is written to.
+
+The `CamelDuckDbDatabasePath` header is ignored unless the endpoint sets
`allowDatabasePathFromHeader=true`. With
+that option, the header opens a short-lived connection to the DuckDB file at
that path for one message, instead of
+the `databasePath`, `jdbcUrl` or `DataSource` of the endpoint, and DuckDB
creates the file when it does not exist.
+
+When the messages come from an untrusted producer through a consumer that does
not filter out `Camel*` headers,
+remove these headers with `removeHeaders("CamelDuckDb*")` before the `duckdb`
endpoint.
== Example
diff --git
a/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointConfigurer.java
b/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointConfigurer.java
index 408807ca5808..1795ccde9b86 100644
---
a/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointConfigurer.java
+++
b/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointConfigurer.java
@@ -24,6 +24,8 @@ public class DuckDbEndpointConfigurer extends
PropertyConfigurerSupport implemen
public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
DuckDbEndpoint target = (DuckDbEndpoint) obj;
switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+ case "allowdatabasepathfromheader":
+ case "allowDatabasePathFromHeader":
target.setAllowDatabasePathFromHeader(property(camelContext, boolean.class,
value)); return true;
case "batchsize":
case "batchSize": target.setBatchSize(property(camelContext,
int.class, value)); return true;
case "format": target.setFormat(property(camelContext,
java.lang.String.class, value)); return true;
@@ -45,6 +47,8 @@ public class DuckDbEndpointConfigurer extends
PropertyConfigurerSupport implemen
@Override
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+ case "allowdatabasepathfromheader":
+ case "allowDatabasePathFromHeader": return boolean.class;
case "batchsize":
case "batchSize": return int.class;
case "format": return java.lang.String.class;
@@ -67,6 +71,8 @@ public class DuckDbEndpointConfigurer extends
PropertyConfigurerSupport implemen
public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
DuckDbEndpoint target = (DuckDbEndpoint) obj;
switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+ case "allowdatabasepathfromheader":
+ case "allowDatabasePathFromHeader": return
target.isAllowDatabasePathFromHeader();
case "batchsize":
case "batchSize": return target.getBatchSize();
case "format": return target.getFormat();
diff --git
a/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointUriFactory.java
b/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointUriFactory.java
index f65906f70adf..9ecdadd310a8 100644
---
a/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointUriFactory.java
+++
b/components/camel-duckdb/src/generated/java/org/apache/camel/component/duckdb/DuckDbEndpointUriFactory.java
@@ -24,7 +24,8 @@ public class DuckDbEndpointUriFactory extends
org.apache.camel.support.component
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(10);
+ Set<String> props = new HashSet<>(11);
+ props.add("allowDatabasePathFromHeader");
props.add("batchSize");
props.add("databasePath");
props.add("format");
diff --git
a/components/camel-duckdb/src/generated/resources/META-INF/org/apache/camel/component/duckdb/duckdb.json
b/components/camel-duckdb/src/generated/resources/META-INF/org/apache/camel/component/duckdb/duckdb.json
index a3915689bcac..6cf60bdcd0a0 100644
---
a/components/camel-duckdb/src/generated/resources/META-INF/org/apache/camel/component/duckdb/duckdb.json
+++
b/components/camel-duckdb/src/generated/resources/META-INF/org/apache/camel/component/duckdb/duckdb.json
@@ -32,7 +32,7 @@
},
"headers": {
"CamelDuckDbOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the operation configured on the endpoint.",
"constantName": "org.apache.camel.component.duckdb.DuckDbConstants#OPERATION" },
- "CamelDuckDbDatabasePath": { "index": 1, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the database path configured on the
endpoint.", "constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#DATABASE_PATH" },
+ "CamelDuckDbDatabasePath": { "index": 1, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the database of the endpoint for one message:
a short-lived connection to the DuckDB file at this path is opened, and DuckDB
creates the file when it does not exist. This header is ignored unless the
endpoint enables allowDataba [...]
"CamelDuckDbTable": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the target table configured on the endpoint.",
"constantName": "org.apache.camel.component.duckdb.DuckDbConstants#TABLE" },
"CamelDuckDbQuery": { "index": 3, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Overrides the SQL for query or execute operations. Takes
precedence over the query option and the message body.", "constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#QUERY" },
"CamelDuckDbRowsWritten": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The number of rows written or affected.",
"constantName":
"org.apache.camel.component.duckdb.DuckDbConstants#ROWS_WRITTEN" },
@@ -53,6 +53,7 @@
"readOnly": { "index": 6, "kind": "parameter", "displayName": "Read Only",
"group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "description": "Open the embedded database file
in read-only mode. Only applies to connections created by this endpoint from
databasePath or jdbcUrl, and requires an existing database file." },
"resultFormat": { "index": 7, "kind": "parameter", "displayName": "Result
Format", "group": "producer", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.duckdb.DuckDbResultFormat", "enum": [
"LIST_MAP", "JSON" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "LIST_MAP", "description": "How query results are returned:
LIST_MAP (List of Map) or JSON array string." },
"table": { "index": 8, "kind": "parameter", "displayName": "Table",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Target table for insert and copy operations.
Can be overridden per message with the CamelDuckDbTable header." },
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
+ "allowDatabasePathFromHeader": { "index": 10, "kind": "parameter",
"displayName": "Allow Database Path From Header", "group": "security", "label":
"producer,security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false,
"description": "Whether to allow overriding the database of the endpoint with
the CamelDuckDbDatabasePath header, which opens [...]
}
}
diff --git a/components/camel-duckdb/src/main/docs/duckdb-component.adoc
b/components/camel-duckdb/src/main/docs/duckdb-component.adoc
index 727bc281c342..5dd2297012c0 100644
--- a/components/camel-duckdb/src/main/docs/duckdb-component.adoc
+++ b/components/camel-duckdb/src/main/docs/duckdb-component.adoc
@@ -66,9 +66,24 @@ A statement that returns rows fails with `execute`, and a
statement that returns
`query`; the error says which operation to use instead. DuckDB has run the
statement when the error is raised.
The endpoint reuses one JDBC connection for its configured `databasePath` /
`jdbcUrl` and serializes access for
-thread safety. Prefer a shared `DataSource` when you need concurrent
producers. Set the
-`CamelDuckDbDatabasePath` header to open a short-lived connection to a
different database file for a single
-message.
+thread safety. Prefer a shared `DataSource` when you need concurrent producers.
+
+== Headers that override the endpoint
+
+These headers take the place of an endpoint option for one message:
+
+* `CamelDuckDbOperation` replaces the `operation` option.
+* `CamelDuckDbQuery` replaces the `query` option and the message body as the
SQL of `execute` and `query`.
+* `CamelDuckDbTable` replaces the `table` option of `insert` and `copy`.
+
+So a message that carries them chooses the operation, the SQL that runs and
the table that is written to.
+
+The `CamelDuckDbDatabasePath` header is ignored unless the endpoint sets
`allowDatabasePathFromHeader=true`. With
+that option, the header opens a short-lived connection to the DuckDB file at
that path for one message, instead of
+the `databasePath`, `jdbcUrl` or `DataSource` of the endpoint, and DuckDB
creates the file when it does not exist.
+
+When the messages come from an untrusted producer through a consumer that does
not filter out `Camel*` headers,
+remove these headers with `removeHeaders("CamelDuckDb*")` before the `duckdb`
endpoint.
== Example
diff --git
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbConstants.java
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbConstants.java
index ea3c5179a8b4..2be7f8b67d10 100644
---
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbConstants.java
+++
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbConstants.java
@@ -22,7 +22,11 @@ public final class DuckDbConstants {
@Metadata(description = "Overrides the operation configured on the
endpoint.", javaType = "String")
public static final String OPERATION = "CamelDuckDbOperation";
- @Metadata(description = "Overrides the database path configured on the
endpoint.", javaType = "String")
+ @Metadata(description = "Overrides the database of the endpoint for one
message: a short-lived connection to the"
+ + " DuckDB file at this path is opened, and DuckDB
creates the file when it does not exist."
+ + " This header is ignored unless the endpoint
enables allowDatabasePathFromHeader=true"
+ + " (disabled by default).",
+ javaType = "String")
public static final String DATABASE_PATH = "CamelDuckDbDatabasePath";
@Metadata(description = "Overrides the target table configured on the
endpoint.", javaType = "String")
public static final String TABLE = "CamelDuckDbTable";
diff --git
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbEndpoint.java
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbEndpoint.java
index fd8a46099d9b..8e6afe2ec117 100644
---
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbEndpoint.java
+++
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbEndpoint.java
@@ -101,6 +101,13 @@ public class DuckDbEndpoint extends DefaultEndpoint {
@UriParam(defaultValue = "LIST_MAP",
description = "How query results are returned: LIST_MAP (List of
Map) or JSON array string.")
private DuckDbResultFormat resultFormat = DuckDbResultFormat.LIST_MAP;
+ @UriParam(defaultValue = "false", label = "producer,security", security =
"insecure:dev", insecureValue = "true",
+ description = "Whether to allow overriding the database of the
endpoint with the CamelDuckDbDatabasePath"
+ + " header, which opens a short-lived connection
to the DuckDB file at that path (DuckDB"
+ + " creates the file when it does not exist)
instead of using the databasePath, jdbcUrl or"
+ + " DataSource of the endpoint. Disabled by
default; enable it only when the header source is"
+ + " trusted, since it lets a message choose the
database file.")
+ private boolean allowDatabasePathFromHeader;
public DuckDbEndpoint() {
}
@@ -122,10 +129,10 @@ public class DuckDbEndpoint extends DefaultEndpoint {
/**
* Opens a connection scope for one producer invocation.
* <p>
- * When {@code databasePathOverride} is set (from the {@code
CamelDuckDbDatabasePath} header), a short-lived
- * connection to that path is opened and closed after use. Otherwise the
endpoint-owned shared connection is used
- * under a lock so concurrent exchanges do not share a JDBC connection
unsafely. DataSource connections are also
- * short-lived and closed after use.
+ * When {@code databasePathOverride} is set (from the {@code
CamelDuckDbDatabasePath} header, which the producer
+ * only reads when {@code allowDatabasePathFromHeader} is enabled), a
short-lived connection to that path is opened
+ * and closed after use. Otherwise the endpoint-owned shared connection is
used under a lock so concurrent exchanges
+ * do not share a JDBC connection unsafely. DataSource connections are
also short-lived and closed after use.
*/
DuckDbConnectionScope openConnectionScope(String databasePathOverride)
throws Exception {
if (ObjectHelper.isNotEmpty(databasePathOverride)) {
@@ -265,6 +272,14 @@ public class DuckDbEndpoint extends DefaultEndpoint {
this.resultFormat = resultFormat;
}
+ public boolean isAllowDatabasePathFromHeader() {
+ return allowDatabasePathFromHeader;
+ }
+
+ public void setAllowDatabasePathFromHeader(boolean
allowDatabasePathFromHeader) {
+ this.allowDatabasePathFromHeader = allowDatabasePathFromHeader;
+ }
+
String resolvedJdbcUrl() {
return DuckDbJdbcSupport.resolveJdbcUrl(jdbcUrl, databasePath);
}
diff --git
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbProducer.java
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbProducer.java
index 72e5e58454f9..dce1c8e8421b 100644
---
a/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbProducer.java
+++
b/components/camel-duckdb/src/main/java/org/apache/camel/component/duckdb/DuckDbProducer.java
@@ -47,7 +47,9 @@ public class DuckDbProducer extends DefaultProducer {
@Override
public void process(Exchange exchange) throws Exception {
DuckDbOperation operation = resolveOperation(exchange);
- String databasePathOverride =
exchange.getIn().getHeader(DuckDbConstants.DATABASE_PATH, String.class);
+ // the header may only move the endpoint to another database file when
the endpoint opts in
+ String databasePathOverride = endpoint.isAllowDatabasePathFromHeader()
+ ? exchange.getIn().getHeader(DuckDbConstants.DATABASE_PATH,
String.class) : null;
try (DuckDbConnectionScope scope =
endpoint.openConnectionScope(databasePathOverride)) {
Connection connection = scope.connection();
switch (operation) {
diff --git
a/components/camel-duckdb/src/test/java/org/apache/camel/component/duckdb/DuckDbProducerTest.java
b/components/camel-duckdb/src/test/java/org/apache/camel/component/duckdb/DuckDbProducerTest.java
index 7ed34cfc0e47..a719031c1d2d 100644
---
a/components/camel-duckdb/src/test/java/org/apache/camel/component/duckdb/DuckDbProducerTest.java
+++
b/components/camel-duckdb/src/test/java/org/apache/camel/component/duckdb/DuckDbProducerTest.java
@@ -204,19 +204,15 @@ class DuckDbProducerTest extends CamelTestSupport {
}
@Test
- void databasePathHeaderOverridesEndpointDatabase(@TempDir Path tempDir)
throws Exception {
+ void databasePathHeaderOverridesEndpointDatabaseWhenAllowed(@TempDir Path
tempDir) throws Exception {
Path otherDb = tempDir.resolve("other.db");
- try (Connection connection =
DriverManager.getConnection("jdbc:duckdb:" + otherDb.toAbsolutePath());
- Statement statement = connection.createStatement()) {
- statement.execute("CREATE TABLE events (id INTEGER, name
VARCHAR)");
- statement.execute("INSERT INTO events VALUES (1, 'header-db')");
- }
+ createEventsDatabase(otherDb, "header-db");
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
from("direct:query-override")
- .to("duckdb::memory:?operation=query");
+
.to("duckdb::memory:?operation=query&allowDatabasePathFromHeader=true");
}
});
@@ -229,4 +225,72 @@ class DuckDbProducerTest extends CamelTestSupport {
List.class);
assertThat(rows).extracting(row ->
row.get("name")).containsExactly("header-db");
}
+
+ @Test
+ void databasePathHeaderIsIgnoredByDefault(@TempDir Path tempDir) throws
Exception {
+ Path otherDb = tempDir.resolve("other.db");
+ createEventsDatabase(otherDb, "header-db");
+ Path endpointDb = tempDir.resolve("endpoint.db");
+ createEventsDatabase(endpointDb, "endpoint-db");
+ String uri = jdbcEndpoint(endpointDb);
+
+ // the query runs on the database of the endpoint, not on the file
that the header names
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> rows = template.requestBodyAndHeader(uri +
"&operation=query", "SELECT name FROM events",
+ DuckDbConstants.DATABASE_PATH,
otherDb.toAbsolutePath().toString(), List.class);
+ assertThat(rows).extracting(row ->
row.get("name")).containsExactly("endpoint-db");
+ }
+
+ @Test
+ void databasePathHeaderDoesNotCreateFileByDefault(@TempDir Path tempDir)
throws Exception {
+ Path headerDb = tempDir.resolve("created-by-header.db");
+ String uri = jdbcEndpoint(tempDir.resolve("endpoint.db"));
+
+ template.sendBodyAndHeader(uri + "&operation=execute", "CREATE TABLE
events (id INTEGER, name VARCHAR)",
+ DuckDbConstants.DATABASE_PATH,
headerDb.toAbsolutePath().toString());
+
+ assertThat(headerDb).doesNotExist();
+ // the statement ran on the database of the endpoint
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> rows
+ = template.requestBody(uri + "&operation=query", "SELECT
count(*) AS total FROM events", List.class);
+ assertThat(rows).extracting(row ->
row.get("total")).containsExactly(0L);
+ }
+
+ @Test
+ void operationHeaderOverridesEndpointOperation(@TempDir Path tempDir) {
+ String uri = jdbcEndpoint(tempDir.resolve("test.db"));
+
+ // the endpoint is configured for execute, the header switches it to
ping
+ Object result = template.requestBodyAndHeader(uri +
"&operation=execute", "not sql",
+ DuckDbConstants.OPERATION, "ping");
+
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ void tableHeaderOverridesEndpointTable(@TempDir Path tempDir) throws
Exception {
+ String uri = jdbcEndpoint(tempDir.resolve("test.db"));
+ template.sendBody(uri + "&operation=execute", "CREATE TABLE events (id
INTEGER, name VARCHAR)");
+ template.sendBody(uri + "&operation=execute", "CREATE TABLE audit (id
INTEGER, name VARCHAR)");
+
+ template.sendBodyAndHeader(uri + "&operation=insert&table=events",
List.of(Map.of("id", 1, "name", "ada")),
+ DuckDbConstants.TABLE, "audit");
+
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> audit = template.requestBody(uri +
"&operation=query", "SELECT name FROM audit", List.class);
+ assertThat(audit).extracting(row ->
row.get("name")).containsExactly("ada");
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> events
+ = template.requestBody(uri + "&operation=query", "SELECT name
FROM events", List.class);
+ assertThat(events).isEmpty();
+ }
+
+ private static void createEventsDatabase(Path dbFile, String name) throws
SQLException {
+ try (Connection connection =
DriverManager.getConnection("jdbc:duckdb:" + dbFile.toAbsolutePath());
+ Statement statement = connection.createStatement()) {
+ statement.execute("CREATE TABLE events (id INTEGER, name
VARCHAR)");
+ statement.execute("INSERT INTO events VALUES (1, '" + name + "')");
+ }
+ }
}
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 97d28b73c6db..82cf73024ba3 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -61,6 +61,7 @@ public final class SecurityUtils {
map.put("acceptallobjects", new SecurityOption(INSECURE_SERIALIZATION,
"true"));
map.put("acceptanyspiffeid", new SecurityOption(INSECURE_SSL, "true"));
map.put("allowcontrolheaders", new SecurityOption(INSECURE_DEV,
"true"));
+ map.put("allowdatabasepathfromheader", new
SecurityOption(INSECURE_DEV, "true"));
map.put("allowexternalentities", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowfilepathsource", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowjavaserializedobject", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
@@ -123,6 +124,8 @@ public final class SecurityUtils {
"component:jgroups"));
owners.put("allowcontrolheaders", Set.of(
"component:exec"));
+ owners.put("allowdatabasepathfromheader", Set.of(
+ "component:duckdb"));
owners.put("allowexternalentities", Set.of(
"component:smooks"));
owners.put("allowfilepathsource", Set.of(
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index a03f79d36c49..7e73cbe50c42 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -4992,6 +4992,17 @@ query had no message to run on. The consumer and
scheduler options (such as `del
`sendEmptyMessageWhenIdle`, `bridgeErrorHandler` and the `scheduler` and
`backoff` options) are removed from the
endpoint, and `bridgeErrorHandler` from the component.
+=== camel-duckdb - the CamelDuckDbDatabasePath header needs the
allowDatabasePathFromHeader option
+
+The `CamelDuckDbDatabasePath` header made the `duckdb` producer open a
short-lived connection to the DuckDB file at
+the path that the header named, instead of using the `databasePath`, `jdbcUrl`
or `DataSource` of the endpoint, and
+DuckDB created that file when it did not exist. The header is now ignored
unless the endpoint sets the new
+`allowDatabasePathFromHeader=true` option (disabled by default), so a message
no longer moves the endpoint to another
+database file. A route that relies on the header must set this option on the
endpoint.
+
+The `CamelDuckDbOperation`, `CamelDuckDbQuery` and `CamelDuckDbTable` headers
still override the operation, the SQL and
+the table of the endpoint, as before.
+
=== camel-schematron - no body fails, and bytes are read in the encoding of
the XML declaration
A message without a body now fails with an `InvalidPayloadException` instead
of a `NullPointerException`.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DuckDbEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DuckDbEndpointBuilderFactory.java
index 93a849b44cca..0583bc5060e3 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DuckDbEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DuckDbEndpointBuilderFactory.java
@@ -241,6 +241,46 @@ public interface DuckDbEndpointBuilderFactory {
doSetProperty("table", table);
return this;
}
+ /**
+ * Whether to allow overriding the database of the endpoint with the
+ * CamelDuckDbDatabasePath header, which opens a short-lived connection
+ * to the DuckDB file at that path (DuckDB creates the file when it
does
+ * not exist) instead of using the databasePath, jdbcUrl or DataSource
+ * of the endpoint. Disabled by default; enable it only when the header
+ * source is trusted, since it lets a message choose the database file.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowDatabasePathFromHeader the value to set
+ * @return the dsl builder
+ */
+ default DuckDbEndpointBuilder allowDatabasePathFromHeader(boolean
allowDatabasePathFromHeader) {
+ doSetProperty("allowDatabasePathFromHeader",
allowDatabasePathFromHeader);
+ return this;
+ }
+ /**
+ * Whether to allow overriding the database of the endpoint with the
+ * CamelDuckDbDatabasePath header, which opens a short-lived connection
+ * to the DuckDB file at that path (DuckDB creates the file when it
does
+ * not exist) instead of using the databasePath, jdbcUrl or DataSource
+ * of the endpoint. Disabled by default; enable it only when the header
+ * source is trusted, since it lets a message choose the database file.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowDatabasePathFromHeader the value to set
+ * @return the dsl builder
+ */
+ default DuckDbEndpointBuilder allowDatabasePathFromHeader(String
allowDatabasePathFromHeader) {
+ doSetProperty("allowDatabasePathFromHeader",
allowDatabasePathFromHeader);
+ return this;
+ }
}
/**
@@ -383,7 +423,11 @@ public interface DuckDbEndpointBuilderFactory {
return "CamelDuckDbOperation";
}
/**
- * Overrides the database path configured on the endpoint.
+ * Overrides the database of the endpoint for one message: a
short-lived
+ * connection to the DuckDB file at this path is opened, and DuckDB
+ * creates the file when it does not exist. This header is ignored
+ * unless the endpoint enables allowDatabasePathFromHeader=true
+ * (disabled by default).
*
* The option is a: {@code String} type.
*