/* 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" #include "apr_private.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 "apr_version.h" #include /* 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 #include #elif defined(HAVE_ODBC_SQL_H) #include #include #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_APR_VERSION_MAJOR APR_MAJOR_VERSION #define DRIVER_APR_VERSION_MINOR APR_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 */ apr_intptr_t transaction_mode; apr_intptr_t dboptions; /* driver options re SQLGetData */ apr_intptr_t 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; apr_intptr_t 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] = (SQLSMALLINT)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] = (SQLINTEGER)maxsize; rc = SQL_SUCCESS; } else { res->colptrs[icol] = apr_pcalloc(res->pool, maxsize); res->colsizes[icol] = (SQLINTEGER)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(long)); len = sizeof(long); *(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 *nxt, 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]; apr_intptr_t 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, apr_intptr_t **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(apr_intptr_t)); *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 aprver; apr_version(&aprver); if (aprver.major != DRIVER_APR_VERSION_MAJOR || aprver.minor != DRIVER_APR_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 APR version %d.%d driver with APR version %d.%d\n", DRIVER_APR_VERSION_MAJOR, DRIVER_APR_VERSION_MINOR, aprver.major, aprver.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, connect = 0; apr_intptr_t *attrvals = NULL; 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(apr_intptr_t), NULL); handle->transaction_mode = handle->default_transaction_mode; SQLGetInfo(hdbc, SQL_GETDATA_EXTENSIONS ,&(handle->dboptions), sizeof(apr_intptr_t), 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); } APR_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