Skip to content

Implementing the driver

Will0mane edited this page May 4, 2025 · 2 revisions

Drivers - what makes Quill work

In order to use Quill you must first provide a Driver instance. In this context, a driver is a class that implements the QuillDriver interface from the api module. The driver provides a way to quill to manage queries, connections and result readers.

The following wiki should guide you on how to implement the driver based on your needs. Note that the driver must be implemented once by your core systems. The same instance of Quill can be shared across the whole JVM without problems (The driver as well).

Providing async functionality

The first method to implement is the async(Runnable runnable) one. You should add a method for quill to run a task asynchronously. Example:

    private final Executor yourDatabaseExecutor = ...;

    @Override
    public void async(Runnable runnable) {
        CompletableFuture.runAsync(runnable, yourDatabaseExecutor);
    }

Note that if the executor is locked or it is stopped quill will stop sending queries. It is a good practice to use a separate executor for quill separated from everything else.

Providing a Connection

The second piece to implement is the connection() method, providing quill the necessary connection to execute SQL statements. Example using the Hikari library:

    @Override
    public Connection connection() {
        try {
            return getHikari().getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

The Query object

Quill uses a custom Query interface to build statements. Example Query implementation (adapt to your needs):

public class QuillQuery implements Query {

    private final QuillDriver driver;

    public QuillQuery(QuillDriver driver) {
        this.driver = driver;
    }

    private String literal;
    private QueryMethod method;
    private Collection<Object> params;

    @Override
    public void literal(String literal) {
        this.literal = literal;
    }

    @Override
    public void params(Collection<Object> params) {
        this.params = params;
    }

    @Override
    public void method(QueryMethod method) {
        this.method = method;
    }

    private void setParam(PreparedStatement statement, int i, Object o) throws SQLException {
        statement.setObject(i, o);
    }

    @Override
    public ResultReader execute() {
        try (Connection connection = driver.connection(); PreparedStatement statement = connection.prepareStatement(literal)) {
            int i = 0;
            for (Object param : params) {
                i++;
                setParam(statement, i, param);
            }

            ResultReader reader = null;
            switch (method) {
                case RETRIEVE_DATA -> reader = driver.reader(statement.executeQuery());
                case UPDATE -> reader = driver.reader(statement.executeUpdate());
                case NORMAL -> reader = driver.reader(statement.execute());
            }

            return reader;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

Readers

In order to get data out of your queries we use a ResultReader object. The driver needs a way to get a reader for a ResultSet, for an int as specified in the JDBC driver and a boolean that represents the query success state.

Example implementation for the methods:

    @Override
    public ResultReader reader(ResultSet set) {
        return new CustomReaderSet(set);
    }

    @Override
    public ResultReader reader(int i) {
        return new CustomReaderSuccess(i > 0);
    }

    @Override
    public ResultReader reader(boolean success) {
        return new CustomReaderSuccess(success);
    }

Implementation of ReaderSet and ReaderSuccess:

import me.will0mane.libs.quill.results.ResultReader;

import java.sql.ResultSet;
import java.sql.SQLException;

public class CustomReaderSet implements ResultReader {

    private final ResultSet set;

    public CustomReaderSet(ResultSet set) {
        this.set = set;
    }

    @Override
    public boolean next() throws SQLException {
        return set.next();
    }

    @Override
    public boolean isSuccess() {
        return true;
    }

    @Override
    public <T> T get(int index) throws SQLException {
        return (T) set.getObject(index);
    }

    @Override
    public <T> T get(String column) throws SQLException {
        return (T) set.getObject(column);
    }
}

import me.will0mane.libs.quill.results.ResultReader;

public class CustomReaderSuccess implements ResultReader {

    private final boolean success;

    public CustomReaderSuccess(boolean success) {
        this.success = success;
    }

    @Override
    public boolean next() {
        return false;
    }

    @Override
    public boolean isSuccess() {
        return success;
    }

    @Override
    public <T> T get(int index) {
        return null;
    }

    @Override
    public <T> T get(String column) {
        return null;
    }
}

Instance of Quill

The FuncQuill object can be instantiated by passing the Driver instance to it like this:

QuillDriver driver = new CustomQuillDriver(survival);
Quill quill = new FuncQuill(driver);

Quill promotes reusability, as such, the Quill object can be shared across the JVM and used to send queries as the Connection provided by the Driver.

Clone this wiki locally