This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 8819eb064 feat(java/driver/jni): implement commit/rollback (#4212)
8819eb064 is described below
commit 8819eb064f8781a53e7d5e7e634cc5d88ed104f1
Author: David Li <[email protected]>
AuthorDate: Wed Apr 15 11:38:57 2026 +0900
feat(java/driver/jni): implement commit/rollback (#4212)
Closes #4209.
---
java/driver/jni/src/main/cpp/jni_wrapper.cc | 24 +++++++
.../arrow/adbc/driver/jni/JniConnection.java | 82 ++++++++++++++++++++++
.../apache/arrow/adbc/driver/jni/JniDriver.java | 16 +++++
.../arrow/adbc/driver/jni/impl/JniLoader.java | 8 +++
.../arrow/adbc/driver/jni/impl/NativeAdbc.java | 4 ++
.../arrow/adbc/driver/jni/JniDriverTest.java | 54 ++++++++++++++
6 files changed, 188 insertions(+)
diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index 6a2a19668..a830e7304 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -914,6 +914,30 @@
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionString(
}
}
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionCommit(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* conn = reinterpret_cast<struct
AdbcConnection*>(static_cast<uintptr_t>(handle));
+ try {
+ CHECK_ADBC_ERROR(AdbcConnectionCommit(conn, &error), error);
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionRollback(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* conn = reinterpret_cast<struct
AdbcConnection*>(static_cast<uintptr_t>(handle));
+ try {
+ CHECK_ADBC_ERROR(AdbcConnectionRollback(conn, &error), error);
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+
JNIEXPORT jbyteArray JNICALL
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionBytes(
JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
index 008aeec42..671a07356 100644
---
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
@@ -21,6 +21,7 @@ import org.apache.arrow.adbc.core.AdbcConnection;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.core.AdbcStatement;
import org.apache.arrow.adbc.core.BulkIngestMode;
+import org.apache.arrow.adbc.core.IsolationLevel;
import org.apache.arrow.adbc.core.TypedKey;
import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
import org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle;
@@ -117,6 +118,87 @@ public class JniConnection implements AdbcConnection {
return
JniLoader.INSTANCE.connectionGetTableTypes(handle).importStream(allocator);
}
+ @Override
+ public boolean getReadOnly() throws AdbcException {
+ return getOption(JniDriver.READONLY);
+ }
+
+ @Override
+ public void setReadOnly(boolean isReadOnly) throws AdbcException {
+ setOption(JniDriver.READONLY, isReadOnly);
+ }
+
+ @Override
+ public boolean getAutoCommit() throws AdbcException {
+ return getOption(JniDriver.AUTOCOMMIT);
+ }
+
+ @Override
+ public void setAutoCommit(boolean enableAutoCommit) throws AdbcException {
+ setOption(JniDriver.AUTOCOMMIT, enableAutoCommit);
+ }
+
+ @Override
+ public IsolationLevel getIsolationLevel() throws AdbcException {
+ String level = getOption(JniDriver.ISOLATION_LEVEL);
+ if (level == null) {
+ return null;
+ }
+ switch (level) {
+ case JniDriver.ISOLATION_LEVEL_READ_UNCOMMITTED:
+ return IsolationLevel.READ_UNCOMMITTED;
+ case JniDriver.ISOLATION_LEVEL_READ_COMMITTED:
+ return IsolationLevel.READ_COMMITTED;
+ case JniDriver.ISOLATION_LEVEL_REPEATABLE_READ:
+ return IsolationLevel.REPEATABLE_READ;
+ case JniDriver.ISOLATION_LEVEL_SNAPSHOT:
+ return IsolationLevel.SNAPSHOT;
+ case JniDriver.ISOLATION_LEVEL_SERIALIZABLE:
+ return IsolationLevel.SERIALIZABLE;
+ default:
+ throw AdbcException.invalidArgument("[jni] invalid isolation level
value: " + level);
+ }
+ }
+
+ @Override
+ public void setIsolationLevel(IsolationLevel level) throws AdbcException {
+ if (level == null) {
+ setOption(JniDriver.ISOLATION_LEVEL, (String) null);
+ } else {
+ String levelValue;
+ switch (level) {
+ case READ_UNCOMMITTED:
+ levelValue = JniDriver.ISOLATION_LEVEL_READ_UNCOMMITTED;
+ break;
+ case READ_COMMITTED:
+ levelValue = JniDriver.ISOLATION_LEVEL_READ_COMMITTED;
+ break;
+ case REPEATABLE_READ:
+ levelValue = JniDriver.ISOLATION_LEVEL_REPEATABLE_READ;
+ break;
+ case SNAPSHOT:
+ levelValue = JniDriver.ISOLATION_LEVEL_SNAPSHOT;
+ break;
+ case SERIALIZABLE:
+ levelValue = JniDriver.ISOLATION_LEVEL_SERIALIZABLE;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown isolation level: " +
level);
+ }
+ setOption(JniDriver.ISOLATION_LEVEL, levelValue);
+ }
+ }
+
+ @Override
+ public void commit() throws AdbcException {
+ JniLoader.INSTANCE.connectionCommit(handle);
+ }
+
+ @Override
+ public void rollback() throws AdbcException {
+ JniLoader.INSTANCE.connectionRollback(handle);
+ }
+
@Override
public void close() {
handle.close();
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
index 9188207fe..fc86c6de5 100644
---
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
@@ -48,6 +48,22 @@ public class JniDriver implements AdbcDriver {
public static final TypedKey<String> PARAM_PROFILE_SEARCH_PATH =
new TypedKey<>("jni.additional_profile_search_path_list", String.class);
+ static final TypedKey<Boolean> AUTOCOMMIT =
+ new TypedKey<>("adbc.connection.autocommit", Boolean.class);
+ static final TypedKey<String> ISOLATION_LEVEL =
+ new TypedKey<>("adbc.connection.transaction.isolation_level",
String.class);
+ static final TypedKey<Boolean> READONLY =
+ new TypedKey<>("adbc.connection.readonly", Boolean.class);
+ static final String ISOLATION_LEVEL_READ_UNCOMMITTED =
+ "adbc.connection.transaction.isolation.read_uncommitted";
+ static final String ISOLATION_LEVEL_READ_COMMITTED =
+ "adbc.connection.transaction.isolation.read_committed";
+ static final String ISOLATION_LEVEL_REPEATABLE_READ =
+ "adbc.connection.transaction.isolation.repeatable_read";
+ static final String ISOLATION_LEVEL_SNAPSHOT =
"adbc.connection.transaction.isolation.snapshot";
+ static final String ISOLATION_LEVEL_SERIALIZABLE =
+ "adbc.connection.transaction.isolation.serializable";
+
private final BufferAllocator allocator;
public JniDriver(BufferAllocator allocator) {
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
index 4959d686a..5b1adaa0d 100644
---
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
@@ -186,6 +186,14 @@ public enum JniLoader {
return
NativeAdbc.connectionGetTableTypes(connection.getConnectionHandle());
}
+ public void connectionCommit(NativeConnectionHandle connection) throws
AdbcException {
+ NativeAdbc.connectionCommit(connection.getConnectionHandle());
+ }
+
+ public void connectionRollback(NativeConnectionHandle connection) throws
AdbcException {
+ NativeAdbc.connectionRollback(connection.getConnectionHandle());
+ }
+
public byte[] connectionGetOptionBytes(NativeConnectionHandle handle, String
key)
throws AdbcException {
return NativeAdbc.connectionGetOptionBytes(handle.getConnectionHandle(),
key);
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
index 3a6d09c8c..601a4716b 100644
---
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
@@ -90,6 +90,10 @@ class NativeAdbc {
static native NativeQueryResult connectionGetTableTypes(long handle) throws
AdbcException;
+ static native void connectionCommit(long handle) throws AdbcException;
+
+ static native void connectionRollback(long handle) throws AdbcException;
+
static native byte[] connectionGetOptionBytes(long handle, String key)
throws AdbcException;
static native double connectionGetOptionDouble(long handle, String key)
throws AdbcException;
diff --git
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
index 8513d0de7..57d6b6434 100644
---
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
+++
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
@@ -399,6 +399,60 @@ class JniDriverTest {
}
}
+ @Test
+ void commit() throws Exception {
+ try (final BufferAllocator allocator = new RootAllocator()) {
+ JniDriver driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+ try (final AdbcDatabase db = driver.open(parameters);
+ final AdbcConnection conn = db.connect()) {
+ try (final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("DROP TABLE IF EXISTS foobar");
+ stmt.executeUpdate();
+ }
+
+ assertThat(conn.getAutoCommit()).isTrue();
+ // not supported by sqlite driver
+ //
assertThat(conn.getIsolationLevel()).isEqualTo(IsolationLevel.SERIALIZABLE);
+ conn.setAutoCommit(false);
+ assertThat(conn.getAutoCommit()).isFalse();
+
+ try (final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("CREATE TABLE foobar (v)");
+ stmt.executeUpdate();
+
+ stmt.setSqlQuery("SELECT * FROM foobar");
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ result.getReader().loadNextBatch();
+ }
+ }
+
+ conn.rollback();
+
+ try (final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT * FROM foobar");
+ AdbcException e = assertThrows(AdbcException.class,
stmt::executeQuery);
+ assertThat(e).hasMessageContaining("no such table: foobar");
+ }
+
+ try (final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("CREATE TABLE foobar (v)");
+ stmt.executeUpdate();
+ }
+
+ conn.commit();
+
+ try (final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT * FROM foobar");
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ result.getReader().loadNextBatch();
+ }
+ }
+ }
+ }
+ }
+
@Test
void getSetOption() throws Exception {
TypedKey<Integer> batchRowsInt = new
TypedKey<>("adbc.sqlite.query.batch_rows", Integer.class);