DEV Community

Cong Li
Cong Li

Posted on

GBase 8a MPP Cluster Development Interface: C-API

Today, we'll introduce the GBase 8a C API interface driver used to connect to the GBase database server. This section will demonstrate how to use the GBase 8a C API functions and provide examples of programming with the GBase C API.

1. GBase C API

1.1 Data Types

1.1.1 GBASE

Structure Description:

This structure represents a handle to the database connection. It is not recommended to copy the GBase structure as the result of such a copy may not be reliable.

1.1.2 GBASE_RES

Structure Description:

This structure is used to store the result set returned by SELECT, SHOW, DESCRIBE, EXPLAIN queries.

1.1.3 GBASE_ROW

Structure Description:

This structure is used to store one row of data. It is implemented as an array of counted byte strings. (If a field value may contain binary data, it cannot be treated as a null-terminated string because such values may contain null bytes.) It is retrieved using gbase_fetch_row().

1.1.4 GBASE_FIELD

Structure Description:

This structure is used to store field information (name, type, size). You can obtain a GBASE_FIELD structure for each field by repeatedly calling gbase_fetch_field().

Structure Members:

Name Type Description
name char* Field name as a null-terminated string. If an alias was specified for this field using an AS clause, the value of name is the alias.
org_name char* Original field name as a null-terminated string. Ignores the alias.
table char* Name of the table containing the field, if the field is not a computed field. For computed fields, table is an empty string. If an alias is specified for the table using an AS clause, the value of table is the alias.
org_table char* Original table name as a null-terminated string. Ignores the alias.
db char* Name of the database from which the field originates as a null-terminated string. For computed fields, db is an empty string.
catalog char* Catalog name, always "def".
def char* Default value of the field as a null-terminated string. It is set only when using GBASE_list_fields().
length unsigned long Width of the field as specified in the table definition.
max_length unsigned long Maximum width of the field in the result set (length of the longest field value in the result set). It contains the maximum length if gbase_store_result() or gbase_list_fields() is used. If gbase_use_result() is used, this variable is 0.
name_length unsigned int Length of the name.
org_name_length unsigned int Length of org_name.
table_length unsigned int Length of the table.
org_table_length unsigned int Length of org_table.
db_length unsigned int Length of the db.
catalog_length unsigned int Length of the catalog.
def_length unsigned int Length of the def.
flags unsigned int Different "bit flags" used for the field.
decimals unsigned int Number of decimals for numeric fields.
charsetnr unsigned int Character set number used for the field.
type enum_field_types Type of the field. The type value can be one of the GBASE_TYPE_ symbols listed below.

Flag Field Value Collection:

Flag Value Flag Description
NOT_NULL_FLAG Field cannot be NULL.
PRI_KEY_FLAG Field is part of a primary key.
UNIQUE_KEY_FLAG Field is part of a unique key.
MULTIPLE_KEY_FLAG Field is part of a non-unique key.
UNSIGNED_FLAG Field has the UNSIGNED attribute.
ZEROFILL_FLAG Field has the ZEROFILL attribute.
BINARY_FLAG Field has the BINARY attribute.
AUTO_INCREMENT_FLAG Field has the AUTO_INCREMENT attribute.
ENUM_FLAG Field is an ENUM (no longer in use).
SET_FLAG Field is a SET (no longer in use).
BLOB_FLAG Field is a BLOB or TEXT (no longer in use).
TIMESTAMP_FLAG Field is a TIMESTAMP (no longer in use).

Typical Usage of Flag Values:

if (field->flags & NOT_NULL_FLAG)
    printf("Field can't be null\n");
Enter fullscreen mode Exit fullscreen mode

Macros to Define the Boolean State of Flag Values:

Flag State Description
IS_NOT_NULL(flags) True if the field is defined as NOT NULL.
IS_PRI_KEY(flags) True if the field is a primary key.
IS_BLOB(flags) True if the field is a BLOB or TEXT. (No longer in use, replaced by testing field->type).

Type Field Value Collection:

Type Value Type Description
GBASE_TYPE_TINY TINYINT field.
GBASE_TYPE_SHORT SMALLINT field.
GBASE_TYPE_LONG INTEGER field.
GBASE_TYPE_INT24 MEDIUMINT field.
GBASE_TYPE_LONGLONG BIGINT field.
GBASE_TYPE_DECIMAL DECIMAL or NUMERIC field.
GBASE_TYPE_NEWDECIMAL Precision DECIMAL or NUMERIC.
GBASE_TYPE_FLOAT FLOAT field.
GBASE_TYPE_DOUBLE DOUBLE or REAL field.
GBASE_TYPE_BIT BIT field.
GBASE_TYPE_TIMESTAMP TIMESTAMP field.
GBASE_TYPE_DATE DATE field.
GBASE_TYPE_TIME TIME field.
GBASE_TYPE_DATETIME DATETIME field.
GBASE_TYPE_YEAR YEAR field.
GBASE_TYPE_STRING CHAR field.
GBASE_TYPE_VAR_STRING VARCHAR field.
GBASE_TYPE_BLOB BLOB or TEXT field (use max_length to determine maximum length).
GBASE_TYPE_SET SET field.
GBASE_TYPE_ENUM ENUM field.
GBASE_TYPE_GEOMETRY Spatial field.
GBASE_TYPE_NULL NULL-type field.
GBASE_TYPE_CHAR No longer in use, replaced by GBASE_TYPE_TINY.

1.1.5 GBASE_FIELD_OFFSET

Structure Description:

This is a "type-safe" representation of the GBASE field list offset (used by gbase_field_seek()). The offset is the field number within the row, starting at 0.

1.1.6 gs_ulonglong

Structure Description:

This type is used for row counts and the return types of gbase_affected_rows() and gbase_num_rows(). The range provided by this type is 0-1.84e19. On some systems, printing values of type gs_ulonglong may not be possible. To print such values, cast them to an unsigned long integer type and use the %lu format specifier, like so: printf("Number of rows: %lu\n", (unsigned long) gbase_num_rows(result));

2 C API Prepared Statements

2.1 Data Types

2.1.1 GBASE_STMT

Description:

This structure represents a prepared statement. The statement is created by calling gbase_stmt_init(), which returns a statement handle, i.e., a pointer to GBASE_STMT. This handle is used in all subsequent functions related to the statement until it is closed using gbase_stmt_close().

The GBASE_STMT structure has no parameters accessible by the application. Additionally, copying the GBASE_STMT structure is not recommended, as such copies are not guaranteed to be useful. Multiple statement handles can be associated with a single connection, with the number of handles limited only by system resources.

2.1.2 GBASE_BIND

Description:

This structure is used for both input (data values sent to the server) and output (result values returned from the server) in statements. For input, it is used with gbase_stmt_bind_param() to bind parameter data values to buffers for use by gbase_stmt_execute(). For output, it is used with gbase_stmt_bind_result() to bind result buffers for use by gbase_stmt_fetch() to retrieve rows.

The GBASE_BIND structure contains the following members for use by the application. Each member is used for both input and output, but in some cases, they serve different purposes depending on the direction of data transmission.

Structure Members:

Name Type Description
buffer_type enum_field_types The type of the buffer.
buffer void * A pointer to the buffer.
buffer_length unsigned long The actual size of the buffer in bytes.
length unsigned long * A pointer to an unsigned long variable that indicates the actual number of bytes stored in *buffer.
is_null gs_bool * Points to a gs_bool variable. If the value is NULL, the variable is true; if non-NULL, the variable is false. is_null is a pointer to a boolean, not a boolean scalar, allowing usage as follows:
1. If the data value is always NULL, bind the column using GBASE_TYPE_NULL.
2. If the data value is always NOT NULL, set is_null = (gs_bool*) 0.
3. In all other cases, is_null should be set to the address of a gs_bool variable, and its value should be appropriately changed between executions to indicate whether the data value is NULL or NOT NULL.
is_unsigned gs_bool For unsigned types, set is_unsigned to true; for signed types, set it to false.
error gs_bool For output, this member is used to report data truncation errors. To enable truncation reporting, call gbase_options() with the GBASE_REPORT_DATA_TRUNCATION option. When enabled, gbase_stmt_fetch() returns GBASE_DATA_TRUNCATED, and for parameters that experience truncation, the error flag in the GBASE_BIND structure is set to true. Truncation indicates the loss of a sign or significant digits, or that a string is too long to fit within a column.

2.1.3 GBASE_TIME

Description:

This structure is used to send DATE, TIME, DATETIME, and TIMESTAMP data directly to the server or receive such data directly from the server.

Structure Members:

Name Type Description
year unsigned int Year
month unsigned int Month
day unsigned int Day
hour unsigned int Hour
minute unsigned int Minute
second unsigned int Second
neg unsigned int Boolean flag indicating if the time is negative

3 GBase C API Application Examples

3.1 Creating a Connection Using the GBase C API

GBASE* gbase = NULL;
/* Initialize GBASE structure */
if(!(gbase = gbase_init(0))) {
    fprintf(stderr, "Unable to initialize GBASE structure!\n");
    exit(1);
}
/* Database connection */
if(!gbase_real_connect(gbase, host, user, passwd, db, port, NULL, 0)) {
    fprintf(stderr, "\n%s\n", gbase_error(gbase));
    exit(1);
}
/* Release database connection handle */
gbase_close(gbase);
Enter fullscreen mode Exit fullscreen mode

3.2 Connecting to a Cluster Using the GBase C API

GBASE* gbase = NULL;
CHAR* host = "192.168.5.64;192.168.5.67;"; // IP addresses of cluster nodes
/* Initialize GBASE structure */
if(!(gbase = gbase_init(0))) {
    fprintf(stderr, "Unable to initialize GBASE structure!\n");
    exit(1);
}
/* Database connection */
if(!gbase_real_connect(gbase, host, user, passwd, db, port, NULL, 0)) {
    fprintf(stderr, "\n%s\n", gbase_error(gbase));
    exit(1);
}
/* Release database connection handle */
gbase_close(gbase);
Enter fullscreen mode Exit fullscreen mode

3.3 Executing a Stored Procedure Using the GBase C API

/* Execute stored procedure */
void demo_3(void) {
    GBASE* gbase = NULL;
    char* sql_drop = "DROP TABLE IF EXISTS g_demo3";
    char* sql = "CREATE TABLE g_demo3(a VARCHAR(10), b INT)";
    char* p_drop = "DROP PROCEDURE IF EXISTS demo_p";
    char* p = "CREATE PROCEDURE demo_p(IN a VARCHAR(10), IN b INT) BEGIN INSERT INTO g_demo3(a, b) VALUES (a, b); END;";
    char* call_p = "CALL demo_p('call_p', 1)";

    if(!(gbase = gbase_init(0))) {
        fprintf(stderr, "Unable to initialize GBASE structure!\n");
        exit(1);
    }
    if(!gbase_real_connect(gbase, host, user, passwd, db, port, NULL, 0)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }
    /* Create table in the database */
    if(gbase_query(gbase, sql_drop)) {
        fprintf(stderr, "%s", gbase_error(gbase));
        exit(1);
    }
    if(gbase_query(gbase, sql)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }
    if(gbase_query(gbase, p_drop)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }
    if(gbase_query(gbase, p)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }
    if(gbase_query(gbase, call_p)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }
    printf("PROCEDURE execution result\n");
    print_table(gbase, "g_demo3");
    gbase_close(gbase);
}
Enter fullscreen mode Exit fullscreen mode

3.4 Using the GBase C API in a Multi-threaded Environment

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "gbase.h"
#include <pthread.h>

static pthread_mutex_t lock_thread_num;
static pthread_cond_t cond_thread_num;
static int thread_num;

void* th_new(void* arg) {
    GBASE* gbase;
    GBASE_RES* res;
    gbase_thread_init();
    gbase = gbase_init(0);

    if(!gbase_real_connect(gbase, "192.168.111.96", "root", "1111", "test", 5258, NULL, 0)) {
        fprintf(stderr, "%s\n", gbase_error(gbase));
        goto exit;
    }

exit:
    gbase_close(gbase);
    gbase_thread_end();

    pthread_mutex_lock(&lock_thread_num);
    thread_num--;
    pthread_cond_signal(&cond_thread_num);
    pthread_mutex_unlock(&lock_thread_num);
    pthread_exit(0);
    return 0;
}

int main() {
    pthread_t t;
    unsigned int i = 10;
    int error;
    pthread_attr_t thr_attr;

    if(gbase_library_init(0, NULL, NULL)) {
        exit(1);
    }

    thread_num = i;

    if ((error = pthread_cond_init(&cond_thread_num, NULL))) {
        exit(1);
    }

    pthread_mutex_init(&lock_thread_num, NULL);

    if ((error = pthread_attr_init(&thr_attr))) {
        exit(1);
    }

    if ((error = pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) {
        exit(1);
    }

    while(i--) {
        pthread_create(&t, &thr_attr, th_new, NULL);
    }

    pthread_mutex_lock(&lock_thread_num);
    while(thread_num > 0) {
        if ((error = pthread_cond_wait(&cond_thread_num, &lock_thread_num))) {
            fprintf(stderr, "\nGot error: %d from pthread_cond_wait\n", error);
        }
    }

    pthread_mutex_unlock(&lock_thread_num);
    pthread_mutex_destroy(&lock_thread_num);

    gbase_library_end();
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

3.5 Fast Data Insertion Using Prepared Statements

#include <windows.h>
#include <time.h>
#include <stdio.h>
#include "gbase.h"

#define NUM_INSERT 1000

void main(void) {
    GBASE* gbase = NULL;
    GBASE_STMT* stmt;
    GBASE_BIND bind[2];
    const char* sql_drop_table = "DROP TABLE IF EXISTS g_demo2";
    const char* sql_create_table = "CREATE TABLE g_demo2(id INT, name VARCHAR(20))";
    const char* insert_mode = "INSERT INTO g_demo2(id, name) VALUES(?, ?)";

    int in_id[NUM_INSERT];
    char in_name[NUM_INSERT][20];
    unsigned int i = 0;

    char* host = "192.168.111.96";
    char* user = "gbase";
    char* passwd = "gbase20110531";
    char* db = "test";
    int port = 5258;

    /* Initialize GBASE structure */
    if(!(gbase = gbase_init(0))) {
        fprintf(stderr, "Unable to initialize GBASE structure!\n");
        exit(1);
    }

    /* Database connection */
    if(!gbase_real_connect(gbase, host, user, passwd, db, port, NULL, 0)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }

    gbase->reconnect = 1;

    /* Create table in the database */
    if(gbase_query(gbase, sql_drop_table)) {
        fprintf(stderr, "%s", gbase_error(gbase));
        exit(1);
    }

    if(gbase_query(gbase, sql_create_table)) {
        fprintf(stderr, "\n%s\n", gbase_error(gbase));
        exit(1);
    }

    gbase_autocommit(gbase, 0);

    /* Insert data into the table */
    for(i = 0; i < NUM_INSERT; i++) {
        in_id[i] = i;
        sprintf(in_name[i], "name_%d", i);
    }

    if(!(stmt = gbase_stmt_init(gbase))) {
        fprintf(stderr, "\n%s\n", gbase_stmt_error(stmt));
        exit(1);
    }

    if(gbase_stmt_prepare(stmt, insert_mode, strlen(insert_mode))) {
        fprintf(stderr, "\n%s\n", gbase_stmt_error(stmt));
        exit(1);
    }

    memset(bind, 0, sizeof(bind));
    bind[0].buffer_type = GBASE_TYPE_LONG;
    bind[1].buffer_type = GBASE_TYPE_STRING;

    printf("Starting to insert %d records\n", NUM_INSERT);
    for(i = 0; i < NUM_INSERT; i++) {
        bind[0].buffer = (void*)&in_id[i];
        bind[1].buffer = (void*)in_name[i];
        bind[1].buffer_length = strlen(in_name[i]);

        if(gbase_stmt_bind_param(stmt, bind)) {
            fprintf(stderr, "\n%s\n", gbase_stmt_error(stmt));
            exit(1);
        }

        if(gbase_stmt_execute(stmt)) {
            fprintf(stderr, "\n%s\n", gbase_stmt_error(stmt));
            exit(1);
        }
    }

    gbase_commit(gbase);

    /* Close the statement handle */
    if(gbase_stmt_close(stmt)) {
        fprintf(stderr, "%s\n", gbase_stmt_error(stmt));
        exit(1);
    }

    /* Release database connection handle */
    gbase_close(gbase);
}
Enter fullscreen mode Exit fullscreen mode

3.6 Load Balancing with the GBase C API

When creating a connection to an 8a cluster using the GBase C API with load balancing, the client should at least pass the IP addresses of the 8a cluster gcluster nodes to the GBase C API. For example, host="192.168.1.1;192.168.1.2". If there is only one node, the client should pass it to the GBase C API like this: host="192.168.1.1;". The semicolon after the IP address in the string is mandatory.

When creating a connection to an 8a cluster using the GBase C API with load balancing, if the "GBASE_OPT_USE_SERVER_BALANCE" option is not set (the default value is 0), the GBase C API will perform load balancing between the IP addresses provided by the client. If the "GBASE_OPT_USE_SERVER_BALANCE" option is set to 1, the GBase C API will perform load balancing across all nodes in the 8a cluster. Below is a code example for using load balancing with the GBase C API.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "gbase.h"
int test_banalce()
{
GBASE* gbase = NULL;
char* host = "192.168.1.1;";
char* user = "gbase";
char* pwd = "gbase20110531";
char* db = "test";
int  port = 5258;
int rc = 0;
int use_server_balance = 1;
gbase = gbase_init(NULL);
gbase_options(gbase,  GBASE_OPT_USE_SERVER_BALANCE, (void*)&use_server_balance);
if(!gbase_real_connect(gbase, host, user, pwd, db, port, NULL, 0))
{
fprintf(stderr, "%d\n%s\n",gbase_errno(gbase), gbase_error(gbase));
rc = 1;
}
else
{
printf("%s\n", gbase_get_host_info(gbase));
}    
gbase_close(gbase);
return rc;
}
int main()
{
int i = 0;
for(;i < 20; i++)
{
test_banalce();
usleep(2*1000);
}
return 0;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)