/* 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 "apu.h"

#if APU_HAVE_SQLITE3

#include <ctype.h>
#include <stdlib.h>

#include <sqlite3.h>

#include "apr_strings.h"
#include "apr_time.h"
#include "apr_buckets.h"

#include "apr_dbd_internal.h"

#define MAX_RETRY_COUNT 15
#define MAX_RETRY_SLEEP 100000

struct apr_dbd_transaction_t {
    int mode;
    int errnum;
    apr_dbd_t *handle;
};

struct apr_dbd_t {
    sqlite3 *conn;
    apr_dbd_transaction_t *trans;
    apr_pool_t *pool;
    apr_dbd_prepared_t *prep;
};

typedef struct {
    char *name;
    char *value;
    int size;
    int type;
} apr_dbd_column_t;

struct apr_dbd_row_t {
    apr_dbd_results_t *res;
    apr_dbd_column_t **columns;
    apr_dbd_row_t *next_row;
    int columnCount;
    int rownum;
};

struct apr_dbd_results_t {
    int random;
    sqlite3 *handle;
    sqlite3_stmt *stmt;
    apr_dbd_row_t *next_row;
    size_t sz;
    int tuples;
    char **col_names;
    apr_pool_t *pool;
};

struct apr_dbd_prepared_t {
    sqlite3_stmt *stmt;
    apr_dbd_prepared_t *next;
    int nargs;
    int nvals;
    apr_dbd_type_e *types;
};

#define dbd_sqlite3_is_success(x) (((x) == SQLITE_DONE) || ((x) == SQLITE_OK))

static int dbd_sqlite3_select_internal(apr_pool_t *pool,
                                       apr_dbd_t *sql,
                                       apr_dbd_results_t **results,
                                       sqlite3_stmt *stmt, int seek)
{
    int ret, retry_count = 0, column_count;
    size_t i, num_tuples = 0;
    int increment = 0;
    apr_dbd_row_t *row = NULL;
    apr_dbd_row_t *lastrow = NULL;
    apr_dbd_column_t *column;
    char *hold = NULL;

    column_count = sqlite3_column_count(stmt);
    if (!*results) {
        *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
    }
    (*results)->stmt = stmt;
    (*results)->sz = column_count;
    (*results)->random = seek;
    (*results)->next_row = 0;
    (*results)->tuples = 0;
    (*results)->col_names = apr_pcalloc(pool, column_count * sizeof(char *));
    (*results)->pool = pool;
    do {
        ret = sqlite3_step(stmt);
        if (ret == SQLITE_BUSY) {
            if (retry_count++ > MAX_RETRY_COUNT) {
                ret = SQLITE_ERROR;
            } else {
                apr_dbd_mutex_unlock();
                apr_sleep(MAX_RETRY_SLEEP);
                apr_dbd_mutex_lock();
            }
        } else if (ret == SQLITE_ROW) {
            int length;
            apr_dbd_column_t *col;
            row = apr_palloc(pool, sizeof(apr_dbd_row_t));
            row->res = *results;
            increment = sizeof(apr_dbd_column_t *);
            length = increment * (*results)->sz;
            row->columns = apr_palloc(pool, length);
            row->columnCount = column_count;
            for (i = 0; i < (*results)->sz; i++) {
                column = apr_palloc(pool, sizeof(apr_dbd_column_t));
                row->columns[i] = column;
                /* copy column name once only */
                if ((*results)->col_names[i] == NULL) {
                    (*results)->col_names[i] =
                        apr_pstrdup(pool, sqlite3_column_name(stmt, i));
                }
                column->name = (*results)->col_names[i];
                column->size = sqlite3_column_bytes(stmt, i);
                column->type = sqlite3_column_type(stmt, i);
                column->value = NULL;
                switch (column->type) {
                case SQLITE_FLOAT:
                case SQLITE_INTEGER:
                case SQLITE_TEXT:
                    hold = (char *) sqlite3_column_text(stmt, i);
                    if (hold) {
                        column->value = apr_pstrmemdup(pool, hold,
                                                       column->size);
                    }
                    break;
                case SQLITE_BLOB:
                    hold = (char *) sqlite3_column_blob(stmt, i);
                    if (hold) {
                        column->value = apr_pstrmemdup(pool, hold,
                                                       column->size);
                    }
                    break;
                case SQLITE_NULL:
                    break;
                }
                col = row->columns[i];
            }
            row->rownum = num_tuples++;
            row->next_row = 0;
            (*results)->tuples = num_tuples;
            if ((*results)->next_row == 0) {
                (*results)->next_row = row;
            }
            if (lastrow != 0) {
                lastrow->next_row = row;
            }
            lastrow = row;
        }
    } while (ret == SQLITE_ROW || ret == SQLITE_BUSY);

    if (dbd_sqlite3_is_success(ret)) {
        ret = 0;
    }
    return ret;
}

static int dbd_sqlite3_select(apr_pool_t *pool, apr_dbd_t *sql,
                              apr_dbd_results_t **results, const char *query,
                              int seek)
{
    sqlite3_stmt *stmt = NULL;
    const char *tail = NULL;
    int ret;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    apr_dbd_mutex_lock();

    ret = sqlite3_prepare(sql->conn, query, strlen(query), &stmt, &tail);
    if (dbd_sqlite3_is_success(ret)) {
        ret = dbd_sqlite3_select_internal(pool, sql, results, stmt, seek);
    }
    sqlite3_finalize(stmt);

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static const char *dbd_sqlite3_get_name(const apr_dbd_results_t *res, int n)
{
    if ((n < 0) || ((size_t)n >= res->sz)) {
        return NULL;
    }

    return res->col_names[n];
}

static int dbd_sqlite3_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
                               apr_dbd_row_t **rowp, int rownum)
{
    int i = 0;

    if (rownum == -1) {
        *rowp = res->next_row;
        if (*rowp == 0)
            return -1;
        res->next_row = (*rowp)->next_row;
        return 0;
    }
    if (rownum > res->tuples) {
        return -1;
    }
    rownum--;
    *rowp = res->next_row;
    for (; *rowp != 0; i++, *rowp = (*rowp)->next_row) {
        if (i == rownum) {
            return 0;
        }
    }

    return -1;

}

static const char *dbd_sqlite3_get_entry(const apr_dbd_row_t *row, int n)
{
    apr_dbd_column_t *column;
    const char *value;
    if ((n < 0) || (n >= row->columnCount)) {
        return NULL;
    }
    column = row->columns[n];
    value = column->value;
    return value;
}

static apr_status_t dbd_sqlite3_datum_get(const apr_dbd_row_t *row, int n,
                                          apr_dbd_type_e type, void *data)
{
    if ((n < 0) || ((size_t)n >= row->res->sz)) {
      return APR_EGENERAL;
    }

    if (row->columns[n]->type == SQLITE_NULL) {
        return APR_ENOENT;
    }

    switch (type) {
    case APR_DBD_TYPE_TINY:
        *(char*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_UTINY:
        *(unsigned char*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_SHORT:
        *(short*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_USHORT:
        *(unsigned short*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_INT:
        *(int*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_UINT:
        *(unsigned int*)data = atoi(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_LONG:
        *(long*)data = atol(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_ULONG:
        *(unsigned long*)data = atol(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_LONGLONG:
        *(apr_int64_t*)data = apr_atoi64(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_ULONGLONG:
        *(apr_uint64_t*)data = apr_atoi64(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_FLOAT:
        *(float*)data = (float)atof(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_DOUBLE:
        *(double*)data = atof(row->columns[n]->value);
        break;
    case APR_DBD_TYPE_STRING:
    case APR_DBD_TYPE_TEXT:
    case APR_DBD_TYPE_TIME:
    case APR_DBD_TYPE_DATE:
    case APR_DBD_TYPE_DATETIME:
    case APR_DBD_TYPE_TIMESTAMP:
    case APR_DBD_TYPE_ZTIMESTAMP:
        *(char**)data = row->columns[n]->value;
        break;
    case APR_DBD_TYPE_BLOB:
    case APR_DBD_TYPE_CLOB:
        {
        apr_bucket *e;
        apr_bucket_brigade *b = (apr_bucket_brigade*)data;

        e = apr_bucket_pool_create(row->columns[n]->value,
                                   row->columns[n]->size,
                                   row->res->pool, b->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(b, e);
        }
        break;
    case APR_DBD_TYPE_NULL:
        *(void**)data = NULL;
        break;
    default:
        return APR_EGENERAL;
    }

    return APR_SUCCESS;
}

static const char *dbd_sqlite3_error(apr_dbd_t *sql, int n)
{
    return sqlite3_errmsg(sql->conn);
}

static int dbd_sqlite3_query_internal(apr_dbd_t *sql, sqlite3_stmt *stmt,
                                      int *nrows)
{
    int ret = -1, retry_count = 0;

    while(retry_count++ <= MAX_RETRY_COUNT) {
        ret = sqlite3_step(stmt);
        if (ret != SQLITE_BUSY)
            break;

        apr_dbd_mutex_unlock();
        apr_sleep(MAX_RETRY_SLEEP);
        apr_dbd_mutex_lock();
    }

    *nrows = sqlite3_changes(sql->conn);

    if (dbd_sqlite3_is_success(ret)) {
        ret = 0;
    }
    return ret;
}

static int dbd_sqlite3_query(apr_dbd_t *sql, int *nrows, const char *query)
{
    sqlite3_stmt *stmt = NULL;
    const char *tail = NULL;
    int ret = -1, length = 0;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    length = strlen(query);
    apr_dbd_mutex_lock();

    do {
        ret = sqlite3_prepare(sql->conn, query, length, &stmt, &tail);
        if (ret != SQLITE_OK) {
            sqlite3_finalize(stmt);
            break;
        }

        ret = dbd_sqlite3_query_internal(sql, stmt, nrows);

        sqlite3_finalize(stmt);
        length -= (tail - query);
        query = tail;
    } while (length > 0);

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static apr_status_t free_mem(void *data)
{
    sqlite3_free(data);
    return APR_SUCCESS;
}

static const char *dbd_sqlite3_escape(apr_pool_t *pool, const char *arg,
                                      apr_dbd_t *sql)
{
    char *ret = sqlite3_mprintf("%q", arg);
    apr_pool_cleanup_register(pool, ret, free_mem,
                              apr_pool_cleanup_null);
    return ret;
}

static int dbd_sqlite3_prepare(apr_pool_t *pool, apr_dbd_t *sql,
                               const char *query, const char *label,
                               int nargs, int nvals, apr_dbd_type_e *types,
                               apr_dbd_prepared_t **statement)
{
    sqlite3_stmt *stmt;
    const char *tail = NULL;
    int ret;

    apr_dbd_mutex_lock();

    ret = sqlite3_prepare(sql->conn, query, strlen(query), &stmt, &tail);
    if (ret == SQLITE_OK) {
        apr_dbd_prepared_t *prep; 

        prep = apr_pcalloc(sql->pool, sizeof(*prep));
        prep->stmt = stmt;
        prep->next = sql->prep;
        prep->nargs = nargs;
        prep->nvals = nvals;
        prep->types = types;

        /* link new statement to the handle */
        sql->prep = prep;

        *statement = prep;
    } else {
        sqlite3_finalize(stmt);
    }
   
    apr_dbd_mutex_unlock();

    return ret;
}

static void dbd_sqlite3_bind(apr_dbd_prepared_t *statement, const char **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int i, j;

    for (i = 0, j = 0; i < statement->nargs; i++, j++) {
        if (values[j] == NULL) {
            sqlite3_bind_null(stmt, i + 1);
        }
        else {
            switch (statement->types[i]) {
            case APR_DBD_TYPE_BLOB:
            case APR_DBD_TYPE_CLOB:
                {
                char *data = (char *)values[j];
                int size = atoi((char*)values[++j]);

                /* skip table and column */
                j += 2;

                sqlite3_bind_blob(stmt, i + 1, data, size, SQLITE_STATIC);
                }
                break;
            default:
                sqlite3_bind_text(stmt, i + 1, values[j],
                                  strlen(values[j]), SQLITE_STATIC);
                break;
            }
        }
    }

    return;
}

static int dbd_sqlite3_pquery(apr_pool_t *pool, apr_dbd_t *sql,
                              int *nrows, apr_dbd_prepared_t *statement,
                              const char **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int ret = -1;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    apr_dbd_mutex_lock();

    ret = sqlite3_reset(stmt);
    if (ret == SQLITE_OK) {
        dbd_sqlite3_bind(statement, values);

        ret = dbd_sqlite3_query_internal(sql, stmt, nrows);

        sqlite3_reset(stmt);
    }

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static int dbd_sqlite3_pvquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
                               apr_dbd_prepared_t *statement, va_list args)
{
    const char **values;
    int i;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    values = apr_palloc(pool, sizeof(*values) * statement->nvals);

    for (i = 0; i < statement->nvals; i++) {
        values[i] = va_arg(args, const char*);
    }

    return dbd_sqlite3_pquery(pool, sql, nrows, statement, values);
}

static int dbd_sqlite3_pselect(apr_pool_t *pool, apr_dbd_t *sql,
                               apr_dbd_results_t **results,
                               apr_dbd_prepared_t *statement, int seek,
                               const char **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int ret;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    apr_dbd_mutex_lock();

    ret = sqlite3_reset(stmt);
    if (ret == SQLITE_OK) {
        dbd_sqlite3_bind(statement, values);

        ret = dbd_sqlite3_select_internal(pool, sql, results, stmt, seek);

        sqlite3_reset(stmt);
    }

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static int dbd_sqlite3_pvselect(apr_pool_t *pool, apr_dbd_t *sql,
                                apr_dbd_results_t **results,
                                apr_dbd_prepared_t *statement, int seek,
                                va_list args)
{
    const char **values;
    int i;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    values = apr_palloc(pool, sizeof(*values) * statement->nvals);

    for (i = 0; i < statement->nvals; i++) {
        values[i] = va_arg(args, const char*);
    }

    return dbd_sqlite3_pselect(pool, sql, results, statement, seek, values);
}

static void dbd_sqlite3_bbind(apr_dbd_prepared_t * statement,
                              const void **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int i, j;
    apr_dbd_type_e type;

    for (i = 0, j = 0; i < statement->nargs; i++, j++) {
        type = (values[j] == NULL ? APR_DBD_TYPE_NULL : statement->types[i]);

        switch (type) {
        case APR_DBD_TYPE_TINY:
            sqlite3_bind_int(stmt, i + 1, *(char*)values[j]);
            break;
        case APR_DBD_TYPE_UTINY:
            sqlite3_bind_int(stmt, i + 1, *(unsigned char*)values[j]);
            break;
        case APR_DBD_TYPE_SHORT:
            sqlite3_bind_int(stmt, i + 1, *(short*)values[j]);
            break;
        case APR_DBD_TYPE_USHORT:
            sqlite3_bind_int(stmt, i + 1, *(unsigned short*)values[j]);
            break;
        case APR_DBD_TYPE_INT:
            sqlite3_bind_int(stmt, i + 1, *(int*)values[j]);
            break;
        case APR_DBD_TYPE_UINT:
            sqlite3_bind_int(stmt, i + 1, *(unsigned int*)values[j]);
            break;
        case APR_DBD_TYPE_LONG:
            sqlite3_bind_int64(stmt, i + 1, *(long*)values[j]);
            break;
        case APR_DBD_TYPE_ULONG:
            sqlite3_bind_int64(stmt, i + 1, *(unsigned long*)values[j]);
            break;
        case APR_DBD_TYPE_LONGLONG:
            sqlite3_bind_int64(stmt, i + 1, *(apr_int64_t*)values[j]);
            break;
        case APR_DBD_TYPE_ULONGLONG:
            sqlite3_bind_int64(stmt, i + 1, *(apr_uint64_t*)values[j]);
            break;
        case APR_DBD_TYPE_FLOAT:
            sqlite3_bind_double(stmt, i + 1, *(float*)values[j]);
            break;
        case APR_DBD_TYPE_DOUBLE:
            sqlite3_bind_double(stmt, i + 1, *(double*)values[j]);
            break;
        case APR_DBD_TYPE_STRING:
        case APR_DBD_TYPE_TEXT:
        case APR_DBD_TYPE_TIME:
        case APR_DBD_TYPE_DATE:
        case APR_DBD_TYPE_DATETIME:
        case APR_DBD_TYPE_TIMESTAMP:
        case APR_DBD_TYPE_ZTIMESTAMP:
            sqlite3_bind_text(stmt, i + 1, values[j], strlen(values[j]),
                              SQLITE_STATIC);
            break;
        case APR_DBD_TYPE_BLOB:
        case APR_DBD_TYPE_CLOB:
            {
            char *data = (char*)values[j];
            apr_size_t size = *(apr_size_t*)values[++j];

            sqlite3_bind_blob(stmt, i + 1, data, size, SQLITE_STATIC);

            /* skip table and column */
            j += 2;
            }
            break;
        case APR_DBD_TYPE_NULL:
        default:
            sqlite3_bind_null(stmt, i + 1);
            break;
        }
    }

    return;
}

static int dbd_sqlite3_pbquery(apr_pool_t * pool, apr_dbd_t * sql,
                               int *nrows, apr_dbd_prepared_t * statement,
                               const void **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int ret = -1;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    apr_dbd_mutex_lock();

    ret = sqlite3_reset(stmt);
    if (ret == SQLITE_OK) {
        dbd_sqlite3_bbind(statement, values);

        ret = dbd_sqlite3_query_internal(sql, stmt, nrows);

        sqlite3_reset(stmt);
    }

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static int dbd_sqlite3_pvbquery(apr_pool_t * pool, apr_dbd_t * sql,
                                int *nrows, apr_dbd_prepared_t * statement,
                                va_list args)
{
    const void **values;
    int i;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    values = apr_palloc(pool, sizeof(*values) * statement->nvals);

    for (i = 0; i < statement->nvals; i++) {
        values[i] = va_arg(args, const void*);
    }

    return dbd_sqlite3_pbquery(pool, sql, nrows, statement, values);
}

static int dbd_sqlite3_pbselect(apr_pool_t * pool, apr_dbd_t * sql,
                                apr_dbd_results_t ** results,
                                apr_dbd_prepared_t * statement,
                                int seek, const void **values)
{
    sqlite3_stmt *stmt = statement->stmt;
    int ret;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    apr_dbd_mutex_lock();

    ret = sqlite3_reset(stmt);
    if (ret == SQLITE_OK) {
        dbd_sqlite3_bbind(statement, values);

        ret = dbd_sqlite3_select_internal(pool, sql, results, stmt, seek);

        sqlite3_reset(stmt);
    }

    apr_dbd_mutex_unlock();

    if (TXN_NOTICE_ERRORS(sql->trans)) {
        sql->trans->errnum = ret;
    }
    return ret;
}

static int dbd_sqlite3_pvbselect(apr_pool_t * pool, apr_dbd_t * sql,
                                 apr_dbd_results_t ** results,
                                 apr_dbd_prepared_t * statement, int seek,
                                 va_list args)
{
    const void **values;
    int i;

    if (sql->trans && sql->trans->errnum) {
        return sql->trans->errnum;
    }

    values = apr_palloc(pool, sizeof(*values) * statement->nvals);

    for (i = 0; i < statement->nvals; i++) {
        values[i] = va_arg(args, const void*);
    }

    return dbd_sqlite3_pbselect(pool, sql, results, statement, seek, values);
}

static int dbd_sqlite3_start_transaction(apr_pool_t *pool,
                                         apr_dbd_t *handle,
                                         apr_dbd_transaction_t **trans)
{
    int ret = 0;
    int nrows = 0;

    ret = dbd_sqlite3_query(handle, &nrows, "BEGIN IMMEDIATE");
    if (!*trans) {
        *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t));
        (*trans)->handle = handle;
        handle->trans = *trans;
    }

    return ret;
}

static int dbd_sqlite3_end_transaction(apr_dbd_transaction_t *trans)
{
    int ret = -1; /* ending transaction that was never started is an error */
    int nrows = 0;

    if (trans) {
        /* rollback on error or explicit rollback request */
        if (trans->errnum || TXN_DO_ROLLBACK(trans)) {
            trans->errnum = 0;
            ret = dbd_sqlite3_query(trans->handle, &nrows, "ROLLBACK");
        } else {
            ret = dbd_sqlite3_query(trans->handle, &nrows, "COMMIT");
        }
        trans->handle->trans = NULL;
    }

    return ret;
}

static int dbd_sqlite3_transaction_mode_get(apr_dbd_transaction_t *trans)
{
    if (!trans)
        return APR_DBD_TRANSACTION_COMMIT;

    return trans->mode;
}

static int dbd_sqlite3_transaction_mode_set(apr_dbd_transaction_t *trans,
                                            int mode)
{
    if (!trans)
        return APR_DBD_TRANSACTION_COMMIT;

    return trans->mode = (mode & TXN_MODE_BITS);
}

static apr_dbd_t *dbd_sqlite3_open(apr_pool_t *pool, const char *params,
                                   const char **error)
{
    apr_dbd_t *sql = NULL;
    sqlite3 *conn = NULL;
    int sqlres;
    if (!params)
        return NULL;
    sqlres = sqlite3_open(params, &conn);
    if (sqlres != SQLITE_OK) {
        if (error) {
            *error = apr_pstrdup(pool, sqlite3_errmsg(conn));
        }
        sqlite3_close(conn);
        return NULL;
    }
    /* should we register rand or power functions to the sqlite VM? */
    sql = apr_pcalloc(pool, sizeof(*sql));
    sql->conn = conn;
    sql->pool = pool;
    sql->trans = NULL;

    return sql;
}

static apr_status_t dbd_sqlite3_close(apr_dbd_t *handle)
{
    apr_dbd_prepared_t *prep = handle->prep;

    /* finalize all prepared statements, or we'll get SQLITE_BUSY on close */
    while (prep) {
        sqlite3_finalize(prep->stmt);
        prep = prep->next;
    }

    sqlite3_close(handle->conn);
    return APR_SUCCESS;
}

static apr_status_t dbd_sqlite3_check_conn(apr_pool_t *pool,
                                           apr_dbd_t *handle)
{
    return (handle->conn != NULL) ? APR_SUCCESS : APR_EGENERAL;
}

static int dbd_sqlite3_select_db(apr_pool_t *pool, apr_dbd_t *handle,
                                 const char *name)
{
    return APR_ENOTIMPL;
}

static void *dbd_sqlite3_native(apr_dbd_t *handle)
{
    return handle->conn;
}

static int dbd_sqlite3_num_cols(apr_dbd_results_t *res)
{
    return res->sz;
}

static int dbd_sqlite3_num_tuples(apr_dbd_results_t *res)
{
    return res->tuples;
}

APU_MODULE_DECLARE_DATA const apr_dbd_driver_t apr_dbd_sqlite3_driver = {
    "sqlite3",
    NULL,
    dbd_sqlite3_native,
    dbd_sqlite3_open,
    dbd_sqlite3_check_conn,
    dbd_sqlite3_close,
    dbd_sqlite3_select_db,
    dbd_sqlite3_start_transaction,
    dbd_sqlite3_end_transaction,
    dbd_sqlite3_query,
    dbd_sqlite3_select,
    dbd_sqlite3_num_cols,
    dbd_sqlite3_num_tuples,
    dbd_sqlite3_get_row,
    dbd_sqlite3_get_entry,
    dbd_sqlite3_error,
    dbd_sqlite3_escape,
    dbd_sqlite3_prepare,
    dbd_sqlite3_pvquery,
    dbd_sqlite3_pvselect,
    dbd_sqlite3_pquery,
    dbd_sqlite3_pselect,
    dbd_sqlite3_get_name,
    dbd_sqlite3_transaction_mode_get,
    dbd_sqlite3_transaction_mode_set,
    "?",
    dbd_sqlite3_pvbquery,
    dbd_sqlite3_pvbselect,
    dbd_sqlite3_pbquery,
    dbd_sqlite3_pbselect,
    dbd_sqlite3_datum_get
};
#endif
