diff --git a/src/main/java/org/duckdb/DuckDBDriver.java b/src/main/java/org/duckdb/DuckDBDriver.java index 81523068a..fe16a7aaf 100644 --- a/src/main/java/org/duckdb/DuckDBDriver.java +++ b/src/main/java/org/duckdb/DuckDBDriver.java @@ -16,6 +16,7 @@ public class DuckDBDriver implements java.sql.Driver { public static final String JDBC_STREAM_RESULTS = "jdbc_stream_results"; public static final String JDBC_AUTO_COMMIT = "jdbc_auto_commit"; public static final String JDBC_PIN_DB = "jdbc_pin_db"; + public static final String JDBC_IGNORE_UNSUPPORTED_OPTIONS = "jdbc_ignore_unsupported_options"; static final String DUCKDB_URL_PREFIX = "jdbc:duckdb:"; static final String MEMORY_DB = ":memory:"; @@ -33,6 +34,9 @@ public class DuckDBDriver implements java.sql.Driver { private static boolean pinnedDbRefsShutdownHookRegistered = false; private static boolean pinnedDbRefsShutdownHookRun = false; + private static final Set supportedOptions = new LinkedHashSet<>(); + private static final ReentrantLock supportedOptionsLock = new ReentrantLock(); + static { try { DriverManager.registerDriver(new DuckDBDriver()); @@ -52,13 +56,20 @@ public Connection connect(String url, Properties info) throws SQLException { props = (Properties) info.clone(); } + // URL options ParsedProps pp = parsePropsFromUrl(url); for (Map.Entry en : pp.props.entrySet()) { props.put(en.getKey(), en.getValue()); } + // Ignore unsupported + removeUnsupportedOptions(props); + + // Read-only option String readOnlyStr = removeOption(props, DUCKDB_READONLY_PROPERTY); boolean readOnly = isStringTruish(readOnlyStr, false); + + // Client name option props.put("duckdb_api", "jdbc"); // Apache Spark passes this option when SELECT on a JDBC DataSource @@ -67,6 +78,7 @@ public Connection connect(String url, Properties info) throws SQLException { // to be established. props.remove("path"); + // DuckLake options String ducklake = removeOption(props, DUCKLAKE_OPTION); String ducklakeAlias = removeOption(props, DUCKLAKE_ALIAS_OPTION, DUCKLAKE_OPTION); final String shortUrl; @@ -83,13 +95,13 @@ public Connection connect(String url, Properties info) throws SQLException { shortUrl = pp.shortUrl; } + // Pin DB option String pinDbOptStr = removeOption(props, JDBC_PIN_DB); boolean pinDBOpt = isStringTruish(pinDbOptStr, false); + // Create connection DuckDBConnection conn = DuckDBConnection.newConnection(shortUrl, readOnly, props); - pinDB(pinDBOpt, shortUrl, conn); - initDucklake(conn, shortUrl, ducklake, ducklakeAlias); return conn; @@ -116,6 +128,8 @@ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws list.add(createDriverPropInfo(JDBC_AUTO_COMMIT, "", "Set default auto-commit mode")); list.add(createDriverPropInfo(JDBC_PIN_DB, "", "Do not close the DB instance after all connections to it are closed")); + list.add(createDriverPropInfo(JDBC_IGNORE_UNSUPPORTED_OPTIONS, "", + "Silently discard unsupported connection options")); list.sort((o1, o2) -> o1.name.compareToIgnoreCase(o2.name)); return list.toArray(new DriverPropertyInfo[0]); } @@ -251,6 +265,38 @@ private static DriverPropertyInfo createDriverPropInfo(String name, String value return dpi; } + private static void removeUnsupportedOptions(Properties props) throws SQLException { + String ignoreStr = removeOption(props, JDBC_IGNORE_UNSUPPORTED_OPTIONS); + boolean ignore = isStringTruish(ignoreStr, false); + if (!ignore) { + return; + } + supportedOptionsLock.lock(); + try { + if (supportedOptions.isEmpty()) { + Driver driver = DriverManager.getDriver(DUCKDB_URL_PREFIX); + Properties dpiProps = new Properties(); + dpiProps.put("threads", 1); + DriverPropertyInfo[] dpis = driver.getPropertyInfo(DUCKDB_URL_PREFIX, dpiProps); + for (DriverPropertyInfo dpi : dpis) { + supportedOptions.add(dpi.name); + } + } + List unsupportedNames = new ArrayList<>(); + for (Object nameObj : props.keySet()) { + String name = String.valueOf(nameObj); + if (!supportedOptions.contains(name)) { + unsupportedNames.add(name); + } + } + for (String name : unsupportedNames) { + props.remove(name); + } + } finally { + supportedOptionsLock.unlock(); + } + } + private static class ParsedProps { final String shortUrl; final LinkedHashMap props; diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index d0bb17a14..616f3c789 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -10,6 +10,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.duckdb.DuckDBDriver.DUCKDB_USER_AGENT_PROPERTY; +import static org.duckdb.DuckDBDriver.JDBC_STREAM_RESULTS; import static org.duckdb.DuckDBTimestamp.localDateTimeFromTimestamp; import static org.duckdb.test.Assertions.*; import static org.duckdb.test.Runner.runTests; @@ -3656,6 +3657,14 @@ public static void test_driver_property_info() throws Exception { assertTrue(dpis.length > 0); } + public static void test_ignore_unsupported_options() throws Exception { + assertThrows(() -> { DriverManager.getConnection("jdbc:duckdb:;foo=bar;"); }, SQLException.class); + Properties config = new Properties(); + config.put("boo", "bar"); + config.put(JDBC_STREAM_RESULTS, true); + DriverManager.getConnection("jdbc:duckdb:;foo=bar;jdbc_ignore_unsupported_options=yes;", config).close(); + } + public static void main(String[] args) throws Exception { String arg1 = args.length > 0 ? args[0] : ""; final int statusCode;