/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mod_lua.h"
#include "lua_dbd.h"

APLOG_USE_MODULE(lua);
static APR_OPTIONAL_FN_TYPE(ap_dbd_close) *lua_ap_dbd_close = NULL;
static APR_OPTIONAL_FN_TYPE(ap_dbd_open) *lua_ap_dbd_open = NULL;




static request_rec *ap_lua_check_request_rec(lua_State *L, int index)
{
    request_rec *r;
    luaL_checkudata(L, index, "Apache2.Request");
    r = lua_unboxpointer(L, index);
    return r;
}

static lua_db_handle *lua_get_db_handle(lua_State *L)
{
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_rawgeti(L, 1, 0);
    luaL_checktype(L, -1, LUA_TUSERDATA);
    return (lua_db_handle *) lua_topointer(L, -1);
}

static lua_db_result_set *lua_get_result_set(lua_State *L)
{
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_rawgeti(L, 1, 0);
    luaL_checktype(L, -1, LUA_TUSERDATA);
    return (lua_db_result_set *) lua_topointer(L, -1);
}


/*
   =============================================================================
    db:close(): Closes an open database connection.
   =============================================================================
 */
int lua_db_close(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db;
    apr_status_t     rc = 0;
    /*~~~~~~~~~~~~~~~~~~~~*/
    
    db = lua_get_db_handle(L);
    if (db && db->alive) {
        if (db->type == LUA_DBTYPE_APR_DBD) {
            rc = apr_dbd_close(db->driver, db->handle);
            if (db->pool) apr_pool_destroy(db->pool);
        }
        else {
            lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close);
            if (lua_ap_dbd_close != NULL)
                if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle);
        }

        db->driver = NULL;
        db->handle = NULL;
        db->alive = 0;
        db->pool = NULL;
    }

    lua_settop(L, 0);
    lua_pushnumber(L, rc);
    return 1;
} 

/*
   =============================================================================
     db:__gc(): Garbage collecting function.
   =============================================================================
 */
int lua_db_gc(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~*/
    lua_db_handle    *db;
    /*~~~~~~~~~~~~~~~~~~~~*/

    db = lua_touserdata(L, 1);
    if (db && db->alive) {
        if (db->type == LUA_DBTYPE_APR_DBD) {
            apr_dbd_close(db->driver, db->handle);
            if (db->pool) apr_pool_destroy(db->pool);
        }
        else {
            lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close);
            if (lua_ap_dbd_close != NULL)
                if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle);
        }
        db->driver = NULL;
        db->handle = NULL;
        db->alive = 0;
        db->pool = NULL;
    }
    lua_settop(L, 0);
    return 0;
}

/*
   =============================================================================
    db:active(): Returns true if the connection to the db is still active.
   =============================================================================
 */
int lua_db_active(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db = 0;
    apr_status_t     rc = 0;
    /*~~~~~~~~~~~~~~~~~~~~*/

    db = lua_get_db_handle(L);
    if (db && db->alive) {
        rc = apr_dbd_check_conn(db->driver, db->pool, db->handle);
        if (rc == APR_SUCCESS) {
            lua_pushboolean(L, 1);
            return 1;
        }
    }

    lua_pushboolean(L, 0);
    return 1;
}

/*
   =============================================================================
    db:query(statement): Executes the given database query and returns the 
    number of rows affected. If an error is encountered, returns nil as the 
    first parameter and the error message as the second.
   =============================================================================
 */
int lua_db_query(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db = 0;
    apr_status_t     rc = 0;
    int              x = 0;
    const char      *statement;
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    luaL_checktype(L, 3, LUA_TSTRING);
    statement = lua_tostring(L, 3);
    db = lua_get_db_handle(L);
    if (db && db->alive)
        rc = apr_dbd_query(db->driver, db->handle, &x, statement);
    else {
        rc = 0;
        x = -1;
    }

    if (rc == APR_SUCCESS)
        lua_pushnumber(L, x);
    else {

        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
        const char  *err = apr_dbd_error(db->driver, db->handle, rc);
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

        lua_pushnil(L);
        if (err) {
            lua_pushstring(L, err);
            return 2;
        }
    }

    return 1;
}

/*
   =============================================================================
    db:escape(string): Escapes a string for safe use in the given database type.
   =============================================================================
 */
int lua_db_escape(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle    *db = 0;
    const char       *statement;
    const char       *escaped = 0;
    request_rec      *r;
    /*~~~~~~~~~~~~~~~~~~~~~*/

    r = ap_lua_check_request_rec(L, 2);
    if (r) {
        luaL_checktype(L, 3, LUA_TSTRING);
        statement = lua_tostring(L, 3);
        db = lua_get_db_handle(L);
        if (db && db->alive) {
            apr_dbd_init(r->pool);
            escaped = apr_dbd_escape(db->driver, r->pool, statement,
                                     db->handle);
            if (escaped) {
                lua_pushstring(L, escaped);
                return 1;
            }
        }
        else {
            lua_pushnil(L);
        }
        return (1);
    }

    return 0;
}

/*
   =============================================================================
     resultset(N): Fetches one or more rows from a result set.
   =============================================================================
 */
int lua_db_get_row(lua_State *L) 
{
    int row_no,x;
    const char      *entry;
    apr_dbd_row_t   *row = 0;
    lua_db_result_set *res = lua_get_result_set(L);
    
    row_no = luaL_optinteger(L, 2, 0);
    lua_settop(L,0);
    
    /* Fetch all rows at once? */
    if (row_no == 0) {
        row_no = 1;
        lua_newtable(L);
        while (apr_dbd_get_row(res->driver, res->pool, res->results,
                            &row, -1) != -1)
         {
            lua_pushinteger(L, row_no);
            lua_newtable(L);
            for (x = 0; x < res->cols; x++) {
                entry = apr_dbd_get_entry(res->driver, row, x);
                if (entry) {
                    lua_pushinteger(L, x + 1);
                    lua_pushstring(L, entry);
                    lua_rawset(L, -3);
                }
            }
            lua_rawset(L, -3);
            row_no++;
        }
        return 1;
    }
    
    /* Just fetch a single row */
    if (apr_dbd_get_row(res->driver, res->pool, res->results,
                            &row, row_no) != -1)
         {
        
        lua_newtable(L);
        for (x = 0; x < res->cols; x++) {
            entry = apr_dbd_get_entry(res->driver, row, x);
            if (entry) {
                lua_pushinteger(L, x + 1);
                lua_pushstring(L, entry);
                lua_rawset(L, -3);
            }
        }
        return 1;
    }
    return 0;
}


/*
   =============================================================================
    db:select(statement): Queries the database for the given statement and 
    returns the rows/columns found as a table. If an error is encountered, 
    returns nil as the first parameter and the error message as the second.
   =============================================================================
 */
int lua_db_select(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db = 0;
    apr_status_t     rc = 0;
    const char      *statement;
    request_rec     *r;
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    r = ap_lua_check_request_rec(L, 2);
    if (r) {
        luaL_checktype(L, 3, LUA_TSTRING);
        statement = lua_tostring(L, 3);
        db = lua_get_db_handle(L);
        if (db && db->alive) {

            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
            int cols;
            apr_dbd_results_t   *results = 0;
            lua_db_result_set* resultset = NULL;
            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

            rc = apr_dbd_select(db->driver, db->pool, db->handle,
                                &results, statement, 0);
            if (rc == APR_SUCCESS) {
                
                cols = apr_dbd_num_cols(db->driver, results);
                
                if (cols > 0) {
                    lua_newtable(L);
                    resultset = lua_newuserdata(L, sizeof(lua_db_result_set));
                    resultset->cols = cols;
                    resultset->driver = db->driver;
                    resultset->pool = db->pool;
                    resultset->rows = apr_dbd_num_tuples(db->driver, results);
                    resultset->results = results;
                    luaL_newmetatable(L, "lua_apr.dbselect");
                    lua_pushliteral(L, "__call");
                    lua_pushcfunction(L, lua_db_get_row);
                    lua_rawset(L, -3);
                    lua_setmetatable(L, -3);
                    lua_rawseti(L, -2, 0);
                    return 1;
                }
                return 0;
            }
            else {

                /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
                const char  *err = apr_dbd_error(db->driver, db->handle, rc);
                /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

                lua_pushnil(L);
                if (err) {
                    lua_pushstring(L, err);
                    return 2;
                }
            }
        }

        lua_pushboolean(L, 0);
        return 1;
    }

    return 0;
}



/*
   =============================================================================
    statement:select(var1, var2, var3...): Injects variables into a prepared 
    statement and returns the number of rows matching the query.
   =============================================================================
 */
int lua_db_prepared_select(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_prepared_statement  *st = 0;
    apr_status_t     rc = 0;
    const char       **vars;
    int              x, have;
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    
    /* Fetch the prepared statement and the vars passed */
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_rawgeti(L, 1, 0);
    luaL_checktype(L, -1, LUA_TUSERDATA);
    st = (lua_db_prepared_statement*) lua_topointer(L, -1);
    
    /* Check if we got enough variables passed on to us.
     * This, of course, only works for prepared statements made through lua. */
    have = lua_gettop(L) - 2;
    if (st->variables != -1 && have < st->variables ) {
        lua_pushboolean(L, 0);
        lua_pushfstring(L, 
                "Error in executing prepared statement: Expected %d arguments, got %d.", 
                st->variables, have);
        return 2;
    }
    vars = apr_pcalloc(st->db->pool, have*sizeof(char *));
    for (x = 0; x < have; x++) {
        vars[x] = lua_tostring(L, x + 2);
    }

    /* Fire off the query */
    if (st->db && st->db->alive) {

        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
        int cols;
        apr_dbd_results_t   *results = 0;
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

        rc = apr_dbd_pselect(st->db->driver, st->db->pool, st->db->handle,
                                &results, st->statement, 0, have, vars);
        if (rc == APR_SUCCESS) {

            /*~~~~~~~~~~~~~~~~~~~~~*/
            lua_db_result_set *resultset;
            /*~~~~~~~~~~~~~~~~~~~~~*/

            cols = apr_dbd_num_cols(st->db->driver, results);
            lua_newtable(L);
            resultset = lua_newuserdata(L, sizeof(lua_db_result_set));
            resultset->cols = cols;
            resultset->driver = st->db->driver;
            resultset->pool = st->db->pool;
            resultset->rows = apr_dbd_num_tuples(st->db->driver, results);
            resultset->results = results;
            luaL_newmetatable(L, "lua_apr.dbselect");
            lua_pushliteral(L, "__call");
            lua_pushcfunction(L, lua_db_get_row);
            lua_rawset(L, -3);
            lua_setmetatable(L, -3);
            lua_rawseti(L, -2, 0);
            return 1;
            
        }
        else {

            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
            const char  *err = apr_dbd_error(st->db->driver, st->db->handle, rc);
            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

            lua_pushnil(L);
            if (err) {
                lua_pushstring(L, err);
                return 2;
            }
            return 1;
        }
    }

    lua_pushboolean(L, 0);
    lua_pushliteral(L, 
            "Database connection seems to be closed, please reacquire it.");
    return (2);
}


/*
   =============================================================================
    statement:query(var1, var2, var3...): Injects variables into a prepared 
    statement and returns the number of rows affected.
   =============================================================================
 */
int lua_db_prepared_query(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_prepared_statement  *st = 0;
    apr_status_t     rc = 0;
    const char       **vars;
    int              x, have;
    /*~~~~~~~~~~~~~~~~~~~~~~~*/
    
    /* Fetch the prepared statement and the vars passed */
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_rawgeti(L, 1, 0);
    luaL_checktype(L, -1, LUA_TUSERDATA);
    st = (lua_db_prepared_statement*) lua_topointer(L, -1);
    
    /* Check if we got enough variables passed on to us.
     * This, of course, only works for prepared statements made through lua. */
    have = lua_gettop(L) - 2;
    if (st->variables != -1 && have < st->variables ) {
        lua_pushboolean(L, 0);
        lua_pushfstring(L, 
                "Error in executing prepared statement: Expected %d arguments, got %d.", 
                st->variables, have);
        return 2;
    }
    vars = apr_pcalloc(st->db->pool, have*sizeof(char *));
    for (x = 0; x < have; x++) {
        vars[x] = lua_tostring(L, x + 2);
    }

    /* Fire off the query */
    if (st->db && st->db->alive) {

        /*~~~~~~~~~~~~~~*/
        int affected = 0;
        /*~~~~~~~~~~~~~~*/

        rc = apr_dbd_pquery(st->db->driver, st->db->pool, st->db->handle,
                                &affected, st->statement, have, vars);
        if (rc == APR_SUCCESS) {
            lua_pushinteger(L, affected);
            return 1;
        }
        else {

            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
            const char  *err = apr_dbd_error(st->db->driver, st->db->handle, rc);
            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

            lua_pushnil(L);
            if (err) {
                lua_pushstring(L, err);
                return 2;
            }
            return 1;
        }
    }

    lua_pushboolean(L, 0);
    lua_pushliteral(L, 
            "Database connection seems to be closed, please reacquire it.");
    return (2);
}

/*
   =============================================================================
    db:prepare(statement): Prepares a statement for later query/select.
    Returns a table with a :query and :select function, same as the db funcs.
   =============================================================================
 */
int lua_db_prepare(lua_State* L) 
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db = 0;
    apr_status_t     rc = 0;
    const char      *statement, *at;
    request_rec     *r;
    lua_db_prepared_statement* st;
    int need = 0;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    
    r = ap_lua_check_request_rec(L, 2);
    if (r) {
        apr_dbd_prepared_t *pstatement = NULL;
        luaL_checktype(L, 3, LUA_TSTRING);
        statement = lua_tostring(L, 3);
        
        /* Count number of variables in statement */
        at = ap_strchr_c(statement,'%');
        while (at != NULL) {
            if (at[1] == '%') {
                at++;
            }
            else {
                need++;
            }
            at = ap_strchr_c(at+1,'%');
        }
        
        
        db = lua_get_db_handle(L);
        rc = apr_dbd_prepare(db->driver, r->pool, db->handle, statement, 
                    NULL, &pstatement);
        if (rc != APR_SUCCESS) {
            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
            const char  *err = apr_dbd_error(db->driver, db->handle, rc);
            /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

            lua_pushnil(L);
            if (err) {
                lua_pushstring(L, err);
                return 2;
            }
            return 1;
        }
        
        /* Push the prepared statement table */
        lua_newtable(L);
        st = lua_newuserdata(L, sizeof(lua_db_prepared_statement));
        st->statement = pstatement;
        st->variables = need;
        st->db = db;
        
        lua_pushliteral(L, "select");
        lua_pushcfunction(L, lua_db_prepared_select);
        lua_rawset(L, -4);
        lua_pushliteral(L, "query");
        lua_pushcfunction(L, lua_db_prepared_query);
        lua_rawset(L, -4);
        lua_rawseti(L, -2, 0);
        return 1;
    }
    return 0;
}



/*
   =============================================================================
    db:prepared(statement): Fetches a prepared statement made through 
    DBDPrepareSQL.
   =============================================================================
 */
int lua_db_prepared(lua_State* L) 
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    lua_db_handle   *db = 0;
    const char      *tag;
    request_rec     *r;
    lua_db_prepared_statement* st;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    
    r = ap_lua_check_request_rec(L, 2);
    if (r) {
        apr_dbd_prepared_t *pstatement = NULL;
        db = lua_get_db_handle(L);
        luaL_checktype(L, 3, LUA_TSTRING);
        tag = lua_tostring(L, 3);
        
        /* Look for the statement */
        pstatement = apr_hash_get(db->dbdhandle->prepared, tag, 
                APR_HASH_KEY_STRING);
        
        if (pstatement == NULL) {
            lua_pushnil(L);
            lua_pushfstring(L, 
                    "Could not find any prepared statement called %s!", tag);
            return 2;
        }
        
        
        /* Push the prepared statement table */
        lua_newtable(L);
        st = lua_newuserdata(L, sizeof(lua_db_prepared_statement));
        st->statement = pstatement;
        st->variables = -1; /* we don't know :( */
        st->db = db;
        lua_pushliteral(L, "select");
        lua_pushcfunction(L, lua_db_prepared_select);
        lua_rawset(L, -4);
        lua_pushliteral(L, "query");
        lua_pushcfunction(L, lua_db_prepared_query);
        lua_rawset(L, -4);
        lua_rawseti(L, -2, 0);
        return 1;
    }
    return 0;
}



/* lua_push_db_handle: Creates a database table object with database functions 
   and a userdata at index 0, which will call lua_dbgc when garbage collected.
 */
static lua_db_handle* lua_push_db_handle(lua_State *L, request_rec* r, int type,
        apr_pool_t* pool) 
{
    lua_db_handle* db;
    lua_newtable(L);
    db = lua_newuserdata(L, sizeof(lua_db_handle));
    db->alive = 1;
    db->pool = pool;
    db->type = type;
    db->dbdhandle = 0;
    db->server = r->server;
    luaL_newmetatable(L, "lua_apr.dbacquire");
    lua_pushliteral(L, "__gc");
    lua_pushcfunction(L, lua_db_gc);
    lua_rawset(L, -3);
    lua_setmetatable(L, -2);
    lua_rawseti(L, -2, 0);
    
    lua_pushliteral(L, "escape");
    lua_pushcfunction(L, lua_db_escape);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "close");
    lua_pushcfunction(L, lua_db_close);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "select");
    lua_pushcfunction(L, lua_db_select);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "query");
    lua_pushcfunction(L, lua_db_query);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "active");
    lua_pushcfunction(L, lua_db_active);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "prepare");
    lua_pushcfunction(L, lua_db_prepare);
    lua_rawset(L, -3);
    
    lua_pushliteral(L, "prepared");
    lua_pushcfunction(L, lua_db_prepared);
    lua_rawset(L, -3);
    return db;
}

/*
   =============================================================================
    dbacquire(dbType, dbString): Opens a new connection to a database of type 
    _dbType_ and with the connection parameters _dbString_. If successful, 
    returns a table with functions for using the database handle. If an error 
    occurs, returns nil as the first parameter and the error message as the 
    second. See the APR_DBD for a list of database types and connection strings 
    supported.
   =============================================================================
 */
int lua_db_acquire(lua_State *L)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    const char      *type;
    const char      *arguments;
    const char      *error = 0;
    request_rec     *r;
    lua_db_handle   *db = 0;
    apr_status_t     rc = 0;
    ap_dbd_t        *dbdhandle = NULL;
    apr_pool_t      *pool = NULL;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    r = ap_lua_check_request_rec(L, 1);
    if (r) {
        type = luaL_optstring(L, 2, "mod_dbd"); /* Defaults to mod_dbd */
        
        if (!strcmp(type, "mod_dbd")) {

            lua_settop(L, 0);
            lua_ap_dbd_open = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_open);
            if (lua_ap_dbd_open)
                dbdhandle = (ap_dbd_t *) lua_ap_dbd_open(
                        r->server->process->pool, r->server);

            if (dbdhandle) {
                db = lua_push_db_handle(L, r, LUA_DBTYPE_MOD_DBD, dbdhandle->pool);
                db->driver = dbdhandle->driver;
                db->handle = dbdhandle->handle;
                db->dbdhandle = dbdhandle;
                return 1;
            }
            else {
                lua_pushnil(L);
                if ( lua_ap_dbd_open == NULL )
                    lua_pushliteral(L,
                                    "mod_dbd doesn't seem to have been loaded.");
                else
                    lua_pushliteral(
                        L,
                        "Could not acquire connection from mod_dbd. If your database is running, this may indicate a permission problem.");
                return 2;
            }
        }
        else {
            rc = apr_pool_create(&pool, NULL);
            if (rc != APR_SUCCESS) {
                lua_pushnil(L);
                lua_pushliteral(L, "Could not allocate memory for database!");
                return 2;
            }
            apr_pool_tag(pool, "lua_dbd_pool");
            apr_dbd_init(pool);
            dbdhandle = apr_pcalloc(pool, sizeof(ap_dbd_t));
            rc = apr_dbd_get_driver(pool, type, &dbdhandle->driver);
            if (rc == APR_SUCCESS) {
                luaL_checktype(L, 3, LUA_TSTRING);
                arguments = lua_tostring(L, 3);
                lua_settop(L, 0);
                
                if (strlen(arguments)) {
                    rc = apr_dbd_open_ex(dbdhandle->driver, pool, 
                            arguments, &dbdhandle->handle, &error);
                    if (rc == APR_SUCCESS) {
                        db = lua_push_db_handle(L, r, LUA_DBTYPE_APR_DBD, pool);
                        db->driver = dbdhandle->driver;
                        db->handle = dbdhandle->handle;
                        db->dbdhandle = dbdhandle;
                        return 1;
                    }
                    else {
                        lua_pushnil(L);
                        if (error) {
                            lua_pushstring(L, error);
                            return 2;
                        }

                        return 1;
                    }
                }

                lua_pushnil(L);
                lua_pushliteral(L,
                                "No database connection string was specified.");
                apr_pool_destroy(pool);
                return (2);
            }
            else {
                lua_pushnil(L);
                if (APR_STATUS_IS_ENOTIMPL(rc)) {
                    lua_pushfstring(L, 
                         "driver for %s not available", type);
                }
                else if (APR_STATUS_IS_EDSOOPEN(rc)) {
                    lua_pushfstring(L, 
                                "can't find driver for %s", type);
                }
                else if (APR_STATUS_IS_ESYMNOTFOUND(rc)) {
                    lua_pushfstring(L, 
                                "driver for %s is invalid or corrupted",
                                type);
                }
                else {
                    lua_pushliteral(L, 
                                "mod_lua not compatible with APR in get_driver");
                }
                lua_pushinteger(L, rc);
                apr_pool_destroy(pool);
                return 3;
            }
        }

        lua_pushnil(L);
        return 1;
    }

    return 0;
}

