Skip to content

Commit 2897886

Browse files
committed
Support disabling auto-commit mode in properties
JDBC spec requires the driver to have `autoCommit` enabled on newly created connections. Changing `autoCommit` using `conn#setAutoCommit()` may be not convenient in some environments with limited access to Java API (e.g. Spark SQL). This change adds a new `jdbc_auto_commit` connection property that allows to create connections with `autoCommit` disabled. Testing: new test added to check that property is effective and that the default mode is unchanged.
1 parent 546c388 commit 2897886

File tree

4 files changed

+80
-22
lines changed

4 files changed

+80
-22
lines changed

src/main/java/org/duckdb/DuckDBConnection.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.duckdb;
22

33
import static java.nio.charset.StandardCharsets.UTF_8;
4+
import static org.duckdb.DuckDBDriver.JDBC_AUTO_COMMIT;
5+
import static org.duckdb.JdbcUtils.isStringTruish;
6+
import static org.duckdb.JdbcUtils.removeOption;
47

58
import java.lang.reflect.InvocationTargetException;
6-
import java.math.BigInteger;
79
import java.nio.ByteBuffer;
810
import java.sql.Array;
911
import java.sql.Blob;
@@ -38,9 +40,9 @@ public final class DuckDBConnection implements java.sql.Connection {
3840
ByteBuffer connRef;
3941
final Lock connRefLock = new ReentrantLock();
4042
final LinkedHashSet<DuckDBPreparedStatement> preparedStatements = new LinkedHashSet<>();
41-
volatile boolean closing = false;
43+
volatile boolean closing;
4244

43-
volatile boolean autoCommit = true;
45+
volatile boolean autoCommit;
4446
volatile boolean transactionRunning;
4547
final String url;
4648
private final boolean readOnly;
@@ -57,14 +59,19 @@ public static DuckDBConnection newConnection(String url, boolean readOnly, Prope
5759
if (db_dir.startsWith("memory:")) {
5860
db_dir = ":" + db_dir;
5961
}
62+
String autoCommitStr = removeOption(properties, JDBC_AUTO_COMMIT);
63+
boolean autoCommit = isStringTruish(autoCommitStr, true);
6064
ByteBuffer nativeReference = DuckDBNative.duckdb_jdbc_startup(db_dir.getBytes(UTF_8), readOnly, properties);
61-
return new DuckDBConnection(nativeReference, url, readOnly);
65+
return new DuckDBConnection(nativeReference, url, readOnly, autoCommit);
6266
}
6367

64-
private DuckDBConnection(ByteBuffer connectionReference, String url, boolean readOnly) throws SQLException {
68+
private DuckDBConnection(ByteBuffer connectionReference, String url, boolean readOnly, boolean autoCommit)
69+
throws SQLException {
6570
this.connRef = connectionReference;
6671
this.url = url;
6772
this.readOnly = readOnly;
73+
this.autoCommit = autoCommit;
74+
// Hardcoded 'true' here is intentional, autocommit is handled in stmt#execute()
6875
DuckDBNative.duckdb_jdbc_set_auto_commit(connectionReference, true);
6976
}
7077

@@ -95,7 +102,7 @@ public Connection duplicate() throws SQLException {
95102
connRefLock.lock();
96103
try {
97104
checkOpen();
98-
return new DuckDBConnection(DuckDBNative.duckdb_jdbc_connect(connRef), url, readOnly);
105+
return new DuckDBConnection(DuckDBNative.duckdb_jdbc_connect(connRef), url, readOnly, autoCommit);
99106
} finally {
100107
connRefLock.unlock();
101108
}

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

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

3+
import static org.duckdb.JdbcUtils.isStringTruish;
4+
import static org.duckdb.JdbcUtils.removeOption;
5+
36
import java.sql.*;
47
import java.util.Properties;
58
import java.util.concurrent.locks.ReentrantLock;
@@ -11,6 +14,7 @@ public class DuckDBDriver implements java.sql.Driver {
1114
public static final String DUCKDB_READONLY_PROPERTY = "duckdb.read_only";
1215
public static final String DUCKDB_USER_AGENT_PROPERTY = "custom_user_agent";
1316
public static final String JDBC_STREAM_RESULTS = "jdbc_stream_results";
17+
public static final String JDBC_AUTO_COMMIT = "jdbc_auto_commit";
1418

1519
private static final String DUCKDB_URL_PREFIX = "jdbc:duckdb:";
1620

@@ -32,17 +36,13 @@ public Connection connect(String url, Properties info) throws SQLException {
3236
if (!acceptsURL(url)) {
3337
return null;
3438
}
35-
boolean read_only = false;
3639
if (info == null) {
3740
info = new Properties();
3841
} else { // make a copy because we're removing the read only property below
3942
info = (Properties) info.clone();
4043
}
41-
String prop_val = (String) info.remove(DUCKDB_READONLY_PROPERTY);
42-
if (prop_val != null) {
43-
String prop_clean = prop_val.trim().toLowerCase();
44-
read_only = prop_clean.equals("1") || prop_clean.equals("true") || prop_clean.equals("yes");
45-
}
44+
String readOnlyStr = removeOption(info, DUCKDB_READONLY_PROPERTY);
45+
boolean readOnly = isStringTruish(readOnlyStr, false);
4646
info.put("duckdb_api", "jdbc");
4747

4848
// Apache Spark passes this option when SELECT on a JDBC DataSource
@@ -54,7 +54,7 @@ public Connection connect(String url, Properties info) throws SQLException {
5454
String ducklake = removeOption(info, DUCKLAKE_OPTION);
5555
String ducklakeAlias = removeOption(info, DUCKLAKE_ALIAS_OPTION);
5656

57-
Connection conn = DuckDBConnection.newConnection(url, read_only, info);
57+
Connection conn = DuckDBConnection.newConnection(url, readOnly, info);
5858

5959
initDucklake(conn, url, ducklake, ducklakeAlias);
6060

@@ -121,12 +121,4 @@ private static String createAttachQuery(String ducklake, String ducklakeAlias) t
121121
}
122122
return query;
123123
}
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-
}
132124
}
Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.duckdb;
22

33
import java.sql.SQLException;
4+
import java.util.Properties;
45

56
final class JdbcUtils {
67

8+
private JdbcUtils() {
9+
}
10+
711
@SuppressWarnings("unchecked")
812
static <T> T unwrap(Object obj, Class<T> iface) throws SQLException {
913
if (!iface.isInstance(obj)) {
@@ -12,6 +16,25 @@ static <T> T unwrap(Object obj, Class<T> iface) throws SQLException {
1216
return (T) obj;
1317
}
1418

15-
private JdbcUtils() {
19+
static String removeOption(Properties props, String opt) {
20+
Object obj = props.remove(opt);
21+
if (null != obj) {
22+
return obj.toString().trim();
23+
}
24+
return null;
25+
}
26+
27+
static boolean isStringTruish(String val, boolean defaultVal) throws SQLException {
28+
if (null == val) {
29+
return defaultVal;
30+
}
31+
String valLower = val.toLowerCase().trim();
32+
if (valLower.equals("true") || valLower.equals("1") || valLower.equals("yes") || valLower.equals("on")) {
33+
return true;
34+
}
35+
if (valLower.equals("false") || valLower.equals("0") || valLower.equals("no") || valLower.equals("off")) {
36+
return false;
37+
}
38+
throw new SQLException("Invalid boolean option value: " + val);
1639
}
1740
}

src/test/java/org/duckdb/TestDuckDBJDBC.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3504,6 +3504,42 @@ public static void test_memory_colon() throws Exception {
35043504
}
35053505
}
35063506

3507+
public static void test_auto_commit_option() throws Exception {
3508+
Properties config = new Properties();
3509+
3510+
try (Connection conn = DriverManager.getConnection(JDBC_URL, config)) {
3511+
assertTrue(conn.getAutoCommit());
3512+
}
3513+
3514+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, true);
3515+
try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL, config).unwrap(DuckDBConnection.class)) {
3516+
assertTrue(conn.getAutoCommit());
3517+
3518+
try (Connection dup = conn.duplicate()) {
3519+
assertTrue(dup.getAutoCommit());
3520+
}
3521+
}
3522+
3523+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, false);
3524+
try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL, config).unwrap(DuckDBConnection.class)) {
3525+
assertFalse(conn.getAutoCommit());
3526+
3527+
try (Connection dup = conn.duplicate()) {
3528+
assertFalse(dup.getAutoCommit());
3529+
}
3530+
}
3531+
3532+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, "on");
3533+
try (Connection conn = DriverManager.getConnection(JDBC_URL, config)) {
3534+
assertTrue(conn.getAutoCommit());
3535+
}
3536+
3537+
config.put(DuckDBDriver.JDBC_AUTO_COMMIT, "off");
3538+
try (Connection conn = DriverManager.getConnection(JDBC_URL, config)) {
3539+
assertFalse(conn.getAutoCommit());
3540+
}
3541+
}
3542+
35073543
public static void main(String[] args) throws Exception {
35083544
String arg1 = args.length > 0 ? args[0] : "";
35093545
final int statusCode;

0 commit comments

Comments
 (0)