Skip to content

C Client

Ozan Tezcan edited this page May 7, 2021 · 1 revision

Installation

Client library distribution is source-based, copy resql.h and resql.c to your project.
C++ projects can use C client as well.

Click here for the files

Minimal example

Resql provides minimal API for clients. A quick example:

#include <stdio.h>
#include "resql.h"

int main() 
{
    resql *client;
    struct resql_result *rs;
    struct resql_column *row;

    resql_create(&client, &(struct resql_config){0});

    resql_put_sql(client, "SELECT 'Hello World!';");
    resql_exec(client, true, &rs);
    row = resql_row(rs);

    printf("%s \n", row[0].text);

    resql_shutdown(client);
    return 0;
}

Configuration

  • client_name : Unique client name, if not provided a random string will be assigned.
  • cluster_name : 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.
  • timeout_millis: 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.
  • outgoing_addr: Set outgoing address of the client, null for automatic address selection.
  • outgoing_port: Set outgoing port of the client, null for automatic port selection.
#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *client;
    struct resql_result *rs;
    struct resql_column *row;

    struct resql_config conf = {
            .client_name = "testclient",
            .cluster_name = "cluster",
            .timeout_millis = 4000,
            .urls = "tcp://127.0.0.1:7600",
    };

    rc = resql_create(&client, &conf);
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(client, "SELECT 'Hello World!';");
    resql_exec(client, true, &rs);
    row = resql_row(rs);
    printf("%s \n", row[0].text);

    rc = resql_shutdown(client);
    if (rc != RESQL_OK) {
        printf("Failed to shutdown client \n");
        exit(1);
    }

    return 0;
}

Executing Statements

Client has two functions to define operations: put_prepared() and put_sql(). These methods will put your operations into a buffer, you may call them more than once, then call resql_exec(). Basically, put_**() will batch your operations, resql_exec() will send it to the server and wait for a response.

#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *client;
    struct resql_result *rs;

    rc = resql_create(&client, &(struct resql_config){0});
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(client, "CREATE TABLE test (key TEXT, value TEXT);");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Clean-up
    resql_put_sql(client, "DROP TABLE test;");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_shutdown(client);
}

As you can see, resql_exec() has a boolean parameter. 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 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

#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *client;
    struct resql_result *rs;

    rc = resql_create(&client, &(struct resql_config){0});
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(client, "CREATE TABLE test (key TEXT, value TEXT);");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Option-1, with parameter name
    resql_put_sql(client, "INSERT INTO test VALUES(:name,:lastname);");
    resql_bind_param_text(client, ":name", "jane");
    resql_bind_param_text(client, ":lastname", "doe");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Option-2, with parameter index
    resql_put_sql(client, "INSERT INTO test VALUES(?, ?);");
    resql_bind_index_text(client, 0, "jane");
    resql_bind_index_text(client, 1, "doe");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Clean-up
    resql_put_sql(client, "DROP TABLE test;");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_shutdown(client);
}

Executing Queries

#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *client;
    struct resql_result *rs;

    rc = resql_create(&client, &(struct resql_config){0});
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(client, "CREATE TABLE test (key TEXT, value TEXT);");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_put_sql(client, "INSERT INTO test VALUES('jane','doe');");
    resql_put_sql(client, "INSERT INTO test VALUES('jack','doe');");
    resql_put_sql(client, "INSERT INTO test VALUES('joe','doe');");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_put_sql(client, "SELECT * FROM test;");
    rc = resql_exec(client, true, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    struct resql_column* row;
    while ((row = resql_row(rs)) != NULL) {
        printf("name : %s, lastname : %s \n", row[0].text, row[1].text);
    }

    // Clean-up
    resql_put_sql(client, "DROP TABLE test;");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_shutdown(client);
}

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 resql_del_prepared() method of the client or they will be freed automatically when the client closes the connection gracefully.

#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *client;
    resql_stmt stmt;
    struct resql_result *rs;

    rc = resql_create(&client, &(struct resql_config){0});
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(client, "CREATE TABLE test (key TEXT, value TEXT);");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Option-1, indexes
    rc = resql_prepare(client, "INSERT INTO test VALUES(?, ?);", &stmt);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_put_prepared(client, &stmt);
    resql_bind_index_text(client, 0, "jane");
    resql_bind_index_text(client, 1, "doe");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Clean-up if you're not going to use prepared statement again.
    rc = resql_del_prepared(client, &stmt);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Option-2, parameter names
    rc = resql_prepare(client, "INSERT INTO test VALUES(:name, :lastname);", &stmt);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }
    resql_put_prepared(client, &stmt);
    resql_bind_param_text(client, ":name", "jane");
    resql_bind_param_text(client, ":lastname", "doe");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Clean-up if you're not going to use prepared statement again.
    rc = resql_del_prepared(client, &stmt);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    // Clean-up
    resql_put_sql(client, "DROP TABLE test;");
    rc = resql_exec(client, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(client));
        exit(1);
    }

    resql_shutdown(client);
}

Executing multiple operations

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

#include <stdio.h>
#include "resql.h"

int main()
{
    int rc;
    resql *r;
    struct resql_result *rs;

    rc = resql_create(&r, &(struct resql_config){0});
    if (rc != RESQL_OK) {
        printf("Failed to create client \n");
        exit(1);
    }

    resql_put_sql(r, "CREATE TABLE test (key TEXT, value TEXT);");
    resql_put_sql(r, "INSERT INTO test VALUES('mykey', 1000);");
    rc = resql_exec(r, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(r));
        exit(1);
    }

    // Demo for getAndIncrement atomically.
    resql_put_sql(r, "SELECT * FROM test WHERE key = 'mykey';");
    resql_put_sql(r, "UPDATE test SET value = value + 1 WHERE key = 'mykey'");
    resql_put_sql(r, "SELECT * FROM test WHERE key = 'mykey';");
    rc = resql_exec(r, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(r));
        exit(1);
    }

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

    // First operation was SELECT
    struct resql_column *row = resql_row(rs);
    printf("Value was : %s \n", row[1].text);

    // Advance to the next result set which is for UPDATE.
    resql_next(rs);
    printf("Line changed : %d \n", resql_changes(rs));

    // Advance to the next result set which is for SELECT again.
    resql_next(rs);
    row = resql_row(rs);
    printf("Value is now : %s \n", row[1].text);

    // Clean-up
    resql_put_sql(r, "DROP TABLE test;");
    rc = resql_exec(r, false, &rs);
    if (rc != RESQL_OK) {
        printf("Operation failed : %s \n", resql_errstr(r));
        exit(1);
    }

    resql_shutdown(r);
}

Clone this wiki locally