Skip to content

Java Client

Ozan Tezcan edited this page May 18, 2021 · 4 revisions

Installation

Maven

<dependency>
    <groupId>com.github.tezc</groupId>
    <artifactId>resql</artifactId>
    <version>0.1.3</version>
</dependency>

Minimal example

Resql provides minimal API for clients. A quick example:

try (Resql client = ResqlClient.create(new Config())) {

    client.put("SELECT 'Hello World!'");
    ResultSet rs = client.execute(true);

    for (Row row : rs) {
         System.out.println(row);
    }

} catch (Exception e) {
    e.printStackTrace();
}

Configuration

The configuration has 6 parameters :

clientName : Unique client name, if not provided a random string will be assigned.
clusterName : Cluster name must match with configured cluster name on servers. This is just a simple security check to prevent clients from connecting to the wrong cluster.
timeoutMillis: Timeout for any operation of the client. This timeout applies to connection/reconnection time and query execution time.
urls: Server urls. It is okay to give just one URL. Client will get other nodes' urls when it's connected to one node.
outgoingAddr: Set outgoing address of the client, null for automatic address selection.
outgoingPort: Set outgoing port of the client, null for automatic port selection.

Config c = new Config().setClientName("test-client")
                       .setClusterName("cluster")
                       .setTimeoutMillis(10000)
                       .setUrls(Arrays.asList("tcp://127.0.0.1:7600",
                                              "tcp://127.0.0.1:7601"));
try (Resql client = ResqlClient.create(c)) {
    client.put("SELECT 'Hello World!'");
    client.execute(true);
}  catch (Exception e) {
    e.printStackTrace();
}

Executing statements

The client has one method to define operations: put(). This method will put your operations into a buffer, you may call it more than once. put() will batch your operations, execute() will send it to the server and wait for a response.

try (Resql client = ResqlClient.create(new Config())) {
    client.put("CREATE TABLE test (key TEXT, value TEXT);");

    // 'true' for readonly operations only, e.g : SELECT.
    client.execute(false);

    // Cleanup
    client.put("DROP TABLE test;");
    client.execute(false);
} catch (Exception e) {
    e.printStackTrace();
}

As you can see, execute has a boolean parameter: execute(boolean readonly)
If your operation is read-only, e.g SELECT, set this parameter to true. This is an optimization. Readonly operations don't change the state of the database, so no need to write these operations to the WAL file. If you set read-only to true even your operation is not, the server will reject your operation and you'll catch that error while developing your application.

Binding parameters

try (Resql client = ResqlClient.create(new Config())) {
    client.put("CREATE TABLE test (key TEXT, value TEXT);");
    client.execute(false);

    // Option 1, with parameter name
    client.put("INSERT INTO test VALUES(:name,:lastname);");
    client.bind(":name", "jane");
    client.bind(":lastname", "doe");
    client.execute(false);

    // Option 2, with index
    client.put("INSERT INTO test VALUES(?, ?)");
    client.bind(0, "jack");
    client.bind(1, "does");
    client.execute(false);

    // Cleanup
    client.put("DROP TABLE test;");
    client.execute(false);
} catch (Exception e) {
    e.printStackTrace();
}

Executing Queries

try (Resql client = ResqlClient.create(new Config())) {
    client.put("CREATE TABLE test (name TEXT, lastname TEXT);");

    // 'true' for readonly operations only, e.g : SELECT.
    client.execute(false);

    // You can execute multiple operations in one round-trip
    client.put("INSERT INTO test VALUES('jane', 'doe');");
    client.put("INSERT INTO test VALUES('jack', 'doe');");
    client.put("INSERT INTO test VALUES('joe', 'doe');");

    client.execute(false);

    client.put("SELECT * FROM test;");

    // This is a readonly operation, we can pass 'true'
    ResultSet rs = client.execute(true);

    for (Row row : rs) {
        System.out.println(" name : " + row.get("name") +
                          " last_name : " + row.get("lastname"));
    }

    // Cleanup
    client.put("DROP TABLE test;");
    client.execute(false);
} catch (Exception e) {
    e.printStackTrace();
}

Prepared statements

If you're going to execute a statement many times, prepare the statement and use it for better performance. Prepared statements are kept on servers. They can be deleted by calling delete() method of the client or they will be freed automatically when the client closes the connection gracefully.

try (Resql client = ResqlClient.create(new Config())) {
    client.put("CREATE TABLE test (name TEXT, lastname TEXT);");
    client.execute(false);
    PreparedStatement statement;

    //Option-1, with parameter index
    statement = client.prepare("INSERT INTO test VALUES(?,?)");
    client.put(statement);
    client.bind(0, "jane");
    client.bind(1, "doe");
    client.execute(false);

    // Clean-up when done.
    client.delete(statement);

    //Option-2, with parameter name
    statement = client.prepare("INSERT INTO test VALUES(:name,:lastname)");
    client.put(statement);
    client.bind(":name", "jane");
    client.bind(":lastname", "doe");
    client.execute(false);

    // Clean-up when done.
    client.delete(statement);

    // Cleanup
    client.put("DROP TABLE test;");
    client.execute(false);
} catch (Exception e) {
    e.printStackTrace();
}

Executing multiple operations

If you put() multiple operations and call execute(), these operations will be processed atomically. Either all succeed or fail. You can even combine INSERT's with SELECT's.

try (Resql client = ResqlClient.create(new Config())) {
    client.put(
            "CREATE TABLE test (key TEXT PRIMARY KEY, value INTEGER)")
    client.put("INSERT INTO test VALUES('mykey', 0);");
    client.execute(false);

    // Demo for getAndIncrement atomically.
    client.put("SELECT * FROM test WHERE key = 'mykey';");
    client.put("UPDATE test SET value = value + 1 WHERE key = 'mykey'");
    client.put("SELECT * FROM test WHERE key = 'mykey';");

    // Parameter is false as we have an UPDATE in batch.
    ResultSet rs = client.execute(false);

    // rs has three result sets, each corresponds to operations 
    // that we added into the batch.

    // First operation was SELECT
    for (Row row : rs) {
        System.out.println("Value was : " + row.get("value"));
    }

    // Advance to the next result set which is for INSERT.
    rs.nextResultSet();
    System.out.println("Changes : " + rs.linesChanged());

    // Advance to the next result set which is for SELECT again.
    rs.nextResultSet();
    for (Row row : rs) {
        System.out.println("Value is now : " + row.get("value"));
    }

    // Cleanup
    client.put("DROP TABLE test;");
    client.execute(false);
} catch (Exception e) {
    e.printStackTrace();
}