From commits-return-9729-apmail-apr-commits-archive=apr.apache.org@apr.apache.org Thu Jun 12 17:55:09 2008 Return-Path: Delivered-To: apmail-apr-commits-archive@www.apache.org Received: (qmail 66169 invoked from network); 12 Jun 2008 17:55:08 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 12 Jun 2008 17:55:08 -0000 Received: (qmail 22413 invoked by uid 500); 12 Jun 2008 17:55:11 -0000 Delivered-To: apmail-apr-commits-archive@apr.apache.org Received: (qmail 22365 invoked by uid 500); 12 Jun 2008 17:55:11 -0000 Mailing-List: contact commits-help@apr.apache.org; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: Reply-To: dev@apr.apache.org List-Id: Delivered-To: mailing list commits@apr.apache.org Received: (qmail 22355 invoked by uid 99); 12 Jun 2008 17:55:10 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 12 Jun 2008 10:55:10 -0700 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 12 Jun 2008 17:54:28 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 7BE0923889BA; Thu, 12 Jun 2008 10:54:16 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r667182 - in /apr/apr-util/trunk: Makefile.win aprutil.dsw dbd/apr_dbd.c dbd/apr_dbd_odbc.c dbd/apr_dbd_odbc.dsp include/apu.hw include/private/apr_dbd_odbc_v2.h Date: Thu, 12 Jun 2008 17:54:16 -0000 To: commits@apr.apache.org From: tdonovan@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20080612175416.7BE0923889BA@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: tdonovan Date: Thu Jun 12 10:54:15 2008 New Revision: 667182 URL: http://svn.apache.org/viewvc?rev=667182&view=rev Log: apr_dbd_odbc driver Added: apr/apr-util/trunk/dbd/apr_dbd_odbc.c apr/apr-util/trunk/dbd/apr_dbd_odbc.dsp apr/apr-util/trunk/include/private/apr_dbd_odbc_v2.h Modified: apr/apr-util/trunk/Makefile.win apr/apr-util/trunk/aprutil.dsw apr/apr-util/trunk/dbd/apr_dbd.c apr/apr-util/trunk/include/apu.hw Modified: apr/apr-util/trunk/Makefile.win URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/Makefile.win?rev=667182&r1=667181&r2=667182&view=diff ============================================================================== --- apr/apr-util/trunk/Makefile.win (original) +++ apr/apr-util/trunk/Makefile.win Thu Jun 12 10:54:15 2008 @@ -190,16 +190,18 @@ cd ldap $(MAKE) $(MAKEOPT) -f apr_ldap.mak CFG="apr_ldap - $(ARCH)" RECURSE=0 $(CTARGET) cd .. -!IFDEF DBD_LIST cd dbd + $(MAKE) $(MAKEOPT) -f apr_dbd_%d.mak CFG="apr_dbd_odbc - $(ARCH)" RECURSE=0 $(CTARGET) +!IFDEF DBD_LIST for %d in ($(DBD_LIST)) do \ $(MAKE) $(MAKEOPT) -f apr_dbd_%d.mak CFG="apr_dbd_%d - $(ARCH)" RECURSE=0 $(CTARGET) - cd .. !ENDIF + cd .. !ELSEIF $(USESLN) == 1 clean: + devenv aprutil.sln /useenv /clean "$(SLNARCH)" /project apr_dbd_odbc !IFDEF DBD_LIST -for %d in ($(DBD_LIST)) do \ devenv aprutil.sln /useenv /clean "$(SLNARCH)" /project apr_dbd_%d @@ -224,6 +226,7 @@ devenv aprutil.sln /useenv /build "$(SLNARCH)" /project aprutil devenv aprutil.sln /useenv /build "$(SLNARCH)" /project libaprutil devenv aprutil.sln /useenv /build "$(SLNARCH)" /project apr_ldap + devenv aprutil.sln /useenv /build "$(SLNARCH)" /project apr_dbd_odbc !IFDEF DBD_LIST for %d in ($(DBD_LIST)) do \ devenv aprutil.sln /useenv /build "$(SLNARCH)" /project apr_dbd_%d @@ -233,6 +236,7 @@ # $(USEDSP) == 1 clean: + msdev aprutil.dsw /USEENV /MAKE "apr_dbd_odbc - $(ARCH)" /CLEAN !IFDEF DBD_LIST -for %d in ($(DBD_LIST)) do \ msdev aprutil.dsw /USEENV /MAKE "apr_dbd_%d - $(ARCH)" /CLEAN @@ -257,6 +261,7 @@ @msdev aprutil.dsw /USEENV /MAKE "libaprapp - $(ARCH)" @msdev aprutil.dsw /USEENV /MAKE "libaprutil - $(ARCH)" @msdev aprutil.dsw /USEENV /MAKE "apr_ldap - $(ARCH)" + msdev aprutil.dsw /USEENV /MAKE "apr_dbd_odbc - $(ARCH)" !IFDEF DBD_LIST @for %d in ($(DBD_LIST)) do \ msdev aprutil.dsw /USEENV /MAKE "apr_dbd_%d - $(ARCH)" @@ -317,6 +322,8 @@ copy $(APU_PATH)\$(ARCHPATH)\libaprutil-1.pdb "$(PREFIX)\bin\" <.y copy $(APU_PATH)\ldap\$(ARCHPATH)\apr_ldap-1.dll "$(PREFIX)\bin\" <.y copy $(APU_PATH)\ldap\$(ARCHPATH)\apr_ldap-1.pdb "$(PREFIX)\bin\" <.y + copy $(APU_PATH)\dbd\$(ARCHPATH)\apr_dbd_odbc-1.dll "$(PREFIX)\bin\" <.y && \ + copy $(APU_PATH)\dbd\$(ARCHPATH)\apr_dbd_odbc-1.pdb "$(PREFIX)\bin\" <.y \ !IFDEF DBD_LIST for %d in ($(DBD_LIST)) do ( \ copy $(APU_PATH)\dbd\$(ARCHPATH)\apr_dbd_%d-1.dll "$(PREFIX)\bin\" <.y && \ Modified: apr/apr-util/trunk/aprutil.dsw URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/aprutil.dsw?rev=667182&r1=667181&r2=667182&view=diff ============================================================================== --- apr/apr-util/trunk/aprutil.dsw (original) +++ apr/apr-util/trunk/aprutil.dsw Thu Jun 12 10:54:15 2008 @@ -15,7 +15,7 @@ ############################################################################### -Project: "aprapp"="..\apr\build\aprapp.dsp" - Package Owner=<4> +Project: "apr_dbd_freetds"=".\dbd\apr_dbd_freetds.dsp" - Package Owner=<4> Package=<5> {{{ @@ -24,13 +24,16 @@ Package=<4> {{{ Begin Project Dependency - Project_Dep_Name preaprapp + Project_Dep_Name libapr + End Project Dependency + Begin Project Dependency + Project_Dep_Name libaprutil End Project Dependency }}} ############################################################################### -Project: "apr_dbd_freetds"=".\dbd\apr_dbd_freetds.dsp" - Package Owner=<4> +Project: "apr_dbd_mysql"=".\dbd\apr_dbd_mysql.dsp" - Package Owner=<4> Package=<5> {{{ @@ -48,7 +51,7 @@ ############################################################################### -Project: "apr_dbd_mysql"=".\dbd\apr_dbd_mysql.dsp" - Package Owner=<4> +Project: "apr_dbd_odbc"=".\dbd\apr_dbd_odbc.dsp" - Package Owner=<4> Package=<5> {{{ @@ -57,10 +60,10 @@ Package=<4> {{{ Begin Project Dependency - Project_Dep_Name libapr + Project_Dep_Name libaprutil End Project Dependency Begin Project Dependency - Project_Dep_Name libaprutil + Project_Dep_Name libapr End Project Dependency }}} @@ -156,6 +159,21 @@ ############################################################################### +Project: "aprapp"="..\apr\build\aprapp.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name preaprapp + End Project Dependency +}}} + +############################################################################### + Project: "apriconv"="..\apr-iconv\apriconv.dsp" - Package Owner=<4> Package=<5> Modified: apr/apr-util/trunk/dbd/apr_dbd.c URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/dbd/apr_dbd.c?rev=667182&r1=667181&r2=667182&view=diff ============================================================================== --- apr/apr-util/trunk/dbd/apr_dbd.c (original) +++ apr/apr-util/trunk/dbd/apr_dbd.c Thu Jun 12 10:54:15 2008 @@ -97,10 +97,10 @@ /* Top level pool scope, need process-scope lifetime */ for (parent = pool; parent; parent = apr_pool_parent_get(pool)) pool = parent; - +#if APU_DSO_BUILD /* deprecate in 2.0 - permit implicit initialization */ apu_dso_init(pool); - +#endif drivers = apr_hash_make(pool); #if APR_HAS_THREADS @@ -146,14 +146,17 @@ #endif apr_status_t rv; +#if APU_DSO_BUILD rv = apu_dso_mutex_lock(); if (rv) { return rv; } - +#endif *driver = apr_hash_get(drivers, name, APR_HASH_KEY_STRING); if (*driver) { +#if APU_DSO_BUILD apu_dso_mutex_unlock(); +#endif return APR_SUCCESS; } Added: apr/apr-util/trunk/dbd/apr_dbd_odbc.c URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/dbd/apr_dbd_odbc.c?rev=667182&view=auto ============================================================================== --- apr/apr-util/trunk/dbd/apr_dbd_odbc.c (added) +++ apr/apr-util/trunk/dbd/apr_dbd_odbc.c Thu Jun 12 10:54:15 2008 @@ -0,0 +1,1615 @@ +/* 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" + +/* 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 */ +#include +#include + + /* 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_internal.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 */ +}; + +/* 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->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; + SQLHANDLE hstmt = statement->stmt; + /* stmt is closed if connection has already been closed */ + 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; + SQLUINTEGER len; + SQLINTEGER *indicator; + static SQLINTEGER 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 { + baseType = sqlBaseType[type]; + cType = sqlCtype[type]; + indicator = NULL; + /* LOBs */ + if (IS_LOB(cType)) { + ptr = (void *) args[*argp]; + len = (SQLUINTEGER) * (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 = (SQLUINTEGER) 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; + SQLINTEGER 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 >= (SQLINTEGER) e->start) + ? (len_indicator - (SQLINTEGER) 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; + SQLINTEGER 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, *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); + do { + if (last[strspn(last, seps)] == CSINGLEQUOTE) { + last += strspn(last, seps); + seps=SSINGLEQUOTE; + } + val[nparams] = apr_strtok(NULL, seps, &last); + seps = DEFAULTSEPS; + name[++nparams] = apr_strtok(NULL, seps, &last); + } while ( nparams <= MAX_PARAMS && name[nparams] != NULL + && val[nparams] != NULL); + + for(j=i=0 ; i< nparams ; i++) + { if (!apr_strnatcasecmp(name[i], "CONNECT")) + { *datasource = apr_pstrdup(pool, val[i]); + *connect=1; + } + else if (!apr_strnatcasecmp(name[i], "DATASOURCE")) + { *datasource = apr_pstrdup(pool, val[i]); + *connect=0; + } + else if (!apr_strnatcasecmp(name[i], "USER")) + *user = apr_pstrdup(pool, val[i]); + else if (!apr_strnatcasecmp(name[i], "PASSWORD")) + *password = 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; + apr_status_t r; + + /* set info about last error in dbc - fast return for SQL_SUCCESS */ + if (rc == SQL_SUCCESS) { + char successMsg[] = "[dbd_odbc] SQL_SUCCESS "; + dbc->lasterrorcode = SQL_SUCCESS; + strcpy(dbc->lastError, successMsg); + strcpy(dbc->lastError+sizeof(successMsg)-1, step); + 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 = 0; + 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); + } + r = 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); + } +} + +/* +* 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="", *user="", *password=""; + 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], (void *) 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(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(datasource), + user, (SQLSMALLINT) strlen(user), + password, (SQLSMALLINT) strlen(password)); + } + } + if (SQL_SUCCEEDED(rc)) { + handle = apr_pcalloc(pool, sizeof(apr_dbd_t)); + handle->dbname = apr_pstrdup(pool, 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; + 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, (void *) + 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 = 1; + } + 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 ? SQL_COMMIT : SQL_ROLLBACK; + + rc = SQLEndTran(SQL_HANDLE_DBC, trans->dbc, SQL_COMMIT); + 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); + } + 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); + + 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) { + rc = SQLRowCount(hstmt, (SQLINTEGER *) nrows); + 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); + + 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; + SQLINTEGER 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 = sqlSizes[dbdtype]; + + if (col >= row->res->ncols) + return APR_EGENERAL; + + if (dbdtype < 0 || dbdtype >= sizeof(sqlCtype)) { + data = NULL; /* invalid type */ + return APR_EGENERAL; + } + 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) + strcpy(data, 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); + + if ((signed int) p > 0) + return apr_pstrdup(row->pool, p); /* row pool lifetime */ + else + return p; /* NULL or invalid (-1) */ +} + +/** 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); + + *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; + + 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 = SQLRowCount(statement->stmt, (SQLINTEGER *) nrows); + 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 **/ +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 (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; + SQLUINTEGER 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, + 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->transaction_mode; +} + +/** transaction_mode_set: set the mode of transaction **/ +static int odbc_transaction_mode_set(apr_dbd_transaction_t * trans, int mode) +{ + SQLRETURN rc; + + int legal = (SQL_TXN_READ_UNCOMMITTED | SQL_TXN_READ_COMMITTED + | SQL_TXN_REPEATABLE_READ | SQL_TXN_SERIALIZABLE); + + if ((mode & legal) != mode) + return APR_EGENERAL; + + trans->apr_dbd->transaction_mode = mode; + rc = SQLSetConnectAttr(trans->dbc, SQL_ATTR_TXN_ISOLATION, + (void *) mode, 0); + CHECK_ERROR(trans->apr_dbd, "SQLSetConnectAttr (SQL_ATTR_TXN_ISOLATION)", + rc, SQL_HANDLE_DBC, trans->dbc); + return APR_FROM_SQL_RESULT(rc); +} + +/** 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; + + 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 = SQLRowCount(statement->stmt, (SQLINTEGER *) nrows); + 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 (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 Added: apr/apr-util/trunk/dbd/apr_dbd_odbc.dsp URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/dbd/apr_dbd_odbc.dsp?rev=667182&view=auto ============================================================================== --- apr/apr-util/trunk/dbd/apr_dbd_odbc.dsp (added) +++ apr/apr-util/trunk/dbd/apr_dbd_odbc.dsp Thu Jun 12 10:54:15 2008 @@ -0,0 +1,112 @@ +# Microsoft Developer Studio Project File - Name="apr_dbd_odbc" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=apr_dbd_odbc - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "apr_dbd_odbc.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "apr_dbd_odbc.mak" CFG="apr_dbd_odbc - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "apr_dbd_odbc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "apr_dbd_odbc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "apr_dbd_odbc - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "APR_DBD_ODBC_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX- /Zi /O2 /I "../include" /I "../../apr/include" /I "../include/private" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "APU_DBD_DSO_BUILD" /D APU_HAVE_ODBC=1 /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"Release/apr_dbd_odbc-1.dll" + +!ELSEIF "$(CFG)" == "apr_dbd_odbc - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "APR_DBD_ODBC_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /Zi /Od /I "../include" /I "../../apr/include" /I "../include/private" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "APU_DBD_DSO_BUILD" /D APU_HAVE_ODBC=1 /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:no /debug /machine:I386 /out:"Debug/apr_dbd_odbc-1.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "apr_dbd_odbc - Win32 Release" +# Name "apr_dbd_odbc - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\apr_dbd_odbc.c +# End Source File +# End Group +# Begin Group "Public Header Files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=..\include\apr_dbd.h +# End Source File +# End Group +# Begin Group "Internal Header Files" + +# PROP Default_Filter ".h" +# End Group +# End Target +# End Project Modified: apr/apr-util/trunk/include/apu.hw URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/include/apu.hw?rev=667182&r1=667181&r2=667182&view=diff ============================================================================== --- apr/apr-util/trunk/include/apu.hw (original) +++ apr/apr-util/trunk/include/apu.hw Thu Jun 12 10:54:15 2008 @@ -116,6 +116,8 @@ #define APU_HAVE_ORACLE 0 #define APU_HAVE_FREETDS 0 #endif +/* Windows always has ODBC */ +#define APU_HAVE_ODBC 1 #define APU_HAVE_APR_ICONV 1 #define APU_HAVE_ICONV 0 Added: apr/apr-util/trunk/include/private/apr_dbd_odbc_v2.h URL: http://svn.apache.org/viewvc/apr/apr-util/trunk/include/private/apr_dbd_odbc_v2.h?rev=667182&view=auto ============================================================================== --- apr/apr-util/trunk/include/private/apr_dbd_odbc_v2.h (added) +++ apr/apr-util/trunk/include/private/apr_dbd_odbc_v2.h Thu Jun 12 10:54:15 2008 @@ -0,0 +1,119 @@ +/* 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. + */ + + +/* ONLY USED FOR ODBC Version 2 -DODBCV2 +* +* Re-define everything to work (more-or-less) in an ODBC V2 environment +* Random access to retrieved rows is not supported - i.e. calls to apr_dbd_select() cannot +* have a 'random' argument of 1. apr_dbd_get_row() must always pass rownum as 0 (get next row) +* +*/ + +#define SQLHANDLE SQLHENV // Presumes that ENV, DBC, and STMT handles are all the same datatype +#define SQL_NULL_HANDLE 0 +#define SQL_HANDLE_STMT 1 +#define SQL_HANDLE_DBC 2 +#define SQL_HANDLE_ENV 3 +#define SQL_NO_DATA SQL_NO_DATA_FOUND + +#ifndef SQL_SUCCEEDED +#define SQL_SUCCEEDED(rc) (((rc)&(~1))==0) +#endif + +#undef SQLSetEnvAttr +#define SQLSetEnvAttr(henv, Attribute, Value, StringLength) (0) + +#undef SQLAllocHandle +#define SQLAllocHandle(type, parent, hndl) \ +( (type == SQL_HANDLE_STMT) ? SQLAllocStmt(parent, hndl) \ + : (type == SQL_HANDLE_ENV) ? SQLAllocEnv(hndl) \ + : SQLAllocConnect(parent, hndl) \ +) + +#undef SQLFreeHandle +#define SQLFreeHandle(type, hndl) \ +( (type == SQL_HANDLE_STMT) ? SQLFreeStmt(hndl, SQL_DROP) \ + : (type == SQL_HANDLE_ENV) ? SQLFreeEnv(hndl) \ + : SQLFreeConnect(hndl) \ +) + +#undef SQLGetDiagRec +#define SQLGetDiagRec(type, h, i, state, native, buffer, bufsize, reslen) \ + SQLError( (type == SQL_HANDLE_ENV) ? h : NULL, \ + (type == SQL_HANDLE_DBC) ? h : NULL, \ + (type == SQL_HANDLE_STMT) ? h : NULL, \ + state, native, buffer, bufsize, reslen) + +#undef SQLCloseCursor +#define SQLCloseCursor(stmt) SQLFreeStmt(stmt, SQL_CLOSE) + +#undef SQLGetConnectAttr +#define SQLGetConnectAttr(hdbc, fOption, ValuePtr, BufferLength, NULL) \ + SQLGetConnectOption(hdbc, fOption, ValuePtr) + +#undef SQLSetConnectAttr +#define SQLSetConnectAttr(hdbc, fOption, ValuePtr, BufferLength) \ + SQLSetConnectOption(hdbc, fOption, (SQLUINTEGER) ValuePtr) + +#undef SQLSetStmtAttr +#define SQLSetStmtAttr(hstmt, fOption, ValuePtr, BufferLength) (0); return APR_ENOTIMPL; + +#undef SQLEndTran +#define SQLEndTran(hType, hdbc,type) SQLTransact(henv, hdbc, type) + +#undef SQLFetchScroll +#define SQLFetchScroll(stmt, orient, rownum) (0); return APR_ENOTIMPL; + +#define SQL_DESC_TYPE SQL_COLUMN_TYPE +#define SQL_DESC_CONCISE_TYPE SQL_COLUMN_TYPE +#define SQL_DESC_DISPLAY_SIZE SQL_COLUMN_DISPLAY_SIZE +#define SQL_DESC_OCTET_LENGTH SQL_COLUMN_LENGTH +#define SQL_DESC_UNSIGNED SQL_COLUMN_UNSIGNED + +#undef SQLColAttribute +#define SQLColAttribute(s, c, f, a, l, m, n) SQLColAttributes(s, c, f, a, l, m, n) + +#define SQL_ATTR_ACCESS_MODE SQL_ACCESS_MODE +#define SQL_ATTR_AUTOCOMMIT SQL_AUTOCOMMIT +#define SQL_ATTR_CONNECTION_TIMEOUT 113 +#define SQL_ATTR_CURRENT_CATALOG SQL_CURRENT_QUALIFIER +#define SQL_ATTR_DISCONNECT_BEHAVIOR 114 +#define SQL_ATTR_ENLIST_IN_DTC 1207 +#define SQL_ATTR_ENLIST_IN_XA 1208 + +#define SQL_ATTR_CONNECTION_DEAD 1209 +#define SQL_CD_TRUE 1L /* Connection is closed/dead */ +#define SQL_CD_FALSE 0L /* Connection is open/available */ + +#define SQL_ATTR_LOGIN_TIMEOUT SQL_LOGIN_TIMEOUT +#define SQL_ATTR_ODBC_CURSORS SQL_ODBC_CURSORS +#define SQL_ATTR_PACKET_SIZE SQL_PACKET_SIZE +#define SQL_ATTR_QUIET_MODE SQL_QUIET_MODE +#define SQL_ATTR_TRACE SQL_OPT_TRACE +#define SQL_ATTR_TRACEFILE SQL_OPT_TRACEFILE +#define SQL_ATTR_TRANSLATE_LIB SQL_TRANSLATE_DLL +#define SQL_ATTR_TRANSLATE_OPTION SQL_TRANSLATE_OPTION +#define SQL_ATTR_TXN_ISOLATION SQL_TXN_ISOLATION + +#define SQL_ATTR_CURSOR_SCROLLABLE -1 + +#define SQL_C_SBIGINT (SQL_BIGINT+SQL_SIGNED_OFFSET) /* SIGNED BIGINT */ +#define SQL_C_UBIGINT (SQL_BIGINT+SQL_UNSIGNED_OFFSET) /* UNSIGNED BIGINT */ + +#define SQL_FALSE 0 +#define SQL_TRUE 1 +