blob: 02b2c02d35cf72f075b539c3626373652843fdc7 [file] [log] [blame]
/* 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