Skip to content

Commit 546c388

Browse files
committed
Support auto-initializing DuckLake
There is a usability gap with using DuckLake with JDBC, when a connection must be opened first and then the following statement must be executed: ``` ATTACH 'ducklake:...' ``` Executing this additional `ATTACH` step, when accessing DuckLake from GUI tools or from high-level engines like Spark, is cumbersome and may require non-trivial configuration. This change adds two new connection properties: 1. `ducklake`: the `database-path` parameter to pass to `ATTACH '<database-path>'`. Value examples: ``` /path/to/lake1.db sqlite:/path/to/lake1.db postgres:postgresql://user:[email protected]:5432/lake1 ``` If `ducklake:` prefix to the value of this option is not specified - it is added automatically. Before running the `ATTACH` it also runs: ``` INSTALL ducklake LOAD ducklake ``` 2. `ducklake_alias`: the `database-alias` parameter to pass to `ATTACH '<database-path>' AS <database-alias>`. This is to allow to override auto-detected DuckLake catalog name in cases when `database-path` has long naming or include UUIDs. After the connection is established it also runs `USE <database-alias>`. Testing: test coverage is pending, DuckLake extension is not yet available in the `main` branch.
1 parent c732b72 commit 546c388

File tree

1 file changed

+64
-7
lines changed

1 file changed

+64
-7
lines changed

src/main/java/org/duckdb/DuckDBDriver.java

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
package org.duckdb;
22

3-
import java.sql.Connection;
4-
import java.sql.DriverManager;
5-
import java.sql.DriverPropertyInfo;
6-
import java.sql.SQLException;
7-
import java.sql.SQLFeatureNotSupportedException;
3+
import java.sql.*;
84
import java.util.Properties;
5+
import java.util.concurrent.locks.ReentrantLock;
96
import java.util.logging.Logger;
7+
import java.util.regex.Pattern;
108

119
public class DuckDBDriver implements java.sql.Driver {
1210

1311
public static final String DUCKDB_READONLY_PROPERTY = "duckdb.read_only";
1412
public static final String DUCKDB_USER_AGENT_PROPERTY = "custom_user_agent";
1513
public static final String JDBC_STREAM_RESULTS = "jdbc_stream_results";
1614

15+
private static final String DUCKDB_URL_PREFIX = "jdbc:duckdb:";
16+
17+
private static final String DUCKLAKE_OPTION = "ducklake";
18+
private static final String DUCKLAKE_ALIAS_OPTION = "ducklake_alias";
19+
private static final Pattern DUCKLAKE_ALIAS_OPTION_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
20+
private static final String DUCKLAKE_URL_PREFIX = "ducklake:";
21+
private static final ReentrantLock DUCKLAKE_INIT_LOCK = new ReentrantLock();
22+
1723
static {
1824
try {
1925
DriverManager.registerDriver(new DuckDBDriver());
@@ -45,11 +51,18 @@ public Connection connect(String url, Properties info) throws SQLException {
4551
// to be established.
4652
info.remove("path");
4753

48-
return DuckDBConnection.newConnection(url, read_only, info);
54+
String ducklake = removeOption(info, DUCKLAKE_OPTION);
55+
String ducklakeAlias = removeOption(info, DUCKLAKE_ALIAS_OPTION);
56+
57+
Connection conn = DuckDBConnection.newConnection(url, read_only, info);
58+
59+
initDucklake(conn, url, ducklake, ducklakeAlias);
60+
61+
return conn;
4962
}
5063

5164
public boolean acceptsURL(String url) throws SQLException {
52-
return url.startsWith("jdbc:duckdb:");
65+
return url.startsWith(DUCKDB_URL_PREFIX);
5366
}
5467

5568
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
@@ -72,4 +85,48 @@ public boolean jdbcCompliant() {
7285
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
7386
throw new SQLFeatureNotSupportedException("no logger");
7487
}
88+
89+
private static void initDucklake(Connection conn, String url, String ducklake, String ducklakeAlias)
90+
throws SQLException {
91+
if (null == ducklake) {
92+
return;
93+
}
94+
DUCKLAKE_INIT_LOCK.lock();
95+
try {
96+
String attachQuery = createAttachQuery(ducklake, ducklakeAlias);
97+
try (Statement stmt = conn.createStatement()) {
98+
stmt.execute("INSTALL ducklake");
99+
stmt.execute("LOAD ducklake");
100+
stmt.execute(attachQuery);
101+
if (null != ducklakeAlias) {
102+
stmt.execute("USE " + ducklakeAlias);
103+
}
104+
}
105+
} finally {
106+
DUCKLAKE_INIT_LOCK.unlock();
107+
}
108+
}
109+
110+
private static String createAttachQuery(String ducklake, String ducklakeAlias) throws SQLException {
111+
ducklake = ducklake.replaceAll("'", "''");
112+
if (!ducklake.startsWith(DUCKLAKE_URL_PREFIX)) {
113+
ducklake = DUCKLAKE_URL_PREFIX + ducklake;
114+
}
115+
String query = "ATTACH IF NOT EXISTS '" + ducklake + "'";
116+
if (null != ducklakeAlias) {
117+
if (!DUCKLAKE_ALIAS_OPTION_PATTERN.matcher(ducklakeAlias).matches()) {
118+
throw new SQLException("Invalid DuckLake alias specified: " + ducklakeAlias);
119+
}
120+
query += " AS " + ducklakeAlias;
121+
}
122+
return query;
123+
}
124+
125+
private static String removeOption(Properties props, String opt) {
126+
Object obj = props.remove(opt);
127+
if (null != obj) {
128+
return obj.toString();
129+
}
130+
return null;
131+
}
75132
}

0 commit comments

Comments
 (0)