11package org .duckdb ;
22
3- import static org .duckdb .JdbcUtils .isStringTruish ;
4- import static org .duckdb .JdbcUtils .removeOption ;
3+ import static org .duckdb .JdbcUtils .*;
54
5+ import java .nio .ByteBuffer ;
66import java .sql .*;
7- import java .util .LinkedHashMap ;
8- import java .util .Map ;
9- import java .util .Properties ;
7+ import java .util .*;
108import java .util .concurrent .locks .ReentrantLock ;
119import java .util .logging .Logger ;
1210import java .util .regex .Pattern ;
@@ -17,15 +15,21 @@ public class DuckDBDriver implements java.sql.Driver {
1715 public static final String DUCKDB_USER_AGENT_PROPERTY = "custom_user_agent" ;
1816 public static final String JDBC_STREAM_RESULTS = "jdbc_stream_results" ;
1917 public static final String JDBC_AUTO_COMMIT = "jdbc_auto_commit" ;
18+ public static final String JDBC_PIN_DB = "jdbc_pin_db" ;
2019
21- private static final String DUCKDB_URL_PREFIX = "jdbc:duckdb:" ;
20+ static final String DUCKDB_URL_PREFIX = "jdbc:duckdb:" ;
2221
2322 private static final String DUCKLAKE_OPTION = "ducklake" ;
2423 private static final String DUCKLAKE_ALIAS_OPTION = "ducklake_alias" ;
2524 private static final Pattern DUCKLAKE_ALIAS_OPTION_PATTERN = Pattern .compile ("[a-zA-Z0-9_]+" );
2625 private static final String DUCKLAKE_URL_PREFIX = "ducklake:" ;
2726 private static final ReentrantLock DUCKLAKE_INIT_LOCK = new ReentrantLock ();
2827
28+ private static final LinkedHashMap <String , ByteBuffer > pinnedDbRefs = new LinkedHashMap <>();
29+ private static final ReentrantLock pinnedDbRefsLock = new ReentrantLock ();
30+ private static boolean pinnedDbRefsShutdownHookRegistered = false ;
31+ private static boolean pinnedDbRefsShutdownHookRun = false ;
32+
2933 static {
3034 try {
3135 DriverManager .registerDriver (new DuckDBDriver ());
@@ -60,12 +64,17 @@ public Connection connect(String url, Properties info) throws SQLException {
6064 // to be established.
6165 info .remove ("path" );
6266
67+ String pinDbOptStr = removeOption (info , JDBC_PIN_DB );
68+ boolean pinDBOpt = isStringTruish (pinDbOptStr , false );
69+
6370 String ducklake = removeOption (info , DUCKLAKE_OPTION );
6471 String ducklakeAlias = removeOption (info , DUCKLAKE_ALIAS_OPTION );
6572
66- Connection conn = DuckDBConnection .newConnection (url , readOnly , info );
73+ DuckDBConnection conn = DuckDBConnection .newConnection (url , readOnly , info );
6774
68- initDucklake (conn , url , ducklake , ducklakeAlias );
75+ pinDB (pinDBOpt , url , conn );
76+
77+ initDucklake (conn , ducklake , ducklakeAlias );
6978
7079 return conn ;
7180 }
@@ -95,8 +104,7 @@ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
95104 throw new SQLFeatureNotSupportedException ("no logger" );
96105 }
97106
98- private static void initDucklake (Connection conn , String url , String ducklake , String ducklakeAlias )
99- throws SQLException {
107+ private static void initDucklake (Connection conn , String ducklake , String ducklakeAlias ) throws SQLException {
100108 if (null == ducklake ) {
101109 return ;
102110 }
@@ -154,6 +162,55 @@ private static ParsedProps parsePropsFromUrl(String url) throws SQLException {
154162 return new ParsedProps (shortUrl , props );
155163 }
156164
165+ private static void pinDB (boolean pinnedDbOpt , String url , DuckDBConnection conn ) throws SQLException {
166+ if (!pinnedDbOpt ) {
167+ return ;
168+ }
169+ String dbName = dbNameFromUrl (url );
170+ if (":memory:" .equals (dbName )) {
171+ return ;
172+ }
173+
174+ pinnedDbRefsLock .lock ();
175+ try {
176+ // Actual native DB cache uses absolute paths to file DBs,
177+ // but that should not make the difference unless CWD is changed,
178+ // that is not expected for a JVM process, see JDK-4045688.
179+ if (pinnedDbRefsShutdownHookRun || pinnedDbRefs .containsKey (dbName )) {
180+ return ;
181+ }
182+ // No need to hold connRef lock here, this connection is not
183+ // yet available to client at this point, so it cannot be closed.
184+ ByteBuffer dbRef = DuckDBNative .duckdb_jdbc_create_db_ref (conn .connRef );
185+ pinnedDbRefs .put (dbName , dbRef );
186+
187+ if (!pinnedDbRefsShutdownHookRegistered ) {
188+ Runtime .getRuntime ().addShutdownHook (new Thread (new PinnedDbRefsShutdownHook ()));
189+ pinnedDbRefsShutdownHookRegistered = true ;
190+ }
191+ } finally {
192+ pinnedDbRefsLock .unlock ();
193+ }
194+ }
195+
196+ public static boolean releaseDB (String url ) throws SQLException {
197+ pinnedDbRefsLock .lock ();
198+ try {
199+ if (pinnedDbRefsShutdownHookRun ) {
200+ return false ;
201+ }
202+ String dbName = dbNameFromUrl (url );
203+ ByteBuffer dbRef = pinnedDbRefs .remove (dbName );
204+ if (null == dbRef ) {
205+ return false ;
206+ }
207+ DuckDBNative .duckdb_jdbc_destroy_db_ref (dbRef );
208+ return true ;
209+ } finally {
210+ pinnedDbRefsLock .unlock ();
211+ }
212+ }
213+
157214 private static class ParsedProps {
158215 final String shortUrl ;
159216 final LinkedHashMap <String , String > props ;
@@ -167,4 +224,23 @@ private ParsedProps(String shortUrl, LinkedHashMap<String, String> props) {
167224 this .props = props ;
168225 }
169226 }
227+
228+ private static class PinnedDbRefsShutdownHook implements Runnable {
229+ @ Override
230+ public void run () {
231+ pinnedDbRefsLock .lock ();
232+ try {
233+ List <ByteBuffer > dbRefsList = new ArrayList <>(pinnedDbRefs .values ());
234+ Collections .reverse (dbRefsList );
235+ for (ByteBuffer dbRef : dbRefsList ) {
236+ DuckDBNative .duckdb_jdbc_destroy_db_ref (dbRef );
237+ }
238+ pinnedDbRefsShutdownHookRun = true ;
239+ } catch (SQLException e ) {
240+ e .printStackTrace ();
241+ } finally {
242+ pinnedDbRefsLock .unlock ();
243+ }
244+ }
245+ }
170246}
0 commit comments