freebsd-skq/contrib/apr-util/dbd/apr_dbd_odbc.c
Peter Wemm 937a200089 Introduce svnlite so that we can check out our source code again.
This is actually a fully functional build except:
* All internal shared libraries are static linked to make sure there
  is no interference with ports (and to reduce build time).
* It does not have the python/perl/etc plugin or API support.
* By default, it installs as "svnlite" rather than "svn".
* If WITH_SVN added in make.conf, you get "svn".
* If WITHOUT_SVNLITE is in make.conf, this is completely disabled.

To be absolutely clear, this is not intended for any use other than
checking out freebsd source and committing, like we once did with cvs.

It should be usable for small scale local repositories that don't
need the python/perl plugin architecture.
2013-06-18 02:53:45 +00:00

1735 lines
60 KiB
C

/* 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_ODBC
#include "apr.h"
#include "apr_strings.h"
#include "apr_buckets.h"
#include "apr_env.h"
#include "apr_file_io.h"
#include "apr_file_info.h"
#include "apr_dbd_internal.h"
#include "apr_thread_proc.h"
#include "apu_version.h"
#include "apu_config.h"
#include <stdlib.h>
/* If library is ODBC-V2, use macros for limited ODBC-V2 support
* No random access in V2.
*/
#ifdef ODBCV2
#define ODBCVER 0x0200
#include "apr_dbd_odbc_v2.h"
#endif
/* standard ODBC include files */
#ifdef HAVE_SQL_H
#include <sql.h>
#include <sqlext.h>
#elif defined(HAVE_ODBC_SQL_H)
#include <odbc/sql.h>
#include <odbc/sqlext.h>
#endif
/* Driver name is "odbc" and the entry point is 'apr_dbd_odbc_driver'
* unless ODBC_DRIVER_NAME is defined and it is linked with another db library which
* is ODBC source-compatible. e.g. DB2, Informix, TimesTen, mysql.
*/
#ifndef ODBC_DRIVER_NAME
#define ODBC_DRIVER_NAME odbc
#endif
#define STRINGIFY(x) #x
#define NAMIFY2(n) apr_dbd_##n##_driver
#define NAMIFY1(n) NAMIFY2(n)
#define ODBC_DRIVER_STRING STRINGIFY(ODBC_DRIVER_NAME)
#define ODBC_DRIVER_ENTRY NAMIFY1(ODBC_DRIVER_NAME)
/* Required APR version for this driver */
#define DRIVER_APU_VERSION_MAJOR APU_MAJOR_VERSION
#define DRIVER_APU_VERSION_MINOR APU_MINOR_VERSION
static SQLHANDLE henv = NULL; /* ODBC ENV handle is process-wide */
/* Use a CHECK_ERROR macro so we can grab the source line numbers
* for error reports
*/
static void check_error(apr_dbd_t *a, const char *step, SQLRETURN rc,
SQLSMALLINT type, SQLHANDLE h, int line);
#define CHECK_ERROR(a,s,r,t,h) check_error(a,s,r,t,h, __LINE__)
#define SOURCE_FILE __FILE__ /* source file for error messages */
#define MAX_ERROR_STRING 1024 /* max length of message in dbc */
#define MAX_COLUMN_NAME 256 /* longest column name recognized */
#define DEFAULT_BUFFER_SIZE 1024 /* value for defaultBufferSize */
#define MAX_PARAMS 20
#define DEFAULTSEPS " \t\r\n,="
#define CSINGLEQUOTE '\''
#define SSINGLEQUOTE "\'"
#define TEXTMODE 1 /* used for text (APR 1.2) mode params */
#define BINARYMODE 0 /* used for binary (APR 1.3+) mode params */
/* Identify datatypes which are LOBs
* - DB2 DRDA driver uses undefined types -98 and -99 for CLOB & BLOB
*/
#define IS_LOB(t) (t == SQL_LONGVARCHAR \
|| t == SQL_LONGVARBINARY || t == SQL_VARBINARY \
|| t == -98 || t == -99)
/* These types are CLOBs
* - DB2 DRDA driver uses undefined type -98 for CLOB
*/
#define IS_CLOB(t) \
(t == SQL_LONGVARCHAR || t == -98)
/* Convert a SQL result to an APR result */
#define APR_FROM_SQL_RESULT(rc) \
(SQL_SUCCEEDED(rc) ? APR_SUCCESS : APR_EGENERAL)
/* DBD opaque structures */
struct apr_dbd_t
{
SQLHANDLE dbc; /* SQL connection handle - NULL after close */
apr_pool_t *pool; /* connection lifetime pool */
char *dbname; /* ODBC datasource */
int lasterrorcode;
int lineNumber;
char lastError[MAX_ERROR_STRING];
int defaultBufferSize; /* used for CLOBs in text mode,
* and when fld size is indeterminate */
int transaction_mode;
int dboptions; /* driver options re SQLGetData */
int default_transaction_mode;
int can_commit; /* controls end_trans behavior */
};
struct apr_dbd_results_t
{
SQLHANDLE stmt; /* parent sql statement handle */
SQLHANDLE dbc; /* parent sql connection handle */
apr_pool_t *pool; /* pool from query or select */
apr_dbd_t *apr_dbd; /* parent DBD connection handle */
int random; /* random access requested */
int ncols; /* number of columns */
int isclosed; /* cursor has been closed */
char **colnames; /* array of column names (NULL until used) */
SQLPOINTER *colptrs; /* pointers to column data */
SQLINTEGER *colsizes; /* sizes for columns (enough for txt or bin) */
SQLINTEGER *coltextsizes; /* max-sizes if converted to text */
SQLSMALLINT *coltypes; /* array of SQL data types for columns */
SQLLEN *colinds; /* array of SQL data indicator/strlens */
int *colstate; /* array of column states
* - avail, bound, present, unavail
*/
int *all_data_fetched; /* flags data as all fetched, for LOBs */
void *data; /* buffer for all data for one row */
};
enum /* results column states */
{
COL_AVAIL, /* data may be retrieved with SQLGetData */
COL_PRESENT, /* data has been retrieved with SQLGetData */
COL_BOUND, /* column is bound to colptr */
COL_RETRIEVED, /* all data from column has been returned */
COL_UNAVAIL /* column is unavailable because ODBC driver
* requires that columns be retrieved
* in ascending order and a higher col
* was accessed
*/
};
struct apr_dbd_row_t {
SQLHANDLE stmt; /* parent ODBC statement handle */
SQLHANDLE dbc; /* parent ODBC connection handle */
apr_pool_t *pool; /* pool from get_row */
apr_dbd_results_t *res;
};
struct apr_dbd_transaction_t {
SQLHANDLE dbc; /* parent ODBC connection handle */
apr_dbd_t *apr_dbd; /* parent DBD connection handle */
};
struct apr_dbd_prepared_t {
SQLHANDLE stmt; /* ODBC statement handle */
SQLHANDLE dbc; /* parent ODBC connection handle */
apr_dbd_t *apr_dbd;
int nargs;
int nvals;
int *types; /* array of DBD data types */
};
static void odbc_lob_bucket_destroy(void *data);
static apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool);
static apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
apr_size_t *len, apr_read_type_e block);
/* the ODBC LOB bucket type */
static const apr_bucket_type_t odbc_bucket_type = {
"ODBC_LOB", 5, APR_BUCKET_DATA,
odbc_lob_bucket_destroy,
odbc_lob_bucket_read,
odbc_lob_bucket_setaside,
apr_bucket_shared_split,
apr_bucket_shared_copy
};
/* ODBC LOB bucket data */
typedef struct {
/** Ref count for shared bucket */
apr_bucket_refcount refcount;
const apr_dbd_row_t *row;
int col;
SQLSMALLINT type;
} odbc_bucket;
/* SQL datatype mappings to DBD datatypes
* These tables must correspond *exactly* to the apr_dbd_type_e enum
* in apr_dbd.h
*/
/* ODBC "C" types to DBD datatypes */
static SQLSMALLINT const sqlCtype[] = {
SQL_C_DEFAULT, /* APR_DBD_TYPE_NONE */
SQL_C_STINYINT, /* APR_DBD_TYPE_TINY, \%hhd */
SQL_C_UTINYINT, /* APR_DBD_TYPE_UTINY, \%hhu */
SQL_C_SSHORT, /* APR_DBD_TYPE_SHORT, \%hd */
SQL_C_USHORT, /* APR_DBD_TYPE_USHORT, \%hu */
SQL_C_SLONG, /* APR_DBD_TYPE_INT, \%d */
SQL_C_ULONG, /* APR_DBD_TYPE_UINT, \%u */
SQL_C_SLONG, /* APR_DBD_TYPE_LONG, \%ld */
SQL_C_ULONG, /* APR_DBD_TYPE_ULONG, \%lu */
SQL_C_SBIGINT, /* APR_DBD_TYPE_LONGLONG, \%lld */
SQL_C_UBIGINT, /* APR_DBD_TYPE_ULONGLONG, \%llu */
SQL_C_FLOAT, /* APR_DBD_TYPE_FLOAT, \%f */
SQL_C_DOUBLE, /* APR_DBD_TYPE_DOUBLE, \%lf */
SQL_C_CHAR, /* APR_DBD_TYPE_STRING, \%s */
SQL_C_CHAR, /* APR_DBD_TYPE_TEXT, \%pDt */
SQL_C_CHAR, /*SQL_C_TYPE_TIME, APR_DBD_TYPE_TIME, \%pDi */
SQL_C_CHAR, /*SQL_C_TYPE_DATE, APR_DBD_TYPE_DATE, \%pDd */
SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_DATETIME, \%pDa */
SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_TIMESTAMP, \%pDs */
SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
SQL_LONGVARBINARY, /* APR_DBD_TYPE_BLOB, \%pDb */
SQL_LONGVARCHAR, /* APR_DBD_TYPE_CLOB, \%pDc */
SQL_TYPE_NULL /* APR_DBD_TYPE_NULL \%pDn */
};
#define NUM_APR_DBD_TYPES (sizeof(sqlCtype) / sizeof(sqlCtype[0]))
/* ODBC Base types to DBD datatypes */
static SQLSMALLINT const sqlBaseType[] = {
SQL_C_DEFAULT, /* APR_DBD_TYPE_NONE */
SQL_TINYINT, /* APR_DBD_TYPE_TINY, \%hhd */
SQL_TINYINT, /* APR_DBD_TYPE_UTINY, \%hhu */
SQL_SMALLINT, /* APR_DBD_TYPE_SHORT, \%hd */
SQL_SMALLINT, /* APR_DBD_TYPE_USHORT, \%hu */
SQL_INTEGER, /* APR_DBD_TYPE_INT, \%d */
SQL_INTEGER, /* APR_DBD_TYPE_UINT, \%u */
SQL_INTEGER, /* APR_DBD_TYPE_LONG, \%ld */
SQL_INTEGER, /* APR_DBD_TYPE_ULONG, \%lu */
SQL_BIGINT, /* APR_DBD_TYPE_LONGLONG, \%lld */
SQL_BIGINT, /* APR_DBD_TYPE_ULONGLONG, \%llu */
SQL_FLOAT, /* APR_DBD_TYPE_FLOAT, \%f */
SQL_DOUBLE, /* APR_DBD_TYPE_DOUBLE, \%lf */
SQL_CHAR, /* APR_DBD_TYPE_STRING, \%s */
SQL_CHAR, /* APR_DBD_TYPE_TEXT, \%pDt */
SQL_CHAR, /*SQL_TIME, APR_DBD_TYPE_TIME, \%pDi */
SQL_CHAR, /*SQL_DATE, APR_DBD_TYPE_DATE, \%pDd */
SQL_CHAR, /*SQL_TIMESTAMP, APR_DBD_TYPE_DATETIME, \%pDa */
SQL_CHAR, /*SQL_TIMESTAMP, APR_DBD_TYPE_TIMESTAMP, \%pDs */
SQL_CHAR, /*SQL_TIMESTAMP, APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
SQL_LONGVARBINARY, /* APR_DBD_TYPE_BLOB, \%pDb */
SQL_LONGVARCHAR, /* APR_DBD_TYPE_CLOB, \%pDc */
SQL_TYPE_NULL /* APR_DBD_TYPE_NULL \%pDn */
};
/* result sizes for DBD datatypes (-1 for null-terminated) */
static int const sqlSizes[] = {
0,
sizeof(char), /**< \%hhd out: char* */
sizeof(unsigned char), /**< \%hhu out: unsigned char* */
sizeof(short), /**< \%hd out: short* */
sizeof(unsigned short), /**< \%hu out: unsigned short* */
sizeof(int), /**< \%d out: int* */
sizeof(unsigned int), /**< \%u out: unsigned int* */
sizeof(long), /**< \%ld out: long* */
sizeof(unsigned long), /**< \%lu out: unsigned long* */
sizeof(apr_int64_t), /**< \%lld out: apr_int64_t* */
sizeof(apr_uint64_t), /**< \%llu out: apr_uint64_t* */
sizeof(float), /**< \%f out: float* */
sizeof(double), /**< \%lf out: double* */
-1, /**< \%s out: char** */
-1, /**< \%pDt out: char** */
-1, /**< \%pDi out: char** */
-1, /**< \%pDd out: char** */
-1, /**< \%pDa out: char** */
-1, /**< \%pDs out: char** */
-1, /**< \%pDz out: char** */
sizeof(apr_bucket_brigade), /**< \%pDb out: apr_bucket_brigade* */
sizeof(apr_bucket_brigade), /**< \%pDc out: apr_bucket_brigade* */
0 /**< \%pDn : in: void*, out: void** */
};
/*
* local functions
*/
/* close any open results for the connection */
static apr_status_t odbc_close_results(void *d)
{
apr_dbd_results_t *dbr = (apr_dbd_results_t *)d;
SQLRETURN rc = SQL_SUCCESS;
if (dbr && dbr->apr_dbd && dbr->apr_dbd->dbc) {
if (!dbr->isclosed)
rc = SQLCloseCursor(dbr->stmt);
dbr->isclosed = 1;
}
return APR_FROM_SQL_RESULT(rc);
}
/* close the ODBC statement handle from a prepare */
static apr_status_t odbc_close_pstmt(void *s)
{
SQLRETURN rc = APR_SUCCESS;
apr_dbd_prepared_t *statement = s;
/* stmt is closed if connection has already been closed */
if (statement) {
SQLHANDLE hstmt = statement->stmt;
if (hstmt && statement->apr_dbd && statement->apr_dbd->dbc) {
rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
statement->stmt = NULL;
}
return APR_FROM_SQL_RESULT(rc);
}
/* close: close/release a connection obtained from open() */
static apr_status_t odbc_close(apr_dbd_t *handle)
{
SQLRETURN rc = SQL_SUCCESS;
if (handle->dbc) {
rc = SQLDisconnect(handle->dbc);
CHECK_ERROR(handle, "SQLDisconnect", rc, SQL_HANDLE_DBC, handle->dbc);
rc = SQLFreeHandle(SQL_HANDLE_DBC, handle->dbc);
CHECK_ERROR(handle, "SQLFreeHandle (DBC)", rc, SQL_HANDLE_ENV, henv);
handle->dbc = NULL;
}
return APR_FROM_SQL_RESULT(rc);
}
/* odbc_close re-defined for passing to pool cleanup */
static apr_status_t odbc_close_cleanup(void *handle)
{
return odbc_close((apr_dbd_t *)handle);
}
/* close the ODBC environment handle at process termination */
static apr_status_t odbc_close_env(SQLHANDLE henv)
{
SQLRETURN rc;
rc = SQLFreeHandle(SQL_HANDLE_ENV, henv);
henv = NULL;
return APR_FROM_SQL_RESULT(rc);
}
/* setup the arrays in results for all the returned columns */
static SQLRETURN odbc_set_result_column(int icol, apr_dbd_results_t *res,
SQLHANDLE stmt)
{
SQLRETURN rc;
int maxsize, textsize, realsize, type, isunsigned = 1;
/* discover the sql type */
rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_UNSIGNED, NULL, 0, NULL,
(SQLPOINTER)&isunsigned);
isunsigned = (isunsigned == SQL_TRUE);
rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_TYPE, NULL, 0, NULL,
(SQLPOINTER)&type);
if (!SQL_SUCCEEDED(rc) || type == SQL_UNKNOWN_TYPE) {
/* MANY ODBC v2 datasources only supply CONCISE_TYPE */
rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_CONCISE_TYPE, NULL,
0, NULL, (SQLPOINTER)&type);
}
if (!SQL_SUCCEEDED(rc)) {
/* if still unknown make it CHAR */
type = SQL_C_CHAR;
}
switch (type) {
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_BIGINT:
/* fix these numeric binary types up as signed/unsigned for C types */
type += (isunsigned) ? SQL_UNSIGNED_OFFSET : SQL_SIGNED_OFFSET;
break;
/* LOB types are not changed to C types */
case SQL_LONGVARCHAR:
type = SQL_LONGVARCHAR;
break;
case SQL_LONGVARBINARY:
type = SQL_LONGVARBINARY;
break;
case SQL_FLOAT :
type = SQL_C_FLOAT;
break;
case SQL_DOUBLE :
type = SQL_C_DOUBLE;
break;
/* DBD wants times as strings */
case SQL_TIMESTAMP:
case SQL_DATE:
case SQL_TIME:
default:
type = SQL_C_CHAR;
}
res->coltypes[icol] = type;
/* size if retrieved as text */
rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0,
NULL, (SQLPOINTER)&textsize);
if (!SQL_SUCCEEDED(rc) || textsize < 0) {
textsize = res->apr_dbd->defaultBufferSize;
}
/* for null-term, which sometimes isn't included */
textsize++;
/* real size */
rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_OCTET_LENGTH, NULL, 0,
NULL, (SQLPOINTER)&realsize);
if (!SQL_SUCCEEDED(rc)) {
realsize = textsize;
}
maxsize = (textsize > realsize) ? textsize : realsize;
if (IS_LOB(type) || maxsize <= 0) {
/* LOB types are never bound and have a NULL colptr for binary.
* Ingore their real (1-2gb) length & use a default - the larger
* of defaultBufferSize or APR_BUCKET_BUFF_SIZE.
* If not a LOB, but simply unknown length - always use defaultBufferSize.
*/
maxsize = res->apr_dbd->defaultBufferSize;
if (IS_LOB(type) && maxsize < APR_BUCKET_BUFF_SIZE) {
maxsize = APR_BUCKET_BUFF_SIZE;
}
res->colptrs[icol] = NULL;
res->colstate[icol] = COL_AVAIL;
res->colsizes[icol] = maxsize;
rc = SQL_SUCCESS;
}
else {
res->colptrs[icol] = apr_pcalloc(res->pool, maxsize);
res->colsizes[icol] = maxsize;
if (res->apr_dbd->dboptions & SQL_GD_BOUND) {
/* we are allowed to call SQLGetData if we need to */
rc = SQLBindCol(stmt, icol + 1, res->coltypes[icol],
res->colptrs[icol], maxsize,
&(res->colinds[icol]));
CHECK_ERROR(res->apr_dbd, "SQLBindCol", rc, SQL_HANDLE_STMT,
stmt);
res->colstate[icol] = SQL_SUCCEEDED(rc) ? COL_BOUND : COL_AVAIL;
}
else {
/* this driver won't allow us to call SQLGetData on bound
* columns - so don't bind any
*/
res->colstate[icol] = COL_AVAIL;
rc = SQL_SUCCESS;
}
}
return rc;
}
/* create and populate an apr_dbd_results_t for a select */
static SQLRETURN odbc_create_results(apr_dbd_t *handle, SQLHANDLE hstmt,
apr_pool_t *pool, const int random,
apr_dbd_results_t **res)
{
SQLRETURN rc;
SQLSMALLINT ncols;
*res = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
(*res)->stmt = hstmt;
(*res)->dbc = handle->dbc;
(*res)->pool = pool;
(*res)->random = random;
(*res)->apr_dbd = handle;
rc = SQLNumResultCols(hstmt, &ncols);
CHECK_ERROR(handle, "SQLNumResultCols", rc, SQL_HANDLE_STMT, hstmt);
(*res)->ncols = ncols;
if (SQL_SUCCEEDED(rc)) {
int i;
(*res)->colnames = apr_pcalloc(pool, ncols * sizeof(char *));
(*res)->colptrs = apr_pcalloc(pool, ncols * sizeof(void *));
(*res)->colsizes = apr_pcalloc(pool, ncols * sizeof(SQLINTEGER));
(*res)->coltypes = apr_pcalloc(pool, ncols * sizeof(SQLSMALLINT));
(*res)->colinds = apr_pcalloc(pool, ncols * sizeof(SQLLEN));
(*res)->colstate = apr_pcalloc(pool, ncols * sizeof(int));
(*res)->ncols = ncols;
for (i = 0; i < ncols; i++) {
odbc_set_result_column(i, (*res), hstmt);
}
}
return rc;
}
/* bind a parameter - input params only, does not support output parameters */
static SQLRETURN odbc_bind_param(apr_pool_t *pool,
apr_dbd_prepared_t *statement, const int narg,
const SQLSMALLINT type, int *argp,
const void **args, const int textmode)
{
SQLRETURN rc;
SQLSMALLINT baseType, cType;
void *ptr;
SQLULEN len;
SQLLEN *indicator;
static SQLLEN nullValue = SQL_NULL_DATA;
static SQLSMALLINT inOut = SQL_PARAM_INPUT; /* only input params */
/* bind a NULL data value */
if (args[*argp] == NULL || type == APR_DBD_TYPE_NULL) {
baseType = SQL_CHAR;
cType = SQL_C_CHAR;
ptr = &nullValue;
len = sizeof(SQLINTEGER);
indicator = &nullValue;
(*argp)++;
}
/* bind a non-NULL data value */
else {
if (type < 0 || type >= NUM_APR_DBD_TYPES) {
return APR_EGENERAL;
}
baseType = sqlBaseType[type];
cType = sqlCtype[type];
indicator = NULL;
/* LOBs */
if (IS_LOB(cType)) {
ptr = (void *)args[*argp];
len = (SQLULEN) * (apr_size_t *)args[*argp + 1];
cType = (IS_CLOB(cType)) ? SQL_C_CHAR : SQL_C_DEFAULT;
(*argp) += 4; /* LOBs consume 4 args (last two are unused) */
}
/* non-LOBs */
else {
switch (baseType) {
case SQL_CHAR:
case SQL_DATE:
case SQL_TIME:
case SQL_TIMESTAMP:
ptr = (void *)args[*argp];
len = (SQLULEN)strlen(ptr);
break;
case SQL_TINYINT:
ptr = apr_palloc(pool, sizeof(unsigned char));
len = sizeof(unsigned char);
*(unsigned char *)ptr =
(textmode ?
atoi(args[*argp]) : *(unsigned char *)args[*argp]);
break;
case SQL_SMALLINT:
ptr = apr_palloc(pool, sizeof(short));
len = sizeof(short);
*(short *)ptr =
(textmode ? atoi(args[*argp]) : *(short *)args[*argp]);
break;
case SQL_INTEGER:
ptr = apr_palloc(pool, sizeof(int));
len = sizeof(int);
*(long *)ptr =
(textmode ? atol(args[*argp]) : *(long *)args[*argp]);
break;
case SQL_FLOAT:
ptr = apr_palloc(pool, sizeof(float));
len = sizeof(float);
*(float *)ptr =
(textmode ?
(float)atof(args[*argp]) : *(float *)args[*argp]);
break;
case SQL_DOUBLE:
ptr = apr_palloc(pool, sizeof(double));
len = sizeof(double);
*(double *)ptr =
(textmode ? atof(args[*argp]) : *(double *)
args[*argp]);
break;
case SQL_BIGINT:
ptr = apr_palloc(pool, sizeof(apr_int64_t));
len = sizeof(apr_int64_t);
*(apr_int64_t *)ptr =
(textmode ?
apr_atoi64(args[*argp]) : *(apr_int64_t *)args[*argp]);
break;
default:
return APR_EGENERAL;
}
(*argp)++; /* non LOBs consume one argument */
}
}
rc = SQLBindParameter(statement->stmt, narg, inOut, cType,
baseType, len, 0, ptr, len, indicator);
CHECK_ERROR(statement->apr_dbd, "SQLBindParameter", rc, SQL_HANDLE_STMT,
statement->stmt);
return rc;
}
/* LOB / Bucket Brigade functions */
/* bucket type specific destroy */
static void odbc_lob_bucket_destroy(void *data)
{
odbc_bucket *bd = data;
if (apr_bucket_shared_destroy(bd))
apr_bucket_free(bd);
}
/* set aside a bucket if possible */
static apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool)
{
odbc_bucket *bd = (odbc_bucket *)e->data;
/* Unlikely - but if the row pool is ancestor of this pool then it is OK */
if (apr_pool_is_ancestor(bd->row->pool, pool))
return APR_SUCCESS;
return apr_bucket_setaside_notimpl(e, pool);
}
/* split a bucket into a heap bucket followed by a LOB bkt w/remaining data */
static apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
apr_size_t *len, apr_read_type_e block)
{
SQLRETURN rc;
SQLLEN len_indicator;
SQLSMALLINT type;
odbc_bucket *bd = (odbc_bucket *)e->data;
apr_bucket *nxt;
void *buf;
int bufsize = bd->row->res->apr_dbd->defaultBufferSize;
int eos;
/* C type is CHAR for CLOBs, DEFAULT for BLOBs */
type = bd->row->res->coltypes[bd->col];
type = (type == SQL_LONGVARCHAR) ? SQL_C_CHAR : SQL_C_DEFAULT;
/* LOB buffers are always at least APR_BUCKET_BUFF_SIZE,
* but they may be much bigger per the BUFSIZE parameter.
*/
if (bufsize < APR_BUCKET_BUFF_SIZE)
bufsize = APR_BUCKET_BUFF_SIZE;
buf = apr_bucket_alloc(bufsize, e->list);
*str = NULL;
*len = 0;
rc = SQLGetData(bd->row->res->stmt, bd->col + 1,
type, buf, bufsize,
&len_indicator);
CHECK_ERROR(bd->row->res->apr_dbd, "SQLGetData", rc,
SQL_HANDLE_STMT, bd->row->res->stmt);
if (rc == SQL_NO_DATA || len_indicator == SQL_NULL_DATA || len_indicator < 0)
len_indicator = 0;
if (SQL_SUCCEEDED(rc) || rc == SQL_NO_DATA) {
if (rc == SQL_SUCCESS_WITH_INFO
&& (len_indicator == SQL_NO_TOTAL || len_indicator >= bufsize)) {
/* not the last read = a full buffer. CLOBs have a null terminator */
*len = bufsize - (IS_CLOB(bd->type) ? 1 : 0 );
eos = 0;
}
else {
/* the last read - len_indicator is supposed to be the length,
* but some driver get this wrong and return the total length.
* We try to handle both interpretations.
*/
*len = (len_indicator > bufsize
&& len_indicator >= (SQLLEN)e->start)
? (len_indicator - (SQLLEN)e->start) : len_indicator;
eos = 1;
}
if (!eos) {
/* Create a new LOB bucket to append and append it */
nxt = apr_bucket_alloc(sizeof(apr_bucket *), e->list);
APR_BUCKET_INIT(nxt);
nxt->length = -1;
nxt->data = e->data;
nxt->type = &odbc_bucket_type;
nxt->free = apr_bucket_free;
nxt->list = e->list;
nxt->start = e->start + *len;
APR_BUCKET_INSERT_AFTER(e, nxt);
}
else {
odbc_lob_bucket_destroy(e->data);
}
/* make current bucket into a heap bucket */
apr_bucket_heap_make(e, buf, *len, apr_bucket_free);
*str = buf;
/* No data is success in this context */
rc = SQL_SUCCESS;
}
return APR_FROM_SQL_RESULT(rc);
}
/* Create a bucket brigade on the row pool for a LOB column */
static apr_status_t odbc_create_bucket(const apr_dbd_row_t *row, const int col,
SQLSMALLINT type, apr_bucket_brigade *bb)
{
apr_bucket_alloc_t *list = bb->bucket_alloc;
apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
odbc_bucket *bd = apr_bucket_alloc(sizeof(odbc_bucket), list);
apr_bucket *eos = apr_bucket_eos_create(list);
bd->row = row;
bd->col = col;
bd->type = type;
APR_BUCKET_INIT(b);
b->type = &odbc_bucket_type;
b->free = apr_bucket_free;
b->list = list;
/* LOB lengths are unknown in ODBC */
b = apr_bucket_shared_make(b, bd, 0, -1);
APR_BRIGADE_INSERT_TAIL(bb, b);
APR_BRIGADE_INSERT_TAIL(bb, eos);
return APR_SUCCESS;
}
/* returns a data pointer for a column, returns NULL for NULL value,
* return -1 if data not available
*/
static void *odbc_get(const apr_dbd_row_t *row, const int col,
const SQLSMALLINT sqltype)
{
SQLRETURN rc;
SQLLEN indicator;
int state = row->res->colstate[col];
int options = row->res->apr_dbd->dboptions;
switch (state) {
case (COL_UNAVAIL):
return (void *)-1;
case (COL_RETRIEVED):
return NULL;
case (COL_BOUND):
case (COL_PRESENT):
if (sqltype == row->res->coltypes[col]) {
/* same type and we already have the data */
row->res->colstate[col] = COL_RETRIEVED;
return (row->res->colinds[col] == SQL_NULL_DATA) ?
NULL : row->res->colptrs[col];
}
}
/* we need to get the data now */
if (!(options & SQL_GD_ANY_ORDER)) {
/* this ODBC driver requires columns to be retrieved in order,
* so we attempt to get every prior un-gotten non-LOB column
*/
int i;
for (i = 0; i < col; i++) {
if (row->res->colstate[i] == COL_AVAIL) {
if (IS_LOB(row->res->coltypes[i]))
row->res->colstate[i] = COL_UNAVAIL;
else {
odbc_get(row, i, row->res->coltypes[i]);
row->res->colstate[i] = COL_PRESENT;
}
}
}
}
if ((state == COL_BOUND && !(options & SQL_GD_BOUND)))
/* this driver won't let us re-get bound columns */
return (void *)-1;
/* a LOB might not have a buffer allocated yet - so create one */
if (!row->res->colptrs[col])
row->res->colptrs[col] = apr_pcalloc(row->pool, row->res->colsizes[col]);
rc = SQLGetData(row->res->stmt, col + 1, sqltype, row->res->colptrs[col],
row->res->colsizes[col], &indicator);
CHECK_ERROR(row->res->apr_dbd, "SQLGetData", rc, SQL_HANDLE_STMT,
row->res->stmt);
if (indicator == SQL_NULL_DATA || rc == SQL_NO_DATA)
return NULL;
if (SQL_SUCCEEDED(rc)) {
/* whatever it was originally, it is now this sqltype */
row->res->coltypes[col] = sqltype;
/* this allows getting CLOBs in text mode by calling get_entry
* until it returns NULL
*/
row->res->colstate[col] =
(rc == SQL_SUCCESS_WITH_INFO) ? COL_AVAIL : COL_RETRIEVED;
return row->res->colptrs[col];
}
else
return (void *)-1;
}
/* Parse the parameter string for open */
static apr_status_t odbc_parse_params(apr_pool_t *pool, const char *params,
int *connect, SQLCHAR **datasource,
SQLCHAR **user, SQLCHAR **password,
int *defaultBufferSize, int *nattrs,
int **attrs, int **attrvals)
{
char *seps, *last, *next, *name[MAX_PARAMS], *val[MAX_PARAMS];
int nparams = 0, i, j;
*attrs = apr_pcalloc(pool, MAX_PARAMS * sizeof(char *));
*attrvals = apr_pcalloc(pool, MAX_PARAMS * sizeof(int));
*nattrs = 0;
seps = DEFAULTSEPS;
name[nparams] = apr_strtok(apr_pstrdup(pool, params), seps, &last);
/* no params is OK here - let connect return a more useful error msg */
if (!name[nparams])
return SQL_SUCCESS;
do {
if (last[strspn(last, seps)] == CSINGLEQUOTE) {
last += strspn(last, seps);
seps=SSINGLEQUOTE;
}
val[nparams] = apr_strtok(NULL, seps, &last);
seps = DEFAULTSEPS;
++nparams;
next = apr_strtok(NULL, seps, &last);
if (!next) {
break;
}
if (nparams >= MAX_PARAMS) {
/* too many parameters, no place to store */
return APR_EGENERAL;
}
name[nparams] = next;
} while (1);
for (j = i = 0; i < nparams; i++) {
if (!apr_strnatcasecmp(name[i], "CONNECT")) {
*datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
*connect = 1;
}
else if (!apr_strnatcasecmp(name[i], "DATASOURCE")) {
*datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
*connect = 0;
}
else if (!apr_strnatcasecmp(name[i], "USER")) {
*user = (SQLCHAR *)apr_pstrdup(pool, val[i]);
}
else if (!apr_strnatcasecmp(name[i], "PASSWORD")) {
*password = (SQLCHAR *)apr_pstrdup(pool, val[i]);
}
else if (!apr_strnatcasecmp(name[i], "BUFSIZE")) {
*defaultBufferSize = atoi(val[i]);
}
else if (!apr_strnatcasecmp(name[i], "ACCESS")) {
if (!apr_strnatcasecmp(val[i], "READ_ONLY"))
(*attrvals)[j] = SQL_MODE_READ_ONLY;
else if (!apr_strnatcasecmp(val[i], "READ_WRITE"))
(*attrvals)[j] = SQL_MODE_READ_WRITE;
else
return SQL_ERROR;
(*attrs)[j++] = SQL_ATTR_ACCESS_MODE;
}
else if (!apr_strnatcasecmp(name[i], "CTIMEOUT")) {
(*attrvals)[j] = atoi(val[i]);
(*attrs)[j++] = SQL_ATTR_LOGIN_TIMEOUT;
}
else if (!apr_strnatcasecmp(name[i], "STIMEOUT")) {
(*attrvals)[j] = atoi(val[i]);
(*attrs)[j++] = SQL_ATTR_CONNECTION_TIMEOUT;
}
else if (!apr_strnatcasecmp(name[i], "TXMODE")) {
if (!apr_strnatcasecmp(val[i], "READ_UNCOMMITTED"))
(*attrvals)[j] = SQL_TXN_READ_UNCOMMITTED;
else if (!apr_strnatcasecmp(val[i], "READ_COMMITTED"))
(*attrvals)[j] = SQL_TXN_READ_COMMITTED;
else if (!apr_strnatcasecmp(val[i], "REPEATABLE_READ"))
(*attrvals)[j] = SQL_TXN_REPEATABLE_READ;
else if (!apr_strnatcasecmp(val[i], "SERIALIZABLE"))
(*attrvals)[j] = SQL_TXN_SERIALIZABLE;
else if (!apr_strnatcasecmp(val[i], "DEFAULT"))
continue;
else
return SQL_ERROR;
(*attrs)[j++] = SQL_ATTR_TXN_ISOLATION;
}
else
return SQL_ERROR;
}
*nattrs = j;
return (*datasource && *defaultBufferSize) ? APR_SUCCESS : SQL_ERROR;
}
/* common handling after ODBC calls - save error info (code and text) in dbc */
static void check_error(apr_dbd_t *dbc, const char *step, SQLRETURN rc,
SQLSMALLINT type, SQLHANDLE h, int line)
{
SQLCHAR buffer[512];
SQLCHAR sqlstate[128];
SQLINTEGER native;
SQLSMALLINT reslength;
char *res, *p, *end, *logval = NULL;
int i;
/* set info about last error in dbc - fast return for SQL_SUCCESS */
if (rc == SQL_SUCCESS) {
char successMsg[] = "[dbd_odbc] SQL_SUCCESS ";
apr_size_t successMsgLen = sizeof successMsg - 1;
dbc->lasterrorcode = SQL_SUCCESS;
apr_cpystrn(dbc->lastError, successMsg, sizeof dbc->lastError);
apr_cpystrn(dbc->lastError + successMsgLen, step,
sizeof dbc->lastError - successMsgLen);
return;
}
switch (rc) {
case SQL_INVALID_HANDLE:
res = "SQL_INVALID_HANDLE";
break;
case SQL_ERROR:
res = "SQL_ERROR";
break;
case SQL_SUCCESS_WITH_INFO:
res = "SQL_SUCCESS_WITH_INFO";
break;
case SQL_STILL_EXECUTING:
res = "SQL_STILL_EXECUTING";
break;
case SQL_NEED_DATA:
res = "SQL_NEED_DATA";
break;
case SQL_NO_DATA:
res = "SQL_NO_DATA";
break;
default:
res = "unrecognized SQL return code";
}
/* these two returns are expected during normal execution */
if (rc != SQL_SUCCESS_WITH_INFO && rc != SQL_NO_DATA
&& dbc->can_commit != APR_DBD_TRANSACTION_IGNORE_ERRORS) {
dbc->can_commit = APR_DBD_TRANSACTION_ROLLBACK;
}
p = dbc->lastError;
end = p + sizeof(dbc->lastError);
dbc->lasterrorcode = rc;
p += sprintf(p, "[dbd_odbc] %.64s returned %.30s (%d) at %.24s:%d ",
step, res, rc, SOURCE_FILE, line - 1);
for (i = 1, rc = 0; rc == 0; i++) {
rc = SQLGetDiagRec(type, h, i, sqlstate, &native, buffer,
sizeof(buffer), &reslength);
if (SQL_SUCCEEDED(rc) && (p < (end - 280)))
p += sprintf(p, "%.256s %.20s ", buffer, sqlstate);
}
apr_env_get(&logval, "apr_dbd_odbc_log", dbc->pool);
/* if env var was set or call was init/open (no dbname) - log to stderr */
if (logval || !dbc->dbname ) {
char timestamp[APR_CTIME_LEN];
apr_file_t *se;
apr_ctime(timestamp, apr_time_now());
apr_file_open_stderr(&se, dbc->pool);
apr_file_printf(se, "[%s] %s\n", timestamp, dbc->lastError);
}
}
static APR_INLINE int odbc_check_rollback(apr_dbd_t *handle)
{
if (handle->can_commit == APR_DBD_TRANSACTION_ROLLBACK) {
handle->lasterrorcode = SQL_ERROR;
apr_cpystrn(handle->lastError, "[dbd_odbc] Rollback pending ",
sizeof handle->lastError);
return 1;
}
return 0;
}
/*
* public functions per DBD driver API
*/
/** init: allow driver to perform once-only initialisation. **/
static void odbc_init(apr_pool_t *pool)
{
SQLRETURN rc;
char *step;
apr_version_t apuver;
apu_version(&apuver);
if (apuver.major != DRIVER_APU_VERSION_MAJOR
|| apuver.minor != DRIVER_APU_VERSION_MINOR) {
apr_file_t *se;
apr_file_open_stderr(&se, pool);
apr_file_printf(se, "Incorrect " ODBC_DRIVER_STRING " dbd driver version\n"
"Attempt to load APU version %d.%d driver with APU version %d.%d\n",
DRIVER_APU_VERSION_MAJOR, DRIVER_APU_VERSION_MINOR,
apuver.major, apuver.minor);
abort();
}
if (henv)
return;
step = "SQLAllocHandle (SQL_HANDLE_ENV)";
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
apr_pool_cleanup_register(pool, henv, odbc_close_env, apr_pool_cleanup_null);
if (SQL_SUCCEEDED(rc)) {
step = "SQLSetEnvAttr";
rc = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3, 0);
}
else {
apr_dbd_t tmp_dbc;
SQLHANDLE err_h = henv;
tmp_dbc.pool = pool;
tmp_dbc.dbname = NULL;
CHECK_ERROR(&tmp_dbc, step, rc, SQL_HANDLE_ENV, err_h);
}
}
/** native_handle: return the native database handle of the underlying db **/
static void *odbc_native_handle(apr_dbd_t *handle)
{
return handle->dbc;
}
/** open: obtain a database connection from the server rec. **/
/* It would be more efficient to allocate a single statement handle
* here - but SQL_ATTR_CURSOR_SCROLLABLE must be set before
* SQLPrepare, and we don't know whether random-access is
* specified until SQLExecute so we cannot.
*/
static apr_dbd_t *odbc_open(apr_pool_t *pool, const char *params, const char **error)
{
SQLRETURN rc;
SQLHANDLE hdbc = NULL;
apr_dbd_t *handle;
char *err_step;
int err_htype, i;
int defaultBufferSize = DEFAULT_BUFFER_SIZE;
SQLHANDLE err_h = NULL;
SQLCHAR *datasource = (SQLCHAR *)"", *user = (SQLCHAR *)"",
*password = (SQLCHAR *)"";
int nattrs = 0, *attrs = NULL, *attrvals = NULL, connect = 0;
err_step = "SQLAllocHandle (SQL_HANDLE_DBC)";
err_htype = SQL_HANDLE_ENV;
err_h = henv;
rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if (SQL_SUCCEEDED(rc)) {
err_step = "Invalid DBD Parameters - open";
err_htype = SQL_HANDLE_DBC;
err_h = hdbc;
rc = odbc_parse_params(pool, params, &connect, &datasource, &user,
&password, &defaultBufferSize, &nattrs, &attrs,
&attrvals);
}
if (SQL_SUCCEEDED(rc)) {
for (i = 0; i < nattrs && SQL_SUCCEEDED(rc); i++) {
err_step = "SQLSetConnectAttr (from DBD Parameters)";
err_htype = SQL_HANDLE_DBC;
err_h = hdbc;
rc = SQLSetConnectAttr(hdbc, attrs[i], (SQLPOINTER)attrvals[i], 0);
}
}
if (SQL_SUCCEEDED(rc)) {
if (connect) {
SQLCHAR out[1024];
SQLSMALLINT outlen;
err_step = "SQLDriverConnect";
err_htype = SQL_HANDLE_DBC;
err_h = hdbc;
rc = SQLDriverConnect(hdbc, NULL, datasource,
(SQLSMALLINT)strlen((char *)datasource),
out, sizeof(out), &outlen, SQL_DRIVER_NOPROMPT);
}
else {
err_step = "SQLConnect";
err_htype = SQL_HANDLE_DBC;
err_h = hdbc;
rc = SQLConnect(hdbc, datasource,
(SQLSMALLINT)strlen((char *)datasource),
user, (SQLSMALLINT)strlen((char *)user),
password, (SQLSMALLINT)strlen((char *)password));
}
}
if (SQL_SUCCEEDED(rc)) {
handle = apr_pcalloc(pool, sizeof(apr_dbd_t));
handle->dbname = apr_pstrdup(pool, (char *)datasource);
handle->dbc = hdbc;
handle->pool = pool;
handle->defaultBufferSize = defaultBufferSize;
CHECK_ERROR(handle, "SQLConnect", rc, SQL_HANDLE_DBC, handle->dbc);
handle->default_transaction_mode = 0;
handle->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
SQLGetInfo(hdbc, SQL_DEFAULT_TXN_ISOLATION,
&(handle->default_transaction_mode), sizeof(int), NULL);
handle->transaction_mode = handle->default_transaction_mode;
SQLGetInfo(hdbc, SQL_GETDATA_EXTENSIONS ,&(handle->dboptions),
sizeof(int), NULL);
apr_pool_cleanup_register(pool, handle, odbc_close_cleanup, apr_pool_cleanup_null);
return handle;
}
else {
apr_dbd_t tmp_dbc;
tmp_dbc.pool = pool;
tmp_dbc.dbname = NULL;
CHECK_ERROR(&tmp_dbc, err_step, rc, err_htype, err_h);
if (error)
*error = apr_pstrdup(pool, tmp_dbc.lastError);
if (hdbc)
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
return NULL;
}
}
/** check_conn: check status of a database connection **/
static apr_status_t odbc_check_conn(apr_pool_t *pool, apr_dbd_t *handle)
{
SQLUINTEGER isDead;
SQLRETURN rc;
rc = SQLGetConnectAttr(handle->dbc, SQL_ATTR_CONNECTION_DEAD, &isDead,
sizeof(SQLUINTEGER), NULL);
CHECK_ERROR(handle, "SQLGetConnectAttr (SQL_ATTR_CONNECTION_DEAD)", rc,
SQL_HANDLE_DBC, handle->dbc);
/* if driver cannot check connection, say so */
if (rc != SQL_SUCCESS)
return APR_ENOTIMPL;
return (isDead == SQL_CD_FALSE) ? APR_SUCCESS : APR_EGENERAL;
}
/** set_dbname: select database name. May be a no-op if not supported. **/
static int odbc_set_dbname(apr_pool_t*pool, apr_dbd_t *handle,
const char *name)
{
if (apr_strnatcmp(name, handle->dbname)) {
return APR_EGENERAL; /* It's illegal to change dbname in ODBC */
}
CHECK_ERROR(handle, "set_dbname (no-op)", SQL_SUCCESS, SQL_HANDLE_DBC,
handle->dbc);
return APR_SUCCESS; /* OK if it's the same name */
}
/** transaction: start a transaction. May be a no-op. **/
static int odbc_start_transaction(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_transaction_t **trans)
{
SQLRETURN rc = SQL_SUCCESS;
if (handle->transaction_mode) {
rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_TXN_ISOLATION,
(SQLPOINTER)handle->transaction_mode, 0);
CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_TXN_ISOLATION)", rc,
SQL_HANDLE_DBC, handle->dbc);
}
if (SQL_SUCCEEDED(rc)) {
/* turn off autocommit for transactions */
rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_AUTOCOMMIT,
SQL_AUTOCOMMIT_OFF, 0);
CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)", rc,
SQL_HANDLE_DBC, handle->dbc);
}
if (SQL_SUCCEEDED(rc)) {
*trans = apr_palloc(pool, sizeof(apr_dbd_transaction_t));
(*trans)->dbc = handle->dbc;
(*trans)->apr_dbd = handle;
}
handle->can_commit = APR_DBD_TRANSACTION_COMMIT;
return APR_FROM_SQL_RESULT(rc);
}
/** end_transaction: end a transaction **/
static int odbc_end_transaction(apr_dbd_transaction_t *trans)
{
SQLRETURN rc;
int action = (trans->apr_dbd->can_commit != APR_DBD_TRANSACTION_ROLLBACK)
? SQL_COMMIT : SQL_ROLLBACK;
rc = SQLEndTran(SQL_HANDLE_DBC, trans->dbc, action);
CHECK_ERROR(trans->apr_dbd, "SQLEndTran", rc, SQL_HANDLE_DBC, trans->dbc);
if (SQL_SUCCEEDED(rc)) {
rc = SQLSetConnectAttr(trans->dbc, SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
CHECK_ERROR(trans->apr_dbd, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)",
rc, SQL_HANDLE_DBC, trans->dbc);
}
trans->apr_dbd->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
return APR_FROM_SQL_RESULT(rc);
}
/** query: execute an SQL statement which doesn't return a result set **/
static int odbc_query(apr_dbd_t *handle, int *nrows, const char *statement)
{
SQLRETURN rc;
SQLHANDLE hstmt = NULL;
size_t len = strlen(statement);
if (odbc_check_rollback(handle))
return APR_EGENERAL;
rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
handle->dbc);
if (!SQL_SUCCEEDED(rc))
return APR_FROM_SQL_RESULT(rc);
rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
if (SQL_SUCCEEDED(rc)) {
SQLLEN rowcount;
rc = SQLRowCount(hstmt, &rowcount);
*nrows = (int)rowcount;
CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT, hstmt);
}
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
return APR_FROM_SQL_RESULT(rc);
}
/** select: execute an SQL statement which returns a result set **/
static int odbc_select(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_results_t **res, const char *statement,
int random)
{
SQLRETURN rc;
SQLHANDLE hstmt;
apr_dbd_prepared_t *stmt;
size_t len = strlen(statement);
if (odbc_check_rollback(handle))
return APR_EGENERAL;
rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
handle->dbc);
if (!SQL_SUCCEEDED(rc))
return APR_FROM_SQL_RESULT(rc);
/* Prepare an apr_dbd_prepared_t for pool cleanup, even though this
* is not a prepared statement. We want the same cleanup mechanism.
*/
stmt = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
stmt->apr_dbd = handle;
stmt->dbc = handle->dbc;
stmt->stmt = hstmt;
apr_pool_cleanup_register(pool, stmt, odbc_close_pstmt, apr_pool_cleanup_null);
if (random) {
rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
(SQLPOINTER)SQL_SCROLLABLE, 0);
CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)", rc,
SQL_HANDLE_STMT, hstmt);
}
if (SQL_SUCCEEDED(rc)) {
rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
}
if (SQL_SUCCEEDED(rc)) {
rc = odbc_create_results(handle, hstmt, pool, random, res);
apr_pool_cleanup_register(pool, *res,
odbc_close_results, apr_pool_cleanup_null);
}
return APR_FROM_SQL_RESULT(rc);
}
/** num_cols: get the number of columns in a results set **/
static int odbc_num_cols(apr_dbd_results_t *res)
{
return res->ncols;
}
/** num_tuples: get the number of rows in a results set **/
static int odbc_num_tuples(apr_dbd_results_t *res)
{
SQLRETURN rc;
SQLLEN nrows;
rc = SQLRowCount(res->stmt, &nrows);
CHECK_ERROR(res->apr_dbd, "SQLRowCount", rc, SQL_HANDLE_STMT, res->stmt);
return SQL_SUCCEEDED(rc) ? (int)nrows : -1;
}
/** get_row: get a row from a result set **/
static int odbc_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
apr_dbd_row_t **row, int rownum)
{
SQLRETURN rc;
char *fetchtype;
int c;
*row = apr_pcalloc(pool, sizeof(apr_dbd_row_t));
(*row)->stmt = res->stmt;
(*row)->dbc = res->dbc;
(*row)->res = res;
(*row)->pool = res->pool;
/* mark all the columns as needing SQLGetData unless they are bound */
for (c = 0; c < res->ncols; c++) {
if (res->colstate[c] != COL_BOUND) {
res->colstate[c] = COL_AVAIL;
}
/* some drivers do not null-term zero-len CHAR data */
if (res->colptrs[c])
*(char *)res->colptrs[c] = 0;
}
if (res->random && (rownum > 0)) {
fetchtype = "SQLFetchScroll";
rc = SQLFetchScroll(res->stmt, SQL_FETCH_ABSOLUTE, rownum);
}
else {
fetchtype = "SQLFetch";
rc = SQLFetch(res->stmt);
}
CHECK_ERROR(res->apr_dbd, fetchtype, rc, SQL_HANDLE_STMT, res->stmt);
(*row)->stmt = res->stmt;
if (!SQL_SUCCEEDED(rc) && !res->random) {
/* early close on any error (usually SQL_NO_DATA) if fetching
* sequentially to release resources ASAP
*/
odbc_close_results(res);
return -1;
}
return SQL_SUCCEEDED(rc) ? 0 : -1;
}
/** datum_get: get a binary entry from a row **/
static apr_status_t odbc_datum_get(const apr_dbd_row_t *row, int col,
apr_dbd_type_e dbdtype, void *data)
{
SQLSMALLINT sqltype;
void *p;
int len;
if (col >= row->res->ncols)
return APR_EGENERAL;
if (dbdtype < 0 || dbdtype >= NUM_APR_DBD_TYPES) {
data = NULL; /* invalid type */
return APR_EGENERAL;
}
len = sqlSizes[dbdtype];
sqltype = sqlCtype[dbdtype];
/* must not memcpy a brigade, sentinals are relative to orig loc */
if (IS_LOB(sqltype))
return odbc_create_bucket(row, col, sqltype, data);
p = odbc_get(row, col, sqltype);
if (p == (void *)-1)
return APR_EGENERAL;
if (p == NULL)
return APR_ENOENT; /* SQL NULL value */
if (len < 0)
*(char**)data = (char *)p;
else
memcpy(data, p, len);
return APR_SUCCESS;
}
/** get_entry: get an entry from a row (string data) **/
static const char *odbc_get_entry(const apr_dbd_row_t *row, int col)
{
void *p;
if (col >= row->res->ncols)
return NULL;
p = odbc_get(row, col, SQL_C_CHAR);
/* NULL or invalid (-1) */
if (p == NULL || p == (void *)-1)
return p;
else
return apr_pstrdup(row->pool, p);
}
/** error: get current error message (if any) **/
static const char *odbc_error(apr_dbd_t *handle, int errnum)
{
return (handle) ? handle->lastError : "[dbd_odbc]No error message available";
}
/** escape: escape a string so it is safe for use in query/select **/
static const char *odbc_escape(apr_pool_t *pool, const char *s,
apr_dbd_t *handle)
{
char *newstr, *src, *dst, *sq;
int qcount;
/* return the original if there are no single-quotes */
if (!(sq = strchr(s, '\'')))
return (char *)s;
/* count the single-quotes and allocate a new buffer */
for (qcount = 1; (sq = strchr(sq + 1, '\'')); )
qcount++;
newstr = apr_palloc(pool, strlen(s) + qcount + 1);
/* move chars, doubling all single-quotes */
src = (char *)s;
for (dst = newstr; *src; src++) {
if ((*dst++ = *src) == '\'')
*dst++ = '\'';
}
*dst = 0;
return newstr;
}
/** prepare: prepare a statement **/
static int odbc_prepare(apr_pool_t *pool, apr_dbd_t *handle,
const char *query, const char *label, int nargs,
int nvals, apr_dbd_type_e *types,
apr_dbd_prepared_t **statement)
{
SQLRETURN rc;
size_t len = strlen(query);
if (odbc_check_rollback(handle))
return APR_EGENERAL;
*statement = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
(*statement)->dbc = handle->dbc;
(*statement)->apr_dbd = handle;
(*statement)->nargs = nargs;
(*statement)->nvals = nvals;
(*statement)->types =
apr_pmemdup(pool, types, nargs * sizeof(apr_dbd_type_e));
rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &((*statement)->stmt));
apr_pool_cleanup_register(pool, *statement,
odbc_close_pstmt, apr_pool_cleanup_null);
CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc,
SQL_HANDLE_DBC, handle->dbc);
rc = SQLPrepare((*statement)->stmt, (SQLCHAR *)query, (SQLINTEGER)len);
CHECK_ERROR(handle, "SQLPrepare", rc, SQL_HANDLE_STMT,
(*statement)->stmt);
return APR_FROM_SQL_RESULT(rc);
}
/** pquery: query using a prepared statement + args **/
static int odbc_pquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
apr_dbd_prepared_t *statement, const char **args)
{
SQLRETURN rc = SQL_SUCCESS;
int i, argp;
if (odbc_check_rollback(handle))
return APR_EGENERAL;
for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
&argp, (const void **)args, TEXTMODE);
}
if (SQL_SUCCEEDED(rc)) {
rc = SQLExecute(statement->stmt);
CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
SQLLEN rowcount;
rc = SQLRowCount(statement->stmt, &rowcount);
*nrows = (int)rowcount;
CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
statement->stmt);
}
return APR_FROM_SQL_RESULT(rc);
}
/** pvquery: query using a prepared statement + args **/
static int odbc_pvquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
apr_dbd_prepared_t *statement, va_list args)
{
const char **values;
int i;
values = apr_palloc(pool, sizeof(*values) * statement->nvals);
for (i = 0; i < statement->nvals; i++)
values[i] = va_arg(args, const char *);
return odbc_pquery(pool, handle, nrows, statement, values);
}
/** pselect: select using a prepared statement + args **/
static int odbc_pselect(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_results_t **res, apr_dbd_prepared_t *statement,
int random, const char **args)
{
SQLRETURN rc = SQL_SUCCESS;
int i, argp;
if (odbc_check_rollback(handle))
return APR_EGENERAL;
if (random) {
rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
(SQLPOINTER)SQL_SCROLLABLE, 0);
CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
rc, SQL_HANDLE_STMT, statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
&argp, (const void **)args, TEXTMODE);
}
}
if (SQL_SUCCEEDED(rc)) {
rc = SQLExecute(statement->stmt);
CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
rc = odbc_create_results(handle, statement->stmt, pool, random, res);
apr_pool_cleanup_register(pool, *res,
odbc_close_results, apr_pool_cleanup_null);
}
return APR_FROM_SQL_RESULT(rc);
}
/** pvselect: select using a prepared statement + args **/
static int odbc_pvselect(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_results_t **res,
apr_dbd_prepared_t *statement, int random,
va_list args)
{
const char **values;
int i;
values = apr_palloc(pool, sizeof(*values) * statement->nvals);
for (i = 0; i < statement->nvals; i++)
values[i] = va_arg(args, const char *);
return odbc_pselect(pool, handle, res, statement, random, values);
}
/** get_name: get a column title from a result set **/
static const char *odbc_get_name(const apr_dbd_results_t *res, int col)
{
SQLRETURN rc;
char buffer[MAX_COLUMN_NAME];
SQLSMALLINT colnamelength, coltype, coldecimal, colnullable;
SQLULEN colsize;
if (col >= res->ncols)
return NULL; /* bogus column number */
if (res->colnames[col] != NULL)
return res->colnames[col]; /* we already retrieved it */
rc = SQLDescribeCol(res->stmt, col + 1,
(SQLCHAR *)buffer, sizeof(buffer), &colnamelength,
&coltype, &colsize, &coldecimal, &colnullable);
CHECK_ERROR(res->apr_dbd, "SQLDescribeCol", rc,
SQL_HANDLE_STMT, res->stmt);
res->colnames[col] = apr_pstrdup(res->pool, buffer);
return res->colnames[col];
}
/** transaction_mode_get: get the mode of transaction **/
static int odbc_transaction_mode_get(apr_dbd_transaction_t *trans)
{
return (int)trans->apr_dbd->can_commit;
}
/** transaction_mode_set: set the mode of transaction **/
static int odbc_transaction_mode_set(apr_dbd_transaction_t *trans, int mode)
{
int legal = ( APR_DBD_TRANSACTION_IGNORE_ERRORS
| APR_DBD_TRANSACTION_COMMIT
| APR_DBD_TRANSACTION_ROLLBACK);
if ((mode & legal) != mode)
return APR_EGENERAL;
trans->apr_dbd->can_commit = mode;
return APR_SUCCESS;
}
/** pbquery: query using a prepared statement + binary args **/
static int odbc_pbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
apr_dbd_prepared_t *statement, const void **args)
{
SQLRETURN rc = SQL_SUCCESS;
int i, argp;
if (odbc_check_rollback(handle))
return APR_EGENERAL;
for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++)
rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
&argp, args, BINARYMODE);
if (SQL_SUCCEEDED(rc)) {
rc = SQLExecute(statement->stmt);
CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
SQLLEN rowcount;
rc = SQLRowCount(statement->stmt, &rowcount);
*nrows = (int)rowcount;
CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
statement->stmt);
}
return APR_FROM_SQL_RESULT(rc);
}
/** pbselect: select using a prepared statement + binary args **/
static int odbc_pbselect(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_results_t **res,
apr_dbd_prepared_t *statement,
int random, const void **args)
{
SQLRETURN rc = SQL_SUCCESS;
int i, argp;
if (odbc_check_rollback(handle))
return APR_EGENERAL;
if (random) {
rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
(SQLPOINTER)SQL_SCROLLABLE, 0);
CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
rc, SQL_HANDLE_STMT, statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
&argp, args, BINARYMODE);
}
}
if (SQL_SUCCEEDED(rc)) {
rc = SQLExecute(statement->stmt);
CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
statement->stmt);
}
if (SQL_SUCCEEDED(rc)) {
rc = odbc_create_results(handle, statement->stmt, pool, random, res);
apr_pool_cleanup_register(pool, *res,
odbc_close_results, apr_pool_cleanup_null);
}
return APR_FROM_SQL_RESULT(rc);
}
/** pvbquery: query using a prepared statement + binary args **/
static int odbc_pvbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
apr_dbd_prepared_t *statement, va_list args)
{
const char **values;
int i;
values = apr_palloc(pool, sizeof(*values) * statement->nvals);
for (i = 0; i < statement->nvals; i++)
values[i] = va_arg(args, const char *);
return odbc_pbquery(pool, handle, nrows, statement, (const void **)values);
}
/** pvbselect: select using a prepared statement + binary args **/
static int odbc_pvbselect(apr_pool_t *pool, apr_dbd_t *handle,
apr_dbd_results_t **res,
apr_dbd_prepared_t *statement,
int random, va_list args)
{
const char **values;
int i;
values = apr_palloc(pool, sizeof(*values) * statement->nvals);
for (i = 0; i < statement->nvals; i++)
values[i] = va_arg(args, const char *);
return odbc_pbselect(pool, handle, res, statement, random, (const void **)values);
}
APU_MODULE_DECLARE_DATA const apr_dbd_driver_t ODBC_DRIVER_ENTRY = {
ODBC_DRIVER_STRING,
odbc_init,
odbc_native_handle,
odbc_open,
odbc_check_conn,
odbc_close,
odbc_set_dbname,
odbc_start_transaction,
odbc_end_transaction,
odbc_query,
odbc_select,
odbc_num_cols,
odbc_num_tuples,
odbc_get_row,
odbc_get_entry,
odbc_error,
odbc_escape,
odbc_prepare,
odbc_pvquery,
odbc_pvselect,
odbc_pquery,
odbc_pselect,
odbc_get_name,
odbc_transaction_mode_get,
odbc_transaction_mode_set,
"?",
odbc_pvbquery,
odbc_pvbselect,
odbc_pbquery,
odbc_pbselect,
odbc_datum_get
};
#endif