Merge 'bindings/java: Refactor to favor composition over inheritance ' from Kim Seon Woo

## Changes
- AS-IS
  - `LimboConnection` implements `Connection`
  - `JDBC4Connection` implements `LimboConnection`
- TO-BE
  - `LimboConnection` doesn't implement `Connection`. It is used to
represent connection to limbo only.
  - `JDBC4Connection` implements `Connection` directly. Holds
`LimboConnection` in member field.
- This helps remove the complexity of java's `Connection` interface away
from `LimboConnection`. We can focus on implementing limbo's feature
inside `LimboConnection` and let `JDBC4Connection` handle the
interoperability with jdbc world.
## Reference
https://github.com/tursodatabase/limbo/issues/615

Closes #916
This commit is contained in:
Pekka Enberg 2025-02-10 12:13:30 +02:00
commit fa883d1c6b
7 changed files with 52 additions and 78 deletions

View file

@ -4,15 +4,17 @@ The Limbo JDBC driver is a library for accessing and creating Limbo database fil
## Project Status
The project is actively developed. Feel free to open issues and contribute.
The project is actively developed. Feel free to open issues and contribute.
To view related works, visit this [issue](https://github.com/tursodatabase/limbo/issues/615).
## How to use
## How to use
Currently, we have not published to the maven central. Instead, you can locally build the jar and deploy it to maven local to use it.
Currently, we have not published to the maven central. Instead, you can locally build the jar and deploy it to
maven local to use it.
### Build jar and publish to maven local
### Build jar and publish to maven local
```shell
$ cd bindings/java
@ -23,44 +25,16 @@ $ make macos_x86
$ make publish_local
```
Now you can use the dependency as follows:
Now you can use the dependency as follows:
```kotlin
dependencies {
implementation("org.github.tursodatabase:limbo:0.0.1-SNAPSHOT")
implementation("org.github.tursodatabase:limbo:0.0.1-SNAPSHOT")
}
```
## Development
## Code style
### How to Run Tests
To run tests, use the following command:
```shell
$ make test
```
### Code Formatting
To unify Java's formatting style, we use Spotless. To apply the formatting style, run:
```shell
$ make lint_apply
```
To apply the formatting style for Rust, run the following command:
```shell
$ cargo fmt
```
## Concepts
Note that this project is actively developed, so the concepts might change in the future.
- `LimboDB` represents a Limbo database.
- `LimboConnection` represents a connection to `LimboDB`. Multiple `LimboConnections` can be created on the same
`LimboDB`.
- `LimboStatement` represents a Limbo database statement. Multiple `LimboStatements` can be created on the same
`LimboConnection`.
- `LimboResultSet` represents the result of `LimboStatement` execution. It is one-to-one mapped to `LimboStatement`.
- Favor composition over inheritance. For example, `JDBC4Connection` doesn't implement `LimboConnection`. Instead,
it includes `LimboConnection` as a field. This approach allows us to preserve the characteristics of Limbo using
`LimboConnection` easily while maintaining interoperability with the Java world using `JDBC4Connection`.

View file

@ -5,7 +5,6 @@ import java.util.Locale;
import java.util.Properties;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
import org.github.tursodatabase.utils.Logger;
import org.github.tursodatabase.utils.LoggerFactory;
@ -24,7 +23,7 @@ public class JDBC implements Driver {
}
@Nullable
public static LimboConnection createConnection(String url, Properties properties)
public static JDBC4Connection createConnection(String url, Properties properties)
throws SQLException {
if (!isValidURL(url)) return null;

View file

@ -2,7 +2,6 @@ package org.github.tursodatabase.core;
import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
@ -11,7 +10,7 @@ import org.github.tursodatabase.utils.LimboExceptionUtils;
import org.github.tursodatabase.utils.Logger;
import org.github.tursodatabase.utils.LoggerFactory;
public abstract class LimboConnection implements Connection {
public class LimboConnection {
private static final Logger logger = LoggerFactory.getLogger(LimboConnection.class);
private final long connectionPtr;
@ -38,11 +37,10 @@ public abstract class LimboConnection implements Connection {
return LimboDBFactory.open(url, filePath, properties);
}
protected void checkOpen() throws SQLException {
public void checkOpen() throws SQLException {
if (isClosed()) throw new SQLException("database connection closed");
}
@Override
public void close() throws SQLException {
if (isClosed()) {
return;
@ -53,7 +51,6 @@ public abstract class LimboConnection implements Connection {
private native void _close(long connectionPtr);
@Override
public boolean isClosed() throws SQLException {
return closed;
}
@ -80,14 +77,7 @@ public abstract class LimboConnection implements Connection {
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
/** @return busy timeout in milliseconds. */
public int getBusyTimeout() {
// TODO: add support for busyTimeout
return 0;
}
// TODO: check whether this is still valid for limbo
/**
* Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
* supported by the SQLite interface. Supported settings are:
@ -102,7 +92,7 @@ public abstract class LimboConnection implements Connection {
* @param resultSetConcurrency the concurrency setting.
* @param resultSetHoldability the holdability setting.
*/
protected void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
public void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
@ -115,10 +105,6 @@ public abstract class LimboConnection implements Connection {
}
}
public void setBusyTimeout(int busyTimeout) {
// TODO: add support for busy timeout
}
/**
* Throws formatted SQLException with error code and message.
*

View file

@ -7,17 +7,24 @@ import java.util.Properties;
import java.util.concurrent.Executor;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.core.LimboStatement;
public class JDBC4Connection extends LimboConnection {
public class JDBC4Connection implements Connection {
private final LimboConnection connection;
private Map<String, Class<?>> typeMap = new HashMap<>();
public JDBC4Connection(String url, String filePath) throws SQLException {
super(url, filePath);
this.connection = new LimboConnection(url, filePath);
}
public JDBC4Connection(String url, String filePath, Properties properties) throws SQLException {
super(url, filePath, properties);
this.connection = new LimboConnection(url, filePath, properties);
}
public LimboStatement prepare(String sql) throws SQLException {
return connection.prepare(sql);
}
@Override
@ -35,8 +42,8 @@ public class JDBC4Connection extends LimboConnection {
@Override
public Statement createStatement(
int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
checkOpen();
checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
connection.checkOpen();
connection.checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
return new JDBC4Statement(this);
}
@ -69,12 +76,12 @@ public class JDBC4Connection extends LimboConnection {
@Override
public void close() throws SQLException {
super.close();
connection.close();
}
@Override
public boolean isClosed() throws SQLException {
return super.isClosed();
return connection.isClosed();
}
@Override
@ -140,13 +147,13 @@ public class JDBC4Connection extends LimboConnection {
@Override
public int getHoldability() throws SQLException {
checkOpen();
connection.checkOpen();
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
@Override
public void setHoldability(int holdability) throws SQLException {
checkOpen();
connection.checkOpen();
if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
throw new SQLException("Limbo only supports CLOSE_CURSORS_AT_COMMIT");
}
@ -214,8 +221,8 @@ public class JDBC4Connection extends LimboConnection {
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
checkOpen();
checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
connection.checkOpen();
connection.checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
return new JDBC4PreparedStatement(this, sql);
}
@ -348,4 +355,14 @@ public class JDBC4Connection extends LimboConnection {
// TODO
return false;
}
public void setBusyTimeout(int busyTimeout) {
// TODO: add support for busy timeout
}
/** @return busy timeout in milliseconds. */
public int getBusyTimeout() {
// TODO: add support for busyTimeout
return 0;
}
}

View file

@ -23,14 +23,13 @@ import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.core.LimboResultSet;
public class JDBC4PreparedStatement extends JDBC4Statement implements PreparedStatement {
private final String sql;
public JDBC4PreparedStatement(LimboConnection connection, String sql) throws SQLException {
public JDBC4PreparedStatement(JDBC4Connection connection, String sql) throws SQLException {
super(connection);
this.sql = sql;

View file

@ -10,13 +10,12 @@ import java.sql.Statement;
import java.util.concurrent.locks.ReentrantLock;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.core.LimboResultSet;
import org.github.tursodatabase.core.LimboStatement;
public class JDBC4Statement implements Statement {
private final LimboConnection connection;
private final JDBC4Connection connection;
@Nullable protected LimboStatement statement = null;
// Because JDBC4Statement has different life cycle in compared to LimboStatement, let's use this
@ -34,7 +33,7 @@ public class JDBC4Statement implements Statement {
private ReentrantLock connectionLock = new ReentrantLock();
public JDBC4Statement(LimboConnection connection) {
public JDBC4Statement(JDBC4Connection connection) {
this(
connection,
ResultSet.TYPE_FORWARD_ONLY,
@ -43,7 +42,7 @@ public class JDBC4Statement implements Statement {
}
public JDBC4Statement(
LimboConnection connection,
JDBC4Connection connection,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) {

View file

@ -6,21 +6,21 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
import org.junit.jupiter.api.Test;
class JDBCTest {
@Test
void null_is_returned_when_invalid_url_is_passed() throws Exception {
LimboConnection connection = JDBC.createConnection("jdbc:invalid:xxx", new Properties());
JDBC4Connection connection = JDBC.createConnection("jdbc:invalid:xxx", new Properties());
assertThat(connection).isNull();
}
@Test
void non_null_connection_is_returned_when_valid_url_is_passed() throws Exception {
String fileUrl = TestUtils.createTempFile();
LimboConnection connection = JDBC.createConnection("jdbc:sqlite:" + fileUrl, new Properties());
JDBC4Connection connection = JDBC.createConnection("jdbc:sqlite:" + fileUrl, new Properties());
assertThat(connection).isNotNull();
}