Merge 'bindings/java: Implement returning updated counts and version information ' from Kim Seon Woo

### Purpose
- Do some minor nits
  - Implement logic that returns updated count when running
executeUpdate
  - Implement version related functions
### Reference
- https://github.com/tursodatabase/limbo/issues/615

Closes #1086
This commit is contained in:
Pekka Enberg
2025-03-04 09:38:32 +02:00
8 changed files with 129 additions and 36 deletions

View File

@@ -270,6 +270,23 @@ pub extern "system" fn Java_tech_turso_core_LimboStatement_bindBlob<'local>(
SQLITE_OK
}
#[no_mangle]
pub extern "system" fn Java_tech_turso_core_LimboStatement_totalChanges<'local>(
mut env: JNIEnv<'local>,
obj: JObject<'local>,
stmt_ptr: jlong,
) -> jlong {
let stmt = match to_limbo_statement(stmt_ptr) {
Ok(stmt) => stmt,
Err(e) => {
set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());
return -1;
}
};
stmt.connection.conn.total_changes()
}
/// Converts an optional `JObject` into Java's `LimboStepResult`.
///
/// This function takes an optional `JObject` and converts it into a Java object

View File

@@ -5,6 +5,7 @@ import java.util.Locale;
import java.util.Properties;
import tech.turso.annotations.Nullable;
import tech.turso.annotations.SkipNullableCheck;
import tech.turso.core.LimboPropertiesHolder;
import tech.turso.jdbc4.JDBC4Connection;
import tech.turso.utils.Logger;
import tech.turso.utils.LoggerFactory;
@@ -57,14 +58,12 @@ public final class JDBC implements Driver {
@Override
public int getMajorVersion() {
// TODO
return 0;
return Integer.parseInt(LimboPropertiesHolder.getDriverVersion().split("\\.")[0]);
}
@Override
public int getMinorVersion() {
// TODO
return 0;
return Integer.parseInt(LimboPropertiesHolder.getDriverVersion().split("\\.")[1]);
}
@Override

View File

@@ -0,0 +1,39 @@
package tech.turso.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import tech.turso.jdbc4.JDBC4DatabaseMetaData;
import tech.turso.utils.Logger;
import tech.turso.utils.LoggerFactory;
public class LimboPropertiesHolder {
private static final Logger logger = LoggerFactory.getLogger(LimboPropertiesHolder.class);
private static String driverName = "";
private static String driverVersion = "";
static {
try (InputStream limboJdbcPropStream =
JDBC4DatabaseMetaData.class.getClassLoader().getResourceAsStream("limbo-jdbc.properties")) {
if (limboJdbcPropStream == null) {
throw new IOException("Cannot load limbo-jdbc.properties from jar");
}
final Properties properties = new Properties();
properties.load(limboJdbcPropStream);
driverName = properties.getProperty("driverName");
driverVersion = properties.getProperty("driverVersion");
} catch (IOException e) {
logger.error("Failed to load driverName and driverVersion");
}
}
public static String getDriverName() {
return driverName;
}
public static String getDriverVersion() {
return driverVersion;
}
}

View File

@@ -214,6 +214,21 @@ public final class LimboStatement {
private native int bindBlob(long statementPointer, int position, byte[] value)
throws SQLException;
/**
* Returns total number of changes.
*
* @throws SQLException If a database access error occurs
*/
public long totalChanges() throws SQLException {
final long result = totalChanges(statementPointer);
if (result == -1) {
throw new SQLException("Exception while retrieving total number of changes");
}
return result;
}
private native long totalChanges(long statementPointer) throws SQLException;
/**
* Checks if the statement is closed.
*

View File

@@ -1,7 +1,5 @@
package tech.turso.jdbc4;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
@@ -9,9 +7,9 @@ import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import tech.turso.annotations.Nullable;
import tech.turso.annotations.SkipNullableCheck;
import tech.turso.core.LimboPropertiesHolder;
import tech.turso.utils.Logger;
import tech.turso.utils.LoggerFactory;
@@ -19,9 +17,6 @@ public final class JDBC4DatabaseMetaData implements DatabaseMetaData {
private static final Logger logger = LoggerFactory.getLogger(JDBC4DatabaseMetaData.class);
private static String driverName = "";
private static String driverVersion = "";
private final JDBC4Connection connection;
@Nullable private PreparedStatement getTables = null;
@Nullable private PreparedStatement getTableTypes = null;
@@ -41,22 +36,6 @@ public final class JDBC4DatabaseMetaData implements DatabaseMetaData {
@Nullable private PreparedStatement getVersionColumns = null;
@Nullable private PreparedStatement getColumnPrivileges = null;
static {
try (InputStream limboJdbcPropStream =
JDBC4DatabaseMetaData.class.getClassLoader().getResourceAsStream("limbo-jdbc.properties")) {
if (limboJdbcPropStream == null) {
throw new IOException("Cannot load limbo-jdbc.properties from jar");
}
final Properties properties = new Properties();
properties.load(limboJdbcPropStream);
driverName = properties.getProperty("driverName");
driverVersion = properties.getProperty("driverVersion");
} catch (IOException e) {
logger.error("Failed to load driverName and driverVersion");
}
}
public JDBC4DatabaseMetaData(JDBC4Connection connection) {
this.connection = connection;
}
@@ -120,22 +99,22 @@ public final class JDBC4DatabaseMetaData implements DatabaseMetaData {
@Override
public String getDriverName() {
return driverName;
return LimboPropertiesHolder.getDriverName();
}
@Override
public String getDriverVersion() {
return driverVersion;
return LimboPropertiesHolder.getDriverVersion();
}
@Override
public int getDriverMajorVersion() {
return Integer.parseInt(driverVersion.split("\\.")[0]);
return Integer.parseInt(getDriverVersion().split("\\.")[0]);
}
@Override
public int getDriverMinorVersion() {
return Integer.parseInt(driverVersion.split("\\.")[1]);
return Integer.parseInt(getDriverVersion().split("\\.")[1]);
}
@Override

View File

@@ -28,8 +28,6 @@ public class JDBC4Statement implements Statement {
private final int resultSetHoldability;
private int queryTimeoutSeconds;
private long updateCount;
private boolean exhaustedResults = false;
private ReentrantLock connectionLock = new ReentrantLock();
@@ -74,14 +72,14 @@ public class JDBC4Statement implements Statement {
@Override
public int executeUpdate(String sql) throws SQLException {
execute(sql);
final long previousTotalChanges = statement == null ? 0L : statement.totalChanges();
execute(sql);
requireNonNull(statement, "statement should not be null after running execute method");
final LimboResultSet resultSet = statement.getResultSet();
resultSet.consumeAll();
// TODO: return update count;
return 0;
return (int) (statement.totalChanges() - previousTotalChanges);
}
@Override
@@ -176,7 +174,6 @@ public class JDBC4Statement implements Statement {
statement = connection.prepare(sql);
final boolean result = statement.execute();
updateGeneratedKeys();
exhaustedResults = false;
return result;
} finally {

View File

@@ -1,8 +1,10 @@
package tech.turso;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
@@ -30,4 +32,22 @@ class JDBCTest {
assertThat(connection).isNotNull();
}
}
@Test
void retrieve_version() {
assertDoesNotThrow(() -> DriverManager.getDriver("jdbc:sqlite:").getMajorVersion());
assertDoesNotThrow(() -> DriverManager.getDriver("jdbc:sqlite:").getMinorVersion());
}
@Test
void all_driver_property_info_should_have_a_description() throws Exception {
Driver driver = DriverManager.getDriver("jdbc:sqlite:");
assertThat(driver.getPropertyInfo(null, null))
.allSatisfy((info) -> assertThat(info.description).isNotNull());
}
@Test
void return_null_when_protocol_can_not_be_handled() throws Exception {
assertThat(JDBC.createConnection("jdbc:unknownprotocol:", null)).isNull();
}
}

View File

@@ -1,5 +1,6 @@
package tech.turso.jdbc4;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import java.sql.ResultSet;
@@ -70,4 +71,30 @@ class JDBC4StatementTest {
stmt.close();
assertThrows(SQLException.class, () -> stmt.execute("SELECT 1;"));
}
@Test
void execute_update_should_return_number_of_inserted_elements() throws Exception {
assertThat(stmt.executeUpdate("CREATE TABLE s1 (c1);")).isEqualTo(0);
assertThat(stmt.executeUpdate("INSERT INTO s1 VALUES (0);")).isEqualTo(1);
assertThat(stmt.executeUpdate("INSERT INTO s1 VALUES (1), (2);")).isEqualTo(2);
assertThat(stmt.executeUpdate("INSERT INTO s1 VALUES (3), (4), (5);")).isEqualTo(3);
}
@Test
@Disabled("Limbo update not yet supported")
void execute_update_should_return_number_of_updated_elements() throws Exception {
assertThat(stmt.executeUpdate("CREATE TABLE s1 (c1);")).isEqualTo(0);
assertThat(stmt.executeUpdate("INSERT INTO s1 VALUES (1), (2), (3);")).isEqualTo(3);
assertThat(stmt.executeUpdate("UPDATE s1 SET c1 = 0;")).isEqualTo(3);
}
@Test
@Disabled("Limbo delete has a bug")
void execute_update_should_return_number_of_deleted_elements() throws Exception {
assertThat(stmt.executeUpdate("CREATE TABLE s1 (c1);")).isEqualTo(0);
assertThat(stmt.executeUpdate("INSERT INTO s1 VALUES (1), (2), (3);")).isEqualTo(3);
assertThat(stmt.executeUpdate("DELETE FROM s1")).isEqualTo(3);
}
}