HOWTO extend dbx

How-to code support for another database
Every supported database module must be loaded by PHP before it can be used. Every supported database module must be added to the dbx-module before it can be used. Currently there is support for MySQL, PostgreSQL, Microsoft SQL Server, Frontbase, Sybase-CT, Oracle (oci8) and ODBC. It is not difficult to add support for more databases.
The dbx module is found in de PHP ext/dbx folder. The support-code is found in the same folder
To add support for module 'blabla' the following steps must be taken:
1. the dbx.c source file must be extended to recognize module 'blabla' and switch to the 'blabla' functions.
2. the files dbx_blabla.h and dbx_blabla.c must be created and edited to produce the required response.
3. add the files from step 2 to the project.
4. compile.
5. enjoy.
You may need a bit of help for step 1 and 2. If you need help for step 3 or 4, you shouldn't try to attempt this probably :-). If you need help with step 5 you're in big trouble ;o)
Help for step 1 and 2 is given below, bold text in code indicate the important bits.

home

1. the dbx.c source file must be extended
Define a module identifier and assign it a unique number. Include your header file here as well.

    // defines for supported databases
    #define DBX_UNKNOWN 0
    #define DBX_MYSQL 1
    #define DBX_ODBC 2
    #define DBX_BLABLA 3
    // includes for supported databases
    #include "dbx.h"
    #include "dbx_mysql.h"
    #include "dbx_odbc.h"
    #include "dbx_blabla.h"

Add code to the module_identifier_exists function so DBX_BLABLA will be recognized:

    int module_identifier_exists(long module_identifier) {
        switch (module_identifier) {
            case DBX_MYSQL: return module_exists("mysql");
            case DBX_ODBC: return module_exists("odbc");
            case DBX_BLABLA: return module_exists("blabla");
            }
        return 0;
        }

Add code to the get_module_identifier function so your extension will be recognized:

    int get_module_identifier(char * module_name) {
        if (!strcmp("mysql", module_name)) return DBX_MYSQL;
        if (!strcmp("odbc", module_name)) return DBX_ODBC;
        if (!strcmp("blabla", module_name)) return DBX_BLABLA;
        return DBX_UNKNOWN;
        }

Add code for exposing the DBX_BLABLA constant to the world:

    ZEND_MINIT_FUNCTION(dbx)
    {
    /*/	REGISTER_INI_ENTRIES(); /*/

        REGISTER_LONG_CONSTANT("DBX_MYSQL", DBX_MYSQL, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("DBX_ODBC", DBX_ODBC, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("DBX_BLABLA", DBX_BLABLA CONST_CS | CONST_PERSISTENT);

        [...]

        return SUCCESS;
        }

Add code for inclusion in the phpinfo() function (optional, but recommended):

    ZEND_MINFO_FUNCTION(dbx)
    {
        php_info_print_table_start();
        php_info_print_table_row(2, "dbx support", "enabled");
        php_info_print_table_row(2, "dbx support for MySQL", "enabled");
        php_info_print_table_row(2, "dbx support for ODBC", "enabled");
        php_info_print_table_row(2, "dbx support for BlaBla", "enabled");
        php_info_print_table_end();
        DISPLAY_INI_ENTRIES();
    }

Finally, for the implementation of all switch_dbx_XXXXX functions, copy a 'case'-line for every function that you support (should be all functions!). Here is an example for only the switch_dbx_connect function:

    int switch_dbx_connect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS, zval **dbx_module) {
        // returns connection handle as resource on success or 0 as long on failure
        switch ((*dbx_module)->value.lval) {
            case DBX_MYSQL: return dbx_mysql_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);
            case DBX_ODBC: return dbx_odbc_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);
            case DBX_BLABLA: return dbx_blabla_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);
            }
        zend_error(E_WARNING, "dbx_connect: not supported in this module");
        return 0;
        }

This should be done for all switch_dbx_XXXXX functions. They are listed below:

    int switch_dbx_connect(...);
    int switch_dbx_pconnect(...);
    int switch_dbx_close(...);
    int switch_dbx_query(...);
    int switch_dbx_getcolumncount(...);
    int switch_dbx_getcolumnname(...);
    int switch_dbx_getcolumntype(...);
    int switch_dbx_getrow(...);
    int switch_dbx_error(...);

This concludes the changes for the dbx.c file. All that is needed now is to actually code the dbx_blabla_connect and other functions, which we will see in the following step.

top

2. the files dbx_blabla.h and dbx_blabla.c
The dbx_blabla.h and dbx_blabla.c file are created in the folder /ext/dbx.
The easiest method is to just copy dbx_mysql.h en dbx_mysql.c, open both files, and do a search and replace ('blabla' for 'mysql' and 'BLABLA' for 'MYSQL'). Yes, case-sensitive.
For the .h file, that's all.
For the .c file, the fun has just started :-)
In the .c is the actual realization of the database abstraction, where a call to a standard function is translated into one or more database-specific calls. For mysql, a dbx_connect translates to a mysql_connect followed by a mysql_select_db. Refer to the dbx_mysql.c and dbx_odbc.c files regularly for examples!
In dbx.h one macro and one function are defined to make the calling of external module functions and returning of the results easier: dbx_call_any_function and MOVE_RETURNED_TO_RV.

The details of what each of the functions do, what parameters they get, and what parameters they should return are discussed below. But first, the dbx_mysql_connect function is presented and explained, so you get an idea of how things work.

    int dbx_mysql_connect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS) {
        // returns connection handle as resource on success or 0 as long on
        // failure
        int number_of_arguments;
        zval **arguments[3];
        zval * returned_zval=NULL;
        zval * select_db_zval=NULL;

        number_of_arguments=3;
        arguments[0]=host;
        arguments[1]=username;
        arguments[2]=password;
        dbx_call_any_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "mysql_connect", &returned_zval, number_of_arguments, arguments);
        if (!returned_zval || returned_zval->type!=IS_RESOURCE) {
            if (returned_zval) zval_ptr_dtor(&returned_zval);
            return 0;
            }
        MOVE_RETURNED_TO_RV(rv, returned_zval);

        number_of_arguments=2;
        arguments[0]=db;
        arguments[1]=rv;
        dbx_call_any_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "mysql_select_db", &select_db_zval, number_of_arguments, arguments);
        zval_ptr_dtor(&select_db_zval);

        return 1;
        }

First of all, all functions return 0 on failure and 1 on success. These values are used in the dbx-routines, they are never actually given back to the PHP-script writer that calls the dbx_connect function.
The actual value that is of interest to the caller is returned in the rv parameter. In this case it is a connection handle (or link identifier, in mysql-speak), that is also returned if the database selection doesn't succeed.
The parameters that are of interest to the function are located between the rv and INTERNAL_FUNCTION_PARAMETERS parameters, in this case it is a host name, a db name, a username and a password. These are the values that the user specifies if he calls dbx_connect(); These parameters are used in the calls to the mysql-database functions. The user actually also specifies a module-name, that decides which connect-function should be called. Here, he specified 'mysql'.
To actually call a mysql module function, you can use dbx_call_any_function where you specify the function name (it is used twice in dbx_mysql_connect, see 'mysql_connect' and 'mysql_select_db', they are printed bold in the code). The value that is returned from the function will be stored in the next argument, a zval * (e.g. returned_zval) parameter that you must declare locally. To actually return such a parameter, use the MOVE_RETURNED_TO_RV(rv, returned_zval) macro, which copies the values to rv and frees anything that may be left in returned_zval. Parameters that must be passed to the mysql-function are stored in the arguments array, which must be large enough to hold all parameters to the function-call that requires the most parameters (in this case, mysql_connect expects 3 parameters, mysql_select_db expects two parameters, so the arguments array is defined 'zval **arguments[3]'). The number_of_arguments parameter is set to the actual number of arguments that the function-call requires. As you can see it is initialized to 3, for the first call to mysql_connect. Then it is set to 2, for the call to mysql_select_db. If you call a function that retrieves a value, and you don't return it with MOVE_RETURNED_TO_RV, then you must free the value using zval_ptr_dtor, as can be seen right after the call to mysql_select_db. This can also be seen directly after the call to mysql_connect, if somehow this function failed or didn't return a resource (on a successful connect mysql_connect returns a resource) the returned value is freed as well (and 0 is returned because the connection failed).

OK, now the description of all functions that you should implement, and what is expected of them...

    int dbx_blabla_connect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on connect-failure and 1 on success
    // rv: connection handle as resource on success or nothing on failure

dbx_blabla_connect creates a connection to a database on a specified host, using username and password for authentication. This may be done by connecting to a server and selecting a database (as mysql does), or connecting to a specific database directly (as in ODBC).
What must be returned (in rv) is the link identifier that is returned from the blabla_connect function, in it's native form so the end-user can use $db->handle to call other blabla_* functions that expect this parameter.
What must be returned from the function is a 1 on success and a 0 on failure. Remember that a failed database selection can still return a 1 because the connection succeeded!
The host (string) is the name of the machine the server is run on, but it may be empty if a database name is enough to establish a connection.
The db (string) is the name of the database to select, or, for e.g. ODBC, the identifier that is needed to actually select the database.
The username (string) and password (string) are used for authentication.

    int dbx_blabla_pconnect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on pconnect-failure and 1 on success
    // rv: persistent connection handle as resource on success or nothing
    // on failure

dbx_blabla_pconnect is identical to dbx_blabla_connect except that it will create a persistent connection.

    int dbx_blabla_close(zval **rv, zval **dbx_handle, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on close-failure and 1 on success
    // rv: 1 as bool on success or nothing on failure

dbx_blabla_close closes an open connection, whether it was created persistently or not.
What must be returned (in rv) is a boolean true that indicates when the connection was closed successfully. If it wasn't, no value is returned in rv.
What must be returned from the function is a 1 on success and a 0 on failure. Note that an unsuccessful close is still a succeeded function call.
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.

    int dbx_blabla_query(zval **rv, zval **dbx_handle, zval **sql_statement, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on query-failure and 1 on success
    // rv: 1 as bool or a result identifier as resource on success
    // or nothing on failure

dbx_blabla_query executes an SQL statement over the connection.
What must be returned (in rv) is a nothing on failure, on success it must return either a boolean 1 for queries that don't return data (like INSERT INTO) or a native result-handle for queries that do return data (SELECT). The native result handle ($q->handle) can be used by the end-user to call other blabla_* functions that expect this parameter.
What must be returned from the function is a 1 on success and a 0 on failure. Note that a failed query execution can still return a 1 because the query function succeeded!
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.
The sql_statement (string) can have any value.

    int dbx_blabla_getcolumncount(zval **rv, zval **result_handle, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on query-failure and 1 on success
    // returns column-count as long on success or nothing on failure

dbx_blabla_getcolumncount gets the number of fields that the query-result contains.
What must be returned (in rv) is the number of fields as long from the query result specified by the result_handle.
What must be returned from the function is a 1 on success and a 0 on failure.
The result_handle is the same value that you returned from dbx_query.

    int dbx_blabla_getcolumnname(zval **rv, zval **result_handle, long column_index, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on failure and 1 on success
    // returns column-name as string on success or nothing on failure

dbx_blabla_getcolumnname gets the fieldname of the specified column.
What must be returned (in rv) is the fieldname as string of the given column.
What must be returned from the function is a 1 on success and a 0 on failure.
The result_handle is the same value that you returned from dbx_query.
The column_index is a long that ranges from 0 to the value you returned from dbx_blabla_getcolumncount minus 1 [0..columncount-1].

    int dbx_blabla_getcolumntype(zval **rv, zval **result_handle, long column_index, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on failure and 1 on success
    // returns column-type as string on success or nothing on failure

dbx_blabla_getcolumnname gets the field type of the specified column.
What must be returned (in rv) is the field type as string of the given column.
What must be returned from the function is a 1 on success and a 0 on failure.
The result_handle is the same value that you returned from dbx_query.
The column_index is a long that ranges from 0 to the value you returned from dbx_blabla_getcolumncount minus 1 [0..columncount-1].

    int dbx_blabla_getrow(zval **rv, zval **result_handle, long row_number, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on failure and 1 on success
    // returns array[0..columncount-1] as strings on success or 0 as long
    // on failure

dbx_blabla_getrow gets the next row from the query-results.
In some cases (PostgreSQL) the rownumber is needed to actually fetch the row. This will be provided (it will be indexed starting at 0) by the dbx_query function. In other cases it is not needed and thus not used.
What must be returned (in rv) is an indexed array[0..columncount-1] of strings, containing the data from the row (for mysql this is easy since it already performs this way, for ODBC the array has to be constructed inside this function from a loop that fetches the data for each column).
What must be returned from the function is a 1 on success and a 0 on failure (function failed or there are no more rows available).
The result_handle is the same value that you returned from dbx_query.

    int dbx_blabla_error(zval **rv, zval **dbx_handle, INTERNAL_FUNCTION_PARAMETERS);
    // int: returns 0 on failure and 1 on success
    // returns error message as string

dbx_blabla_error gets the error message from the last database call.
What must be returned (in rv) is the error message as a string.
What must be returned from the function is a 1 on success and a 0 on failure.
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.

top

For specifics or the finer details you can always refer to dbx_mysql.c and dbx_odbc.c to see everything in action.
More Zend API documentation can be found at http://www.zend.com/apidoc.
This document can be found at http://www.guidance.nl/php/dbx.

top