apr_dbd_odbc.c revision 289166
198186Sgordon/* Licensed to the Apache Software Foundation (ASF) under one or more
298186Sgordon * contributor license agreements.  See the NOTICE file distributed with
378344Sobrien * this work for additional information regarding copyright ownership.
498186Sgordon * The ASF licenses this file to You under the Apache License, Version 2.0
578344Sobrien * (the "License"); you may not use this file except in compliance with
678344Sobrien * the License.  You may obtain a copy of the License at
778344Sobrien *
878344Sobrien *     http://www.apache.org/licenses/LICENSE-2.0
978344Sobrien *
1078344Sobrien * Unless required by applicable law or agreed to in writing, software
1178344Sobrien * distributed under the License is distributed on an "AS IS" BASIS,
1278344Sobrien * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1378344Sobrien * See the License for the specific language governing permissions and
1478344Sobrien * limitations under the License.
1578344Sobrien */
1678344Sobrien
1778344Sobrien#include "apu.h"
1878344Sobrien#if APU_HAVE_ODBC
1978344Sobrien
2078344Sobrien#include "apr.h"
2178344Sobrien#include "apr_strings.h"
2278344Sobrien#include "apr_buckets.h"
2378344Sobrien#include "apr_env.h"
2478344Sobrien#include "apr_file_io.h"
2578344Sobrien#include "apr_file_info.h"
2678344Sobrien#include "apr_dbd_internal.h"
2778344Sobrien#include "apr_thread_proc.h"
2878344Sobrien#include "apu_version.h"
2978344Sobrien#include "apu_config.h"
3078344Sobrien
3178344Sobrien#include <stdlib.h>
3278344Sobrien
3378344Sobrien/* If library is ODBC-V2, use macros for limited ODBC-V2 support
3478344Sobrien * No random access in V2.
3578344Sobrien */
3678344Sobrien#ifdef ODBCV2
3778344Sobrien#define ODBCVER 0x0200
3878344Sobrien#include "apr_dbd_odbc_v2.h"
3978344Sobrien#endif
4078344Sobrien
4178344Sobrien/* standard ODBC include files */
4278344Sobrien#ifdef HAVE_SQL_H
4398186Sgordon#include <sql.h>
4498186Sgordon#include <sqlext.h>
4598186Sgordon#elif defined(HAVE_ODBC_SQL_H)
4698186Sgordon#include <odbc/sql.h>
4798186Sgordon#include <odbc/sqlext.h>
4898186Sgordon#endif
49103018Sgordon
50124832Smtm/*
51124832Smtm* MSVC6 does not support intptr_t (C99)
5298186Sgordon* APR does not have a signed inptr type until 2.0  (r1557720)
53103018Sgordon*/
5498186Sgordon#if defined(_MSC_VER) && _MSC_VER < 1400
5598186Sgordon#if APR_SIZEOF_VOIDP == 8
5698186Sgordon#define   ODBC_INTPTR_T  apr_int64_t
5798186Sgordon#else
5898186Sgordon#define   ODBC_INTPTR_T  apr_int32_t
5998186Sgordon#endif
6098186Sgordon#else
6198186Sgordon#define   ODBC_INTPTR_T  intptr_t
6298186Sgordon#endif
6378344Sobrien
6478344Sobrien
6578344Sobrien/* Driver name is "odbc" and the entry point is 'apr_dbd_odbc_driver'
6678344Sobrien * unless ODBC_DRIVER_NAME is defined and it is linked with another db library which
6798186Sgordon * is ODBC source-compatible. e.g. DB2, Informix, TimesTen, mysql.
6898186Sgordon */
6998186Sgordon#ifndef ODBC_DRIVER_NAME
7098186Sgordon#define ODBC_DRIVER_NAME odbc
7198186Sgordon#endif
7298186Sgordon#define STRINGIFY(x) #x
7398186Sgordon#define NAMIFY2(n) apr_dbd_##n##_driver
7498186Sgordon#define NAMIFY1(n) NAMIFY2(n)
7598186Sgordon#define ODBC_DRIVER_STRING STRINGIFY(ODBC_DRIVER_NAME)
7698186Sgordon#define ODBC_DRIVER_ENTRY NAMIFY1(ODBC_DRIVER_NAME)
7798186Sgordon
7898186Sgordon/* Required APR version for this driver */
7998186Sgordon#define DRIVER_APU_VERSION_MAJOR APU_MAJOR_VERSION
8098186Sgordon#define DRIVER_APU_VERSION_MINOR APU_MINOR_VERSION
8198186Sgordon
8298186Sgordonstatic SQLHANDLE henv = NULL;           /* ODBC ENV handle is process-wide */
8398186Sgordon
84103018Sgordon/* Use a CHECK_ERROR macro so we can grab the source line numbers
8598186Sgordon * for error reports
8698186Sgordon */
8798186Sgordonstatic void check_error(apr_dbd_t *a, const char *step, SQLRETURN rc,
8898186Sgordon                 SQLSMALLINT type, SQLHANDLE h, int line);
8998186Sgordon#define CHECK_ERROR(a,s,r,t,h)  check_error(a,s,r,t,h, __LINE__)
9098186Sgordon
9198186Sgordon#define SOURCE_FILE __FILE__            /* source file for error messages */
9298186Sgordon#define MAX_ERROR_STRING 1024           /* max length of message in dbc */
9398186Sgordon#define MAX_COLUMN_NAME 256             /* longest column name recognized */
9498186Sgordon#define DEFAULT_BUFFER_SIZE 1024        /* value for defaultBufferSize */
9598186Sgordon
9698186Sgordon#define MAX_PARAMS  20
9798186Sgordon#define DEFAULTSEPS " \t\r\n,="
9898186Sgordon#define CSINGLEQUOTE '\''
9998186Sgordon#define SSINGLEQUOTE "\'"
10098186Sgordon
10198186Sgordon#define TEXTMODE 1              /* used for text (APR 1.2) mode params */
10298186Sgordon#define BINARYMODE 0            /* used for binary (APR 1.3+) mode params */
10398186Sgordon
10498186Sgordon/* Identify datatypes which are LOBs
10598186Sgordon * - DB2 DRDA driver uses undefined types -98 and -99 for CLOB & BLOB
10698186Sgordon */
10798186Sgordon#define IS_LOB(t)  (t == SQL_LONGVARCHAR \
10898186Sgordon     || t == SQL_LONGVARBINARY || t == SQL_VARBINARY \
10998186Sgordon     || t == -98 || t == -99)
11098186Sgordon
11198186Sgordon/* These types are CLOBs
11298186Sgordon * - DB2 DRDA driver uses undefined type -98 for CLOB
11398186Sgordon */
11498186Sgordon#define IS_CLOB(t) \
11598186Sgordon    (t == SQL_LONGVARCHAR || t == -98)
11698186Sgordon
11778344Sobrien/* Convert a SQL result to an APR result */
11878344Sobrien#define APR_FROM_SQL_RESULT(rc) \
11978344Sobrien    (SQL_SUCCEEDED(rc) ? APR_SUCCESS : APR_EGENERAL)
12078344Sobrien
12178344Sobrien/* DBD opaque structures */
12278344Sobrienstruct apr_dbd_t
12378344Sobrien{
12498186Sgordon    SQLHANDLE dbc;              /* SQL connection handle - NULL after close */
12578344Sobrien    apr_pool_t *pool;           /* connection lifetime pool */
12678344Sobrien    char *dbname;               /* ODBC datasource */
12778344Sobrien    int lasterrorcode;
12878344Sobrien    int lineNumber;
12978344Sobrien    char lastError[MAX_ERROR_STRING];
13078344Sobrien    int defaultBufferSize;      /* used for CLOBs in text mode,
13178344Sobrien                                 * and when fld size is indeterminate */
13278344Sobrien    ODBC_INTPTR_T transaction_mode;
13378344Sobrien    ODBC_INTPTR_T dboptions;         /* driver options re SQLGetData */
13478344Sobrien    ODBC_INTPTR_T default_transaction_mode;
13578344Sobrien    int can_commit;             /* controls end_trans behavior */
13678344Sobrien};
137106643Sgordon
13878344Sobrienstruct apr_dbd_results_t
13978344Sobrien{
14078344Sobrien    SQLHANDLE stmt;             /* parent sql statement handle */
14178344Sobrien    SQLHANDLE dbc;              /* parent sql connection handle */
14278344Sobrien    apr_pool_t *pool;           /* pool from query or select */
14398186Sgordon    apr_dbd_t *apr_dbd;         /* parent DBD connection handle */
14498186Sgordon    int random;                 /* random access requested */
14578344Sobrien    int ncols;                  /* number of columns */
14698186Sgordon    int isclosed;               /* cursor has been closed */
14798186Sgordon    char **colnames;            /* array of column names (NULL until used) */
14898186Sgordon    SQLPOINTER *colptrs;        /* pointers to column data */
149126286Smtm    SQLINTEGER *colsizes;       /* sizes for columns (enough for txt or bin) */
15098186Sgordon    SQLINTEGER *coltextsizes;   /* max-sizes if converted to text */
15198186Sgordon    SQLSMALLINT *coltypes;      /* array of SQL data types for columns */
15298186Sgordon    SQLLEN *colinds;            /* array of SQL data indicator/strlens */
15398186Sgordon    int *colstate;              /* array of column states
15498186Sgordon                                 * - avail, bound, present, unavail
15578344Sobrien                                 */
15698186Sgordon    int *all_data_fetched;      /* flags data as all fetched, for LOBs  */
15798186Sgordon    void *data;                 /* buffer for all data for one row */
15898186Sgordon};
15998186Sgordon
16098186Sgordonenum                            /* results column states */
16178344Sobrien{
16278344Sobrien    COL_AVAIL,                  /* data may be retrieved with SQLGetData */
16398186Sgordon    COL_PRESENT,                /* data has been retrieved with SQLGetData */
16478344Sobrien    COL_BOUND,                  /* column is bound to colptr */
16578344Sobrien    COL_RETRIEVED,              /* all data from column has been returned */
166126285Smtm    COL_UNAVAIL                 /* column is unavailable because ODBC driver
16778344Sobrien                                 *  requires that columns be retrieved
16878344Sobrien                                 *  in ascending order and a higher col
169126285Smtm                                 *  was accessed
17078344Sobrien                                 */
17178344Sobrien};
172126285Smtm
173126285Smtmstruct apr_dbd_row_t {
174126285Smtm    SQLHANDLE stmt;             /* parent ODBC statement handle */
17578344Sobrien    SQLHANDLE dbc;              /* parent ODBC connection handle */
17678344Sobrien    apr_pool_t *pool;           /* pool from get_row */
17798186Sgordon    apr_dbd_results_t *res;
17878344Sobrien};
17978344Sobrien
18078344Sobrienstruct apr_dbd_transaction_t {
18178344Sobrien    SQLHANDLE dbc;              /* parent ODBC connection handle */
18298186Sgordon    apr_dbd_t *apr_dbd;         /* parent DBD connection handle */
18398186Sgordon};
18478344Sobrien
18598186Sgordonstruct apr_dbd_prepared_t {
18698186Sgordon    SQLHANDLE stmt;             /* ODBC statement handle */
18778344Sobrien    SQLHANDLE dbc;              /* parent ODBC connection handle */
18878344Sobrien    apr_dbd_t *apr_dbd;
18978344Sobrien    int nargs;
19078344Sobrien    int nvals;
19178344Sobrien    int *types;                 /* array of DBD data types */
19298186Sgordon};
19378344Sobrien
19498186Sgordonstatic void odbc_lob_bucket_destroy(void *data);
19578344Sobrienstatic apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool);
19678344Sobrienstatic apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
19798186Sgordon                                         apr_size_t *len, apr_read_type_e block);
19878344Sobrien
19978344Sobrien/* the ODBC LOB bucket type */
20078344Sobrienstatic const apr_bucket_type_t odbc_bucket_type = {
20178344Sobrien    "ODBC_LOB", 5, APR_BUCKET_DATA,
20298186Sgordon    odbc_lob_bucket_destroy,
20378344Sobrien    odbc_lob_bucket_read,
20478344Sobrien    odbc_lob_bucket_setaside,
20598186Sgordon    apr_bucket_shared_split,
20678344Sobrien    apr_bucket_shared_copy
20778344Sobrien};
20878344Sobrien
20998186Sgordon/* ODBC LOB bucket data */
21078344Sobrientypedef struct {
21198186Sgordon    /** Ref count for shared bucket */
21298186Sgordon    apr_bucket_refcount  refcount;
21378344Sobrien    const apr_dbd_row_t *row;
21478344Sobrien    int col;
21578344Sobrien    SQLSMALLINT type;
21678344Sobrien} odbc_bucket;
21798186Sgordon
21878344Sobrien/* SQL datatype mappings to DBD datatypes
21998186Sgordon * These tables must correspond *exactly* to the apr_dbd_type_e enum
22078344Sobrien * in apr_dbd.h
22198186Sgordon */
22298186Sgordon
22398186Sgordon/* ODBC "C" types to DBD datatypes  */
22498186Sgordonstatic SQLSMALLINT const sqlCtype[] = {
22598186Sgordon    SQL_C_DEFAULT,                  /* APR_DBD_TYPE_NONE              */
22698186Sgordon    SQL_C_STINYINT,                 /* APR_DBD_TYPE_TINY,       \%hhd */
22798186Sgordon    SQL_C_UTINYINT,                 /* APR_DBD_TYPE_UTINY,      \%hhu */
22898186Sgordon    SQL_C_SSHORT,                   /* APR_DBD_TYPE_SHORT,      \%hd  */
22998186Sgordon    SQL_C_USHORT,                   /* APR_DBD_TYPE_USHORT,     \%hu  */
23098186Sgordon    SQL_C_SLONG,                    /* APR_DBD_TYPE_INT,        \%d   */
23198186Sgordon    SQL_C_ULONG,                    /* APR_DBD_TYPE_UINT,       \%u   */
23298186Sgordon    SQL_C_SLONG,                    /* APR_DBD_TYPE_LONG,       \%ld  */
23398186Sgordon    SQL_C_ULONG,                    /* APR_DBD_TYPE_ULONG,      \%lu  */
23498186Sgordon    SQL_C_SBIGINT,                  /* APR_DBD_TYPE_LONGLONG,   \%lld */
23598186Sgordon    SQL_C_UBIGINT,                  /* APR_DBD_TYPE_ULONGLONG,  \%llu */
23698186Sgordon    SQL_C_FLOAT,                    /* APR_DBD_TYPE_FLOAT,      \%f   */
23798186Sgordon    SQL_C_DOUBLE,                   /* APR_DBD_TYPE_DOUBLE,     \%lf  */
23898186Sgordon    SQL_C_CHAR,                     /* APR_DBD_TYPE_STRING,     \%s   */
23998186Sgordon    SQL_C_CHAR,                     /* APR_DBD_TYPE_TEXT,       \%pDt */
24098186Sgordon    SQL_C_CHAR, /*SQL_C_TYPE_TIME,      APR_DBD_TYPE_TIME,       \%pDi */
24198186Sgordon    SQL_C_CHAR, /*SQL_C_TYPE_DATE,      APR_DBD_TYPE_DATE,       \%pDd */
24298186Sgordon    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_DATETIME,   \%pDa */
24398186Sgordon    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_TIMESTAMP,  \%pDs */
24498186Sgordon    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
24598186Sgordon    SQL_LONGVARBINARY,              /* APR_DBD_TYPE_BLOB,       \%pDb */
24698186Sgordon    SQL_LONGVARCHAR,                /* APR_DBD_TYPE_CLOB,       \%pDc */
24798186Sgordon    SQL_TYPE_NULL                   /* APR_DBD_TYPE_NULL        \%pDn */
24898186Sgordon};
24998186Sgordon#define NUM_APR_DBD_TYPES (sizeof(sqlCtype) / sizeof(sqlCtype[0]))
25078344Sobrien
25198186Sgordon/*  ODBC Base types to DBD datatypes */
25298186Sgordonstatic SQLSMALLINT const sqlBaseType[] = {
25398186Sgordon    SQL_C_DEFAULT,              /* APR_DBD_TYPE_NONE              */
25498186Sgordon    SQL_TINYINT,                /* APR_DBD_TYPE_TINY,       \%hhd */
25598186Sgordon    SQL_TINYINT,                /* APR_DBD_TYPE_UTINY,      \%hhu */
25698186Sgordon    SQL_SMALLINT,               /* APR_DBD_TYPE_SHORT,      \%hd  */
25778344Sobrien    SQL_SMALLINT,               /* APR_DBD_TYPE_USHORT,     \%hu  */
25898186Sgordon    SQL_INTEGER,                /* APR_DBD_TYPE_INT,        \%d   */
25998186Sgordon    SQL_INTEGER,                /* APR_DBD_TYPE_UINT,       \%u   */
26098186Sgordon    SQL_INTEGER,                /* APR_DBD_TYPE_LONG,       \%ld  */
26198186Sgordon    SQL_INTEGER,                /* APR_DBD_TYPE_ULONG,      \%lu  */
26298186Sgordon    SQL_BIGINT,                 /* APR_DBD_TYPE_LONGLONG,   \%lld */
26398186Sgordon    SQL_BIGINT,                 /* APR_DBD_TYPE_ULONGLONG,  \%llu */
26498186Sgordon    SQL_FLOAT,                  /* APR_DBD_TYPE_FLOAT,      \%f   */
26598186Sgordon    SQL_DOUBLE,                 /* APR_DBD_TYPE_DOUBLE,     \%lf  */
26698186Sgordon    SQL_CHAR,                   /* APR_DBD_TYPE_STRING,     \%s   */
26798186Sgordon    SQL_CHAR,                   /* APR_DBD_TYPE_TEXT,       \%pDt */
26898186Sgordon    SQL_CHAR, /*SQL_TIME,          APR_DBD_TYPE_TIME,       \%pDi */
26998186Sgordon    SQL_CHAR, /*SQL_DATE,          APR_DBD_TYPE_DATE,       \%pDd */
27098186Sgordon    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_DATETIME,   \%pDa */
27198186Sgordon    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_TIMESTAMP,  \%pDs */
27298186Sgordon    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
27398186Sgordon    SQL_LONGVARBINARY,          /* APR_DBD_TYPE_BLOB,       \%pDb */
27498186Sgordon    SQL_LONGVARCHAR,            /* APR_DBD_TYPE_CLOB,       \%pDc */
27598186Sgordon    SQL_TYPE_NULL               /* APR_DBD_TYPE_NULL        \%pDn */
27698186Sgordon};
27798186Sgordon
27898186Sgordon/*  result sizes for DBD datatypes (-1 for null-terminated) */
27998186Sgordonstatic int const sqlSizes[] = {
28098186Sgordon    0,
28198186Sgordon    sizeof(char),               /**< \%hhd out: char* */
282114272Smtm    sizeof(unsigned char),      /**< \%hhu out: unsigned char* */
28398186Sgordon    sizeof(short),              /**< \%hd  out: short* */
28498186Sgordon    sizeof(unsigned short),     /**< \%hu  out: unsigned short* */
28598186Sgordon    sizeof(int),                /**< \%d   out: int* */
28698186Sgordon    sizeof(unsigned int),       /**< \%u   out: unsigned int* */
28798186Sgordon    sizeof(long),               /**< \%ld  out: long* */
28898186Sgordon    sizeof(unsigned long),      /**< \%lu  out: unsigned long* */
28998186Sgordon    sizeof(apr_int64_t),        /**< \%lld out: apr_int64_t* */
29098186Sgordon    sizeof(apr_uint64_t),       /**< \%llu out: apr_uint64_t* */
29198186Sgordon    sizeof(float),              /**< \%f   out: float* */
292126286Smtm    sizeof(double),             /**< \%lf  out: double* */
29398186Sgordon    -1,                         /**< \%s   out: char** */
29498186Sgordon    -1,                         /**< \%pDt out: char** */
29598186Sgordon    -1,                         /**< \%pDi out: char** */
29698186Sgordon    -1,                         /**< \%pDd out: char** */
29798186Sgordon    -1,                         /**< \%pDa out: char** */
29898186Sgordon    -1,                         /**< \%pDs out: char** */
29998186Sgordon    -1,                         /**< \%pDz out: char** */
30098186Sgordon    sizeof(apr_bucket_brigade), /**< \%pDb out: apr_bucket_brigade* */
30198186Sgordon    sizeof(apr_bucket_brigade), /**< \%pDc out: apr_bucket_brigade* */
30298186Sgordon    0                           /**< \%pDn : in: void*, out: void** */
30398186Sgordon};
30498186Sgordon
30598186Sgordon/*
30678344Sobrien * local functions
30798186Sgordon */
30898186Sgordon
30998186Sgordon/* close any open results for the connection */
31098186Sgordonstatic apr_status_t odbc_close_results(void *d)
31178344Sobrien{
31298186Sgordon    apr_dbd_results_t *dbr = (apr_dbd_results_t *)d;
31398186Sgordon    SQLRETURN rc = SQL_SUCCESS;
31498186Sgordon
31578344Sobrien    if (dbr && dbr->apr_dbd && dbr->apr_dbd->dbc) {
31678344Sobrien    	if (!dbr->isclosed)
31778344Sobrien            rc = SQLCloseCursor(dbr->stmt);
31898186Sgordon    	dbr->isclosed = 1;
31998186Sgordon    }
32098186Sgordon    return APR_FROM_SQL_RESULT(rc);
32198186Sgordon}
32298186Sgordon
32378344Sobrien/* close the ODBC statement handle from a  prepare */
32498186Sgordonstatic apr_status_t odbc_close_pstmt(void *s)
32598186Sgordon{
32678344Sobrien    SQLRETURN rc = APR_SUCCESS;
32798186Sgordon    apr_dbd_prepared_t *statement = s;
32898186Sgordon
32978344Sobrien    /* stmt is closed if connection has already been closed */
33078344Sobrien    if (statement) {
33178344Sobrien        SQLHANDLE hstmt = statement->stmt;
33298186Sgordon
33398186Sgordon        if (hstmt && statement->apr_dbd && statement->apr_dbd->dbc) {
33478344Sobrien            rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
33578344Sobrien        }
33678344Sobrien        statement->stmt = NULL;
33798186Sgordon    }
33878344Sobrien    return APR_FROM_SQL_RESULT(rc);
33978344Sobrien}
34078344Sobrien
34178344Sobrien/* close: close/release a connection obtained from open() */
34298186Sgordonstatic apr_status_t odbc_close(apr_dbd_t *handle)
34398186Sgordon{
34498186Sgordon    SQLRETURN rc = SQL_SUCCESS;
34578344Sobrien
34678344Sobrien    if (handle->dbc) {
34798186Sgordon        rc = SQLDisconnect(handle->dbc);
34898186Sgordon        CHECK_ERROR(handle, "SQLDisconnect", rc, SQL_HANDLE_DBC, handle->dbc);
34998186Sgordon        rc = SQLFreeHandle(SQL_HANDLE_DBC, handle->dbc);
35078344Sobrien        CHECK_ERROR(handle, "SQLFreeHandle (DBC)", rc, SQL_HANDLE_ENV, henv);
35198186Sgordon        handle->dbc = NULL;
35298186Sgordon    }
35378344Sobrien    return APR_FROM_SQL_RESULT(rc);
35478344Sobrien}
35578344Sobrien
35678344Sobrien/* odbc_close re-defined for passing to pool cleanup */
35798186Sgordonstatic apr_status_t odbc_close_cleanup(void *handle)
35878344Sobrien{
35978344Sobrien    return odbc_close((apr_dbd_t *)handle);
36078344Sobrien}
36178344Sobrien
36278344Sobrien/* close the ODBC environment handle at process termination */
36378344Sobrienstatic apr_status_t odbc_close_env(SQLHANDLE henv)
36478344Sobrien{
36578344Sobrien    SQLRETURN rc;
36678344Sobrien
36778344Sobrien    rc = SQLFreeHandle(SQL_HANDLE_ENV, henv);
36878344Sobrien    henv = NULL;
36978344Sobrien    return APR_FROM_SQL_RESULT(rc);
37098186Sgordon}
37178344Sobrien
37278344Sobrien/* setup the arrays in results for all the returned columns */
37398186Sgordonstatic SQLRETURN odbc_set_result_column(int icol, apr_dbd_results_t *res,
37478344Sobrien                                        SQLHANDLE stmt)
37598186Sgordon{
37698186Sgordon    SQLRETURN rc;
37798186Sgordon    ODBC_INTPTR_T maxsize, textsize, realsize, type, isunsigned = 1;
37878344Sobrien
37998186Sgordon    /* discover the sql type */
38078344Sobrien    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_UNSIGNED, NULL, 0, NULL,
38178344Sobrien                         (SQLPOINTER)&isunsigned);
38298186Sgordon    isunsigned = (isunsigned == SQL_TRUE);
38398186Sgordon
38498186Sgordon    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_TYPE, NULL, 0, NULL,
38598186Sgordon                         (SQLPOINTER)&type);
38678344Sobrien    if (!SQL_SUCCEEDED(rc) || type == SQL_UNKNOWN_TYPE) {
38798186Sgordon        /* MANY ODBC v2 datasources only supply CONCISE_TYPE */
38878344Sobrien        rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_CONCISE_TYPE, NULL,
38998186Sgordon                             0, NULL, (SQLPOINTER)&type);
39098186Sgordon    }
39198186Sgordon
39298186Sgordon    if (!SQL_SUCCEEDED(rc)) {
39378344Sobrien        /* if still unknown make it CHAR */
39478344Sobrien        type = SQL_C_CHAR;
39578344Sobrien    }
39678344Sobrien
39778344Sobrien    switch (type) {
39878344Sobrien    case SQL_INTEGER:
39978344Sobrien    case SQL_SMALLINT:
40078344Sobrien    case SQL_TINYINT:
40178344Sobrien    case SQL_BIGINT:
40278344Sobrien      /* fix these numeric binary types up as signed/unsigned for C types */
40378344Sobrien      type += (isunsigned) ? SQL_UNSIGNED_OFFSET : SQL_SIGNED_OFFSET;
40478344Sobrien      break;
40598186Sgordon    /* LOB types are not changed to C types */
40698186Sgordon    case SQL_LONGVARCHAR:
40778344Sobrien        type = SQL_LONGVARCHAR;
40898186Sgordon        break;
40998186Sgordon    case SQL_LONGVARBINARY:
41078344Sobrien        type = SQL_LONGVARBINARY;
41178344Sobrien        break;
41278344Sobrien    case SQL_FLOAT :
41378344Sobrien        type = SQL_C_FLOAT;
41498186Sgordon        break;
41578344Sobrien    case SQL_DOUBLE :
41698186Sgordon        type = SQL_C_DOUBLE;
41798186Sgordon        break;
41898186Sgordon
41998186Sgordon    /* DBD wants times as strings */
42078344Sobrien    case SQL_TIMESTAMP:
42198186Sgordon    case SQL_DATE:
42298186Sgordon    case SQL_TIME:
42378344Sobrien    default:
42478344Sobrien      type = SQL_C_CHAR;
42578344Sobrien    }
42678344Sobrien
42798186Sgordon    res->coltypes[icol] = (SQLSMALLINT)type;
42878344Sobrien
42998186Sgordon    /* size if retrieved as text */
43098186Sgordon    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0,
43198186Sgordon                         NULL, (SQLPOINTER)&textsize);
43298186Sgordon    if (!SQL_SUCCEEDED(rc) || textsize < 0) {
43398186Sgordon        textsize = res->apr_dbd->defaultBufferSize;
43498186Sgordon    }
43598186Sgordon    /* for null-term, which sometimes isn't included */
43698186Sgordon    textsize++;
43798186Sgordon
43898186Sgordon    /* real size */
43998186Sgordon    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_OCTET_LENGTH, NULL, 0,
44098186Sgordon                         NULL, (SQLPOINTER)&realsize);
44198186Sgordon    if (!SQL_SUCCEEDED(rc)) {
44298186Sgordon        realsize = textsize;
44398186Sgordon    }
44498186Sgordon
44598186Sgordon    maxsize = (textsize > realsize) ? textsize : realsize;
44698186Sgordon    if (IS_LOB(type) || maxsize <= 0) {
44798186Sgordon        /* LOB types are never bound and have a NULL colptr for binary.
44898186Sgordon         * Ingore their real (1-2gb) length & use a default - the larger
44998186Sgordon         * of defaultBufferSize or APR_BUCKET_BUFF_SIZE.
45098186Sgordon         * If not a LOB, but simply unknown length - always use defaultBufferSize.
45198186Sgordon         */
45298186Sgordon        maxsize = res->apr_dbd->defaultBufferSize;
45378344Sobrien        if (IS_LOB(type) && maxsize < APR_BUCKET_BUFF_SIZE) {
45478344Sobrien            maxsize = APR_BUCKET_BUFF_SIZE;
455116097Smtm        }
45698186Sgordon
45778344Sobrien        res->colptrs[icol] =  NULL;
45898186Sgordon        res->colstate[icol] = COL_AVAIL;
45978344Sobrien        res->colsizes[icol] = (SQLINTEGER)maxsize;
46078344Sobrien        rc = SQL_SUCCESS;
46198186Sgordon    }
46278344Sobrien    else {
46398186Sgordon        res->colptrs[icol] = apr_pcalloc(res->pool, maxsize);
46498186Sgordon        res->colsizes[icol] = (SQLINTEGER)maxsize;
46578344Sobrien        if (res->apr_dbd->dboptions & SQL_GD_BOUND) {
46678344Sobrien            /* we are allowed to call SQLGetData if we need to */
46798186Sgordon            rc = SQLBindCol(stmt, icol + 1, res->coltypes[icol],
46898186Sgordon                            res->colptrs[icol], maxsize,
46978344Sobrien                            &(res->colinds[icol]));
47078344Sobrien            CHECK_ERROR(res->apr_dbd, "SQLBindCol", rc, SQL_HANDLE_STMT,
47178344Sobrien                        stmt);
47278344Sobrien            res->colstate[icol] = SQL_SUCCEEDED(rc) ? COL_BOUND : COL_AVAIL;
47378344Sobrien        }
47478344Sobrien        else {
47598186Sgordon            /* this driver won't allow us to call SQLGetData on bound
47698186Sgordon             * columns - so don't bind any
47798186Sgordon             */
47898186Sgordon            res->colstate[icol] = COL_AVAIL;
47998186Sgordon            rc = SQL_SUCCESS;
48078344Sobrien        }
48198186Sgordon    }
48278344Sobrien    return rc;
48398186Sgordon}
48498186Sgordon
48578344Sobrien/* create and populate an apr_dbd_results_t for a select */
48698186Sgordonstatic SQLRETURN odbc_create_results(apr_dbd_t *handle, SQLHANDLE hstmt,
48778344Sobrien                                     apr_pool_t *pool, const int random,
48898186Sgordon                                     apr_dbd_results_t **res)
48998186Sgordon{
49098186Sgordon    SQLRETURN rc;
49178344Sobrien    SQLSMALLINT ncols;
49278344Sobrien
49398186Sgordon    *res = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
49478344Sobrien    (*res)->stmt = hstmt;
49578344Sobrien    (*res)->dbc = handle->dbc;
49678344Sobrien    (*res)->pool = pool;
49798186Sgordon    (*res)->random = random;
49878344Sobrien    (*res)->apr_dbd = handle;
49978344Sobrien    rc = SQLNumResultCols(hstmt, &ncols);
50078344Sobrien    CHECK_ERROR(handle, "SQLNumResultCols", rc, SQL_HANDLE_STMT, hstmt);
50178344Sobrien    (*res)->ncols = ncols;
50298186Sgordon
50378344Sobrien    if (SQL_SUCCEEDED(rc)) {
50498186Sgordon        int i;
50578344Sobrien
50698186Sgordon        (*res)->colnames = apr_pcalloc(pool, ncols * sizeof(char *));
50798186Sgordon        (*res)->colptrs = apr_pcalloc(pool, ncols * sizeof(void *));
50898186Sgordon        (*res)->colsizes = apr_pcalloc(pool, ncols * sizeof(SQLINTEGER));
50978344Sobrien        (*res)->coltypes = apr_pcalloc(pool, ncols * sizeof(SQLSMALLINT));
51098186Sgordon        (*res)->colinds = apr_pcalloc(pool, ncols * sizeof(SQLLEN));
511124832Smtm        (*res)->colstate = apr_pcalloc(pool, ncols * sizeof(int));
51298186Sgordon        (*res)->ncols = ncols;
51398186Sgordon
51498186Sgordon        for (i = 0; i < ncols; i++) {
51598186Sgordon            odbc_set_result_column(i, (*res), hstmt);
51678344Sobrien        }
51798186Sgordon    }
51878344Sobrien    return rc;
51978344Sobrien}
52078344Sobrien
52198186Sgordon
52278344Sobrien/* bind a parameter - input params only, does not support output parameters */
52378344Sobrienstatic SQLRETURN odbc_bind_param(apr_pool_t *pool,
52478344Sobrien                                 apr_dbd_prepared_t *statement, const int narg,
52578344Sobrien                                 const SQLSMALLINT type, int *argp,
52678344Sobrien                                 const void **args, const int textmode)
52778344Sobrien{
52878344Sobrien    SQLRETURN rc;
52978344Sobrien    SQLSMALLINT baseType, cType;
53098186Sgordon    void *ptr;
53178344Sobrien    SQLULEN len;
53278344Sobrien    SQLLEN *indicator;
53378344Sobrien    static SQLLEN nullValue = SQL_NULL_DATA;
53478344Sobrien    static SQLSMALLINT inOut = SQL_PARAM_INPUT;     /* only input params */
53578344Sobrien
53678344Sobrien    /* bind a NULL data value */
53798186Sgordon    if (args[*argp] == NULL || type == APR_DBD_TYPE_NULL) {
53898186Sgordon        baseType = SQL_CHAR;
53978344Sobrien        cType = SQL_C_CHAR;
54078344Sobrien        ptr = &nullValue;
54178344Sobrien        len = sizeof(SQLINTEGER);
54278344Sobrien        indicator = &nullValue;
543116097Smtm        (*argp)++;
544116097Smtm    }
545116097Smtm    /* bind a non-NULL data value */
546116097Smtm    else {
547116097Smtm        if (type < 0 || type >= NUM_APR_DBD_TYPES) {
548116097Smtm            return APR_EGENERAL;
54978344Sobrien        }
55078344Sobrien
551116097Smtm        baseType = sqlBaseType[type];
552116097Smtm        cType = sqlCtype[type];
553116097Smtm        indicator = NULL;
554116097Smtm        /* LOBs */
555116097Smtm        if (IS_LOB(cType)) {
556116097Smtm            ptr = (void *)args[*argp];
55798186Sgordon            len = (SQLULEN) * (apr_size_t *)args[*argp + 1];
558109582Smtm            cType = (IS_CLOB(cType)) ? SQL_C_CHAR : SQL_C_DEFAULT;
559116097Smtm            (*argp) += 4;  /* LOBs consume 4 args (last two are unused) */
560116097Smtm        }
561116097Smtm        /* non-LOBs */
562116097Smtm        else {
563116097Smtm            switch (baseType) {
564116097Smtm            case SQL_CHAR:
56578344Sobrien            case SQL_DATE:
56678344Sobrien            case SQL_TIME:
56798186Sgordon            case SQL_TIMESTAMP:
56878344Sobrien                ptr = (void *)args[*argp];
56978344Sobrien                len = (SQLULEN)strlen(ptr);
57098186Sgordon                break;
57198186Sgordon            case SQL_TINYINT:
57278344Sobrien                ptr = apr_palloc(pool, sizeof(unsigned char));
57378344Sobrien                len = sizeof(unsigned char);
57478344Sobrien                *(unsigned char *)ptr =
57578344Sobrien                    (textmode ?
57678344Sobrien                     atoi(args[*argp]) : *(unsigned char *)args[*argp]);
57778344Sobrien                break;
57878344Sobrien            case SQL_SMALLINT:
57998186Sgordon                ptr = apr_palloc(pool, sizeof(short));
58098186Sgordon                len = sizeof(short);
58178344Sobrien                *(short *)ptr =
58278344Sobrien                    (textmode ? atoi(args[*argp]) : *(short *)args[*argp]);
58378344Sobrien                break;
58478344Sobrien            case SQL_INTEGER:
58598186Sgordon                ptr = apr_palloc(pool, sizeof(int));
58678344Sobrien                len = sizeof(int);
58778344Sobrien                *(long *)ptr =
58878344Sobrien                    (textmode ? atol(args[*argp]) : *(long *)args[*argp]);
58978344Sobrien                break;
59078344Sobrien            case SQL_FLOAT:
59178344Sobrien                ptr = apr_palloc(pool, sizeof(float));
59278344Sobrien                len = sizeof(float);
59378344Sobrien                *(float *)ptr =
59478344Sobrien                    (textmode ?
59598186Sgordon                     (float)atof(args[*argp]) : *(float *)args[*argp]);
59678344Sobrien                break;
59778344Sobrien            case SQL_DOUBLE:
59878344Sobrien                ptr = apr_palloc(pool, sizeof(double));
59978344Sobrien                len = sizeof(double);
60078344Sobrien                *(double *)ptr =
60178344Sobrien                    (textmode ? atof(args[*argp]) : *(double *)
60278344Sobrien                     args[*argp]);
60398186Sgordon                break;
60478344Sobrien            case SQL_BIGINT:
60578344Sobrien                ptr = apr_palloc(pool, sizeof(apr_int64_t));
60678344Sobrien                len = sizeof(apr_int64_t);
60778344Sobrien                *(apr_int64_t *)ptr =
60878344Sobrien                    (textmode ?
60978344Sobrien                     apr_atoi64(args[*argp]) : *(apr_int64_t *)args[*argp]);
61078344Sobrien                break;
61198186Sgordon            default:
61278344Sobrien                return APR_EGENERAL;
61378344Sobrien            }
61478344Sobrien            (*argp)++;          /* non LOBs consume one argument */
61578344Sobrien        }
61678344Sobrien    }
61778344Sobrien    rc = SQLBindParameter(statement->stmt, narg, inOut, cType,
61878344Sobrien                          baseType, len, 0, ptr, len, indicator);
61978344Sobrien    CHECK_ERROR(statement->apr_dbd, "SQLBindParameter", rc, SQL_HANDLE_STMT,
620116097Smtm                statement->stmt);
621116097Smtm    return rc;
622116097Smtm}
623116097Smtm
624116097Smtm/* LOB / Bucket Brigade functions */
625116097Smtm
62678344Sobrien/* bucket type specific destroy */
62778344Sobrienstatic void odbc_lob_bucket_destroy(void *data)
62878344Sobrien{
62978344Sobrien    odbc_bucket *bd = data;
63078344Sobrien
63178344Sobrien    if (apr_bucket_shared_destroy(bd))
63278344Sobrien        apr_bucket_free(bd);
63378344Sobrien}
63478344Sobrien
63598186Sgordon/* set aside a bucket if possible */
63678344Sobrienstatic apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool)
63778344Sobrien{
63878344Sobrien    odbc_bucket *bd = (odbc_bucket *)e->data;
63978344Sobrien
64098186Sgordon    /* Unlikely - but if the row pool is ancestor of this pool then it is OK */
64198186Sgordon    if (apr_pool_is_ancestor(bd->row->pool, pool))
64298186Sgordon        return APR_SUCCESS;
64398186Sgordon
64478344Sobrien    return apr_bucket_setaside_notimpl(e, pool);
64598186Sgordon}
64698186Sgordon
64798186Sgordon/* split a bucket into a heap bucket followed by a LOB bkt w/remaining data */
64898186Sgordonstatic apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
64998186Sgordon                                         apr_size_t *len, apr_read_type_e block)
650116097Smtm{
651116097Smtm    SQLRETURN rc;
652116097Smtm    SQLLEN len_indicator;
65398186Sgordon    SQLSMALLINT type;
65498186Sgordon    odbc_bucket *bd = (odbc_bucket *)e->data;
65598186Sgordon    apr_bucket *nxt;
656116097Smtm    void *buf;
657116097Smtm    int bufsize = bd->row->res->apr_dbd->defaultBufferSize;
658116097Smtm    int eos;
659116097Smtm
66078344Sobrien    /* C type is CHAR for CLOBs, DEFAULT for BLOBs */
66178344Sobrien    type = bd->row->res->coltypes[bd->col];
66278344Sobrien    type = (type == SQL_LONGVARCHAR) ? SQL_C_CHAR : SQL_C_DEFAULT;
66398186Sgordon
66478344Sobrien    /* LOB buffers are always at least APR_BUCKET_BUFF_SIZE,
66578344Sobrien     *   but they may be much bigger per the BUFSIZE parameter.
66678344Sobrien     */
66778344Sobrien    if (bufsize < APR_BUCKET_BUFF_SIZE)
66878344Sobrien        bufsize = APR_BUCKET_BUFF_SIZE;
66978344Sobrien
67078344Sobrien    buf = apr_bucket_alloc(bufsize, e->list);
67178344Sobrien    *str = NULL;
67278344Sobrien    *len = 0;
67398186Sgordon
67498186Sgordon    rc = SQLGetData(bd->row->res->stmt, bd->col + 1,
67598186Sgordon                    type, buf, bufsize,
676117977Smtm                    &len_indicator);
677116097Smtm
678116097Smtm    CHECK_ERROR(bd->row->res->apr_dbd, "SQLGetData", rc,
679116097Smtm                SQL_HANDLE_STMT, bd->row->res->stmt);
680116097Smtm
68178344Sobrien    if (rc == SQL_NO_DATA || len_indicator == SQL_NULL_DATA || len_indicator < 0)
68298186Sgordon        len_indicator = 0;
68398186Sgordon
68498186Sgordon    if (SQL_SUCCEEDED(rc) || rc == SQL_NO_DATA) {
68578344Sobrien
68698186Sgordon        if (rc == SQL_SUCCESS_WITH_INFO
68798186Sgordon            && (len_indicator == SQL_NO_TOTAL || len_indicator >= bufsize)) {
68898186Sgordon            /* not the last read = a full buffer. CLOBs have a null terminator */
68998186Sgordon            *len = bufsize - (IS_CLOB(bd->type) ? 1 : 0 );
69098186Sgordon
69198186Sgordon             eos = 0;
69298186Sgordon        }
69398186Sgordon        else {
694116097Smtm            /* the last read - len_indicator is supposed to be the length,
695116097Smtm             * but some driver get this wrong and return the total length.
696116097Smtm             * We try to handle both interpretations.
69798186Sgordon             */
69898186Sgordon            *len =  (len_indicator > bufsize
69998186Sgordon                     && len_indicator >= (SQLLEN)e->start)
70098186Sgordon                ? (len_indicator - (SQLLEN)e->start) : len_indicator;
701116097Smtm
702116097Smtm            eos = 1;
703116097Smtm        }
704116097Smtm
70578344Sobrien        if (!eos) {
70678344Sobrien            /* Create a new LOB bucket to append and append it */
70778344Sobrien            nxt = apr_bucket_alloc(sizeof(apr_bucket *), e->list);
70898186Sgordon            APR_BUCKET_INIT(nxt);
70978344Sobrien            nxt->length = -1;
71078344Sobrien            nxt->data   = e->data;
71178344Sobrien            nxt->type   = &odbc_bucket_type;
71278344Sobrien            nxt->free   = apr_bucket_free;
71378344Sobrien            nxt->list   = e->list;
71478344Sobrien            nxt->start  = e->start + *len;
71578344Sobrien            APR_BUCKET_INSERT_AFTER(e, nxt);
71678344Sobrien        }
71778344Sobrien        else {
718116097Smtm            odbc_lob_bucket_destroy(e->data);
719116097Smtm        }
720116097Smtm        /* make current bucket into a heap bucket */
721116097Smtm        apr_bucket_heap_make(e, buf, *len, apr_bucket_free);
722116097Smtm        *str = buf;
72378344Sobrien
72498186Sgordon        /* No data is success in this context */
72598186Sgordon        rc = SQL_SUCCESS;
72698186Sgordon    }
72798186Sgordon    return APR_FROM_SQL_RESULT(rc);
728116097Smtm}
729116097Smtm
730116097Smtm/* Create a bucket brigade on the row pool for a LOB column */
731116097Smtmstatic apr_status_t odbc_create_bucket(const apr_dbd_row_t *row, const int col,
732116097Smtm                                       SQLSMALLINT type, apr_bucket_brigade *bb)
733116097Smtm{
73498186Sgordon    apr_bucket_alloc_t *list = bb->bucket_alloc;
73578344Sobrien    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
73678344Sobrien    odbc_bucket *bd = apr_bucket_alloc(sizeof(odbc_bucket), list);
73778344Sobrien    apr_bucket *eos = apr_bucket_eos_create(list);
738116097Smtm
739116097Smtm    bd->row = row;
740116097Smtm    bd->col = col;
741116097Smtm    bd->type = type;
742116097Smtm
74378344Sobrien    APR_BUCKET_INIT(b);
74478344Sobrien    b->type = &odbc_bucket_type;
74578344Sobrien    b->free = apr_bucket_free;
74678344Sobrien    b->list = list;
747126285Smtm    /* LOB lengths are unknown in ODBC */
74878344Sobrien    b = apr_bucket_shared_make(b, bd, 0, -1);
74978344Sobrien
750126285Smtm    APR_BRIGADE_INSERT_TAIL(bb, b);
75178344Sobrien    APR_BRIGADE_INSERT_TAIL(bb, eos);
75298186Sgordon
75398186Sgordon    return APR_SUCCESS;
75498186Sgordon}
755116097Smtm
756116097Smtm/* returns a data pointer for a column,  returns NULL for NULL value,
757116097Smtm * return -1 if data not available
758116097Smtm */
75978344Sobrienstatic void *odbc_get(const apr_dbd_row_t *row, const int col,
76078344Sobrien                      const SQLSMALLINT sqltype)
76198186Sgordon{
76298186Sgordon    SQLRETURN rc;
76398186Sgordon    SQLLEN indicator;
76498186Sgordon    int state = row->res->colstate[col];
76598186Sgordon    ODBC_INTPTR_T options = row->res->apr_dbd->dboptions;
76698186Sgordon
76778344Sobrien    switch (state) {
76878344Sobrien    case (COL_UNAVAIL):
76978344Sobrien        return (void *)-1;
77078344Sobrien    case (COL_RETRIEVED):
77178344Sobrien        return NULL;
77278344Sobrien
77378344Sobrien    case (COL_BOUND):
77478344Sobrien    case (COL_PRESENT):
77578344Sobrien        if (sqltype == row->res->coltypes[col]) {
77678344Sobrien            /* same type and we already have the data */
77778344Sobrien            row->res->colstate[col] = COL_RETRIEVED;
77878344Sobrien            return (row->res->colinds[col] == SQL_NULL_DATA) ?
77978344Sobrien                NULL : row->res->colptrs[col];
78078344Sobrien        }
78178344Sobrien    }
78278344Sobrien
783116097Smtm    /* we need to get the data now */
78478344Sobrien    if (!(options & SQL_GD_ANY_ORDER)) {
78578344Sobrien        /* this ODBC driver requires columns to be retrieved in order,
78698186Sgordon         * so we attempt to get every prior un-gotten non-LOB column
78778344Sobrien         */
78878344Sobrien        int i;
78978344Sobrien        for (i = 0; i < col; i++) {
79078344Sobrien            if (row->res->colstate[i] == COL_AVAIL) {
79178344Sobrien                if (IS_LOB(row->res->coltypes[i]))
79278344Sobrien                       row->res->colstate[i] = COL_UNAVAIL;
79378344Sobrien                else {
79478344Sobrien                    odbc_get(row, i, row->res->coltypes[i]);
79598186Sgordon                    row->res->colstate[i] = COL_PRESENT;
79698186Sgordon                }
79798186Sgordon            }
79878344Sobrien        }
79978344Sobrien    }
80078344Sobrien
80178344Sobrien    if ((state == COL_BOUND && !(options & SQL_GD_BOUND)))
80278344Sobrien        /* this driver won't let us re-get bound columns */
80378344Sobrien        return (void *)-1;
80478344Sobrien
80578344Sobrien    /* a LOB might not have a buffer allocated yet - so create one */
80678344Sobrien    if (!row->res->colptrs[col])
80798186Sgordon        row->res->colptrs[col] = apr_pcalloc(row->pool, row->res->colsizes[col]);
80898186Sgordon
80998186Sgordon    rc = SQLGetData(row->res->stmt, col + 1, sqltype, row->res->colptrs[col],
81098186Sgordon                    row->res->colsizes[col], &indicator);
81198186Sgordon    CHECK_ERROR(row->res->apr_dbd, "SQLGetData", rc, SQL_HANDLE_STMT,
81298186Sgordon                row->res->stmt);
81398186Sgordon    if (indicator == SQL_NULL_DATA || rc == SQL_NO_DATA)
81478344Sobrien        return NULL;
81578344Sobrien
81678344Sobrien    if (SQL_SUCCEEDED(rc)) {
81778344Sobrien        /* whatever it was originally, it is now this sqltype */
81898186Sgordon        row->res->coltypes[col] = sqltype;
81998186Sgordon        /* this allows getting CLOBs in text mode by calling get_entry
82098186Sgordon         *   until it returns NULL
82178344Sobrien         */
82298186Sgordon        row->res->colstate[col] =
82398186Sgordon            (rc == SQL_SUCCESS_WITH_INFO) ? COL_AVAIL : COL_RETRIEVED;
82498186Sgordon        return row->res->colptrs[col];
82598186Sgordon    }
82698186Sgordon    else
82798186Sgordon        return (void *)-1;
82898186Sgordon}
82998186Sgordon
83078344Sobrien/* Parse the parameter string for open */
83178344Sobrienstatic apr_status_t odbc_parse_params(apr_pool_t *pool, const char *params,
83278344Sobrien                               int *connect, SQLCHAR **datasource,
83378344Sobrien                               SQLCHAR **user, SQLCHAR **password,
83478344Sobrien                               int *defaultBufferSize, int *nattrs,
83578344Sobrien                               int **attrs, ODBC_INTPTR_T **attrvals)
83678344Sobrien{
83778344Sobrien    char *seps, *last, *next, *name[MAX_PARAMS], *val[MAX_PARAMS];
83878344Sobrien    int nparams = 0, i, j;
83978344Sobrien
84078344Sobrien    *attrs = apr_pcalloc(pool, MAX_PARAMS * sizeof(char *));
84178344Sobrien    *attrvals = apr_pcalloc(pool, MAX_PARAMS * sizeof(ODBC_INTPTR_T));
84278344Sobrien    *nattrs = 0;
84378344Sobrien    seps = DEFAULTSEPS;
84478344Sobrien    name[nparams] = apr_strtok(apr_pstrdup(pool, params), seps, &last);
845126285Smtm
846126285Smtm    /* no params is OK here - let connect return a more useful error msg */
847126285Smtm    if (!name[nparams])
84898186Sgordon        return SQL_SUCCESS;
84998186Sgordon
85098186Sgordon    do {
85198186Sgordon        if (last[strspn(last, seps)] == CSINGLEQUOTE) {
85298186Sgordon            last += strspn(last, seps);
85398186Sgordon            seps=SSINGLEQUOTE;
85498186Sgordon        }
85598186Sgordon        val[nparams] = apr_strtok(NULL, seps, &last);
856126285Smtm        seps = DEFAULTSEPS;
85798186Sgordon
85878344Sobrien        ++nparams;
85998186Sgordon        next = apr_strtok(NULL, seps, &last);
86078344Sobrien        if (!next) {
86178344Sobrien            break;
862101850Sgordon        }
863101850Sgordon        if (nparams >= MAX_PARAMS) {
864101850Sgordon            /* too many parameters, no place to store */
865103018Sgordon            return APR_EGENERAL;
866101850Sgordon        }
867101850Sgordon        name[nparams] = next;
868101850Sgordon    } while (1);
869101850Sgordon
870101850Sgordon    for (j = i = 0; i < nparams; i++) {
871101850Sgordon        if (!apr_strnatcasecmp(name[i], "CONNECT")) {
872101850Sgordon            *datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
873101850Sgordon            *connect = 1;
874115950Smtm        }
875115950Smtm        else if (!apr_strnatcasecmp(name[i], "DATASOURCE")) {
876101850Sgordon            *datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
877101850Sgordon            *connect = 0;
878101850Sgordon        }
87978344Sobrien        else if (!apr_strnatcasecmp(name[i], "USER")) {
88078344Sobrien            *user = (SQLCHAR *)apr_pstrdup(pool, val[i]);
88178344Sobrien        }
88278344Sobrien        else if (!apr_strnatcasecmp(name[i], "PASSWORD")) {
88378344Sobrien            *password = (SQLCHAR *)apr_pstrdup(pool, val[i]);
88478344Sobrien        }
88578344Sobrien        else if (!apr_strnatcasecmp(name[i], "BUFSIZE")) {
88678344Sobrien            *defaultBufferSize = atoi(val[i]);
88778344Sobrien        }
888106643Sgordon        else if (!apr_strnatcasecmp(name[i], "ACCESS")) {
88978344Sobrien            if (!apr_strnatcasecmp(val[i], "READ_ONLY"))
89078344Sobrien                (*attrvals)[j] = SQL_MODE_READ_ONLY;
891126286Smtm            else if (!apr_strnatcasecmp(val[i], "READ_WRITE"))
89278344Sobrien                (*attrvals)[j] = SQL_MODE_READ_WRITE;
89378344Sobrien            else
89478344Sobrien                return SQL_ERROR;
89578344Sobrien            (*attrs)[j++] = SQL_ATTR_ACCESS_MODE;
89678344Sobrien        }
89778344Sobrien        else if (!apr_strnatcasecmp(name[i], "CTIMEOUT")) {
89878344Sobrien            (*attrvals)[j] = atoi(val[i]);
89978344Sobrien            (*attrs)[j++] = SQL_ATTR_LOGIN_TIMEOUT;
90078344Sobrien        }
90178344Sobrien        else if (!apr_strnatcasecmp(name[i], "STIMEOUT")) {
90278344Sobrien            (*attrvals)[j] = atoi(val[i]);
90378344Sobrien            (*attrs)[j++] = SQL_ATTR_CONNECTION_TIMEOUT;
90478344Sobrien        }
90578344Sobrien        else if (!apr_strnatcasecmp(name[i], "TXMODE")) {
90678344Sobrien            if (!apr_strnatcasecmp(val[i], "READ_UNCOMMITTED"))
90778344Sobrien                (*attrvals)[j] = SQL_TXN_READ_UNCOMMITTED;
908106643Sgordon            else if (!apr_strnatcasecmp(val[i], "READ_COMMITTED"))
909106643Sgordon                (*attrvals)[j] = SQL_TXN_READ_COMMITTED;
910106643Sgordon            else if (!apr_strnatcasecmp(val[i], "REPEATABLE_READ"))
911106643Sgordon                (*attrvals)[j] = SQL_TXN_REPEATABLE_READ;
91278344Sobrien            else if (!apr_strnatcasecmp(val[i], "SERIALIZABLE"))
91378344Sobrien                (*attrvals)[j] = SQL_TXN_SERIALIZABLE;
91478344Sobrien            else if (!apr_strnatcasecmp(val[i], "DEFAULT"))
91578344Sobrien                continue;
91678344Sobrien            else
91778344Sobrien                return SQL_ERROR;
91878344Sobrien            (*attrs)[j++] = SQL_ATTR_TXN_ISOLATION;
91978344Sobrien        }
92078344Sobrien        else
921106643Sgordon            return SQL_ERROR;
922106643Sgordon    }
923106643Sgordon    *nattrs = j;
924106643Sgordon    return (*datasource && *defaultBufferSize) ? APR_SUCCESS : SQL_ERROR;
92578344Sobrien}
92698186Sgordon
92798186Sgordon/* common handling after ODBC calls - save error info (code and text) in dbc */
92898186Sgordonstatic void check_error(apr_dbd_t *dbc, const char *step, SQLRETURN rc,
92998186Sgordon                 SQLSMALLINT type, SQLHANDLE h, int line)
93098186Sgordon{
93198186Sgordon    SQLCHAR buffer[512];
93298186Sgordon    SQLCHAR sqlstate[128];
933119170Smtm    SQLINTEGER native;
934119170Smtm    SQLSMALLINT reslength;
935119170Smtm    char *res, *p, *end, *logval = NULL;
936119170Smtm    int i;
937119170Smtm
938119170Smtm    /* set info about last error in dbc  - fast return for SQL_SUCCESS  */
939119170Smtm    if (rc == SQL_SUCCESS) {
940119170Smtm        char successMsg[] = "[dbd_odbc] SQL_SUCCESS ";
94198186Sgordon        apr_size_t successMsgLen = sizeof successMsg - 1;
94298186Sgordon
94398186Sgordon        dbc->lasterrorcode = SQL_SUCCESS;
94498186Sgordon        apr_cpystrn(dbc->lastError, successMsg, sizeof dbc->lastError);
945106643Sgordon        apr_cpystrn(dbc->lastError + successMsgLen, step,
94698186Sgordon                    sizeof dbc->lastError - successMsgLen);
94798186Sgordon        return;
94898186Sgordon    }
94998186Sgordon    switch (rc) {
95098186Sgordon    case SQL_INVALID_HANDLE:
95198186Sgordon        res = "SQL_INVALID_HANDLE";
95298186Sgordon        break;
953106700Sgordon    case SQL_ERROR:
954106700Sgordon        res = "SQL_ERROR";
955106700Sgordon        break;
956106643Sgordon    case SQL_SUCCESS_WITH_INFO:
95798186Sgordon        res = "SQL_SUCCESS_WITH_INFO";
95898186Sgordon        break;
95998186Sgordon    case SQL_STILL_EXECUTING:
96098186Sgordon        res = "SQL_STILL_EXECUTING";
96198186Sgordon        break;
96298186Sgordon    case SQL_NEED_DATA:
96398186Sgordon        res = "SQL_NEED_DATA";
96498186Sgordon        break;
96598186Sgordon    case SQL_NO_DATA:
96698186Sgordon        res = "SQL_NO_DATA";
96798186Sgordon        break;
96898186Sgordon    default:
96998186Sgordon        res = "unrecognized SQL return code";
97098186Sgordon    }
97198186Sgordon    /* these two returns are expected during normal execution */
97298186Sgordon    if (rc != SQL_SUCCESS_WITH_INFO && rc != SQL_NO_DATA
97398186Sgordon        && dbc->can_commit != APR_DBD_TRANSACTION_IGNORE_ERRORS) {
97498186Sgordon        dbc->can_commit = APR_DBD_TRANSACTION_ROLLBACK;
97598186Sgordon    }
97698186Sgordon    p = dbc->lastError;
97798186Sgordon    end = p + sizeof(dbc->lastError);
97898186Sgordon    dbc->lasterrorcode = rc;
97998186Sgordon    p += sprintf(p, "[dbd_odbc] %.64s returned %.30s (%d) at %.24s:%d ",
98098186Sgordon                 step, res, rc, SOURCE_FILE, line - 1);
98198186Sgordon    for (i = 1, rc = 0; rc == 0; i++) {
98298186Sgordon        rc = SQLGetDiagRec(type, h, i, sqlstate, &native, buffer,
98398186Sgordon                            sizeof(buffer), &reslength);
98498186Sgordon        if (SQL_SUCCEEDED(rc) && (p < (end - 280)))
98598186Sgordon            p += sprintf(p, "%.256s %.20s ", buffer, sqlstate);
98698186Sgordon    }
98798186Sgordon    apr_env_get(&logval, "apr_dbd_odbc_log", dbc->pool);
98898186Sgordon    /* if env var was set or call was init/open (no dbname) - log to stderr */
98998186Sgordon    if (logval || !dbc->dbname ) {
99098186Sgordon        char timestamp[APR_CTIME_LEN];
99198186Sgordon
99298186Sgordon        apr_file_t *se;
99398186Sgordon        apr_ctime(timestamp, apr_time_now());
99498186Sgordon        apr_file_open_stderr(&se, dbc->pool);
99598186Sgordon        apr_file_printf(se, "[%s] %s\n", timestamp, dbc->lastError);
99698186Sgordon    }
99798186Sgordon}
99898186Sgordon
99998186Sgordonstatic APR_INLINE int odbc_check_rollback(apr_dbd_t *handle)
100098186Sgordon{
100198186Sgordon    if (handle->can_commit == APR_DBD_TRANSACTION_ROLLBACK) {
100298186Sgordon        handle->lasterrorcode = SQL_ERROR;
100398186Sgordon        apr_cpystrn(handle->lastError, "[dbd_odbc] Rollback pending ",
100498186Sgordon                    sizeof handle->lastError);
100598186Sgordon        return 1;
100698186Sgordon    }
100798186Sgordon    return 0;
100898186Sgordon}
100998186Sgordon
101098186Sgordon/*
101198186Sgordon *   public functions per DBD driver API
101298186Sgordon */
101398186Sgordon
101498186Sgordon/** init: allow driver to perform once-only initialisation. **/
101598186Sgordonstatic void odbc_init(apr_pool_t *pool)
101698186Sgordon{
101798186Sgordon    SQLRETURN rc;
101898186Sgordon    char *step;
101998186Sgordon    apr_version_t apuver;
102098186Sgordon
102198186Sgordon    apu_version(&apuver);
102298186Sgordon    if (apuver.major != DRIVER_APU_VERSION_MAJOR
102398186Sgordon        || apuver.minor != DRIVER_APU_VERSION_MINOR) {
102498186Sgordon            apr_file_t *se;
102598186Sgordon
102698186Sgordon            apr_file_open_stderr(&se, pool);
102798186Sgordon            apr_file_printf(se, "Incorrect " ODBC_DRIVER_STRING " dbd driver version\n"
102898186Sgordon                "Attempt to load APU version %d.%d driver with APU version %d.%d\n",
102998186Sgordon                DRIVER_APU_VERSION_MAJOR, DRIVER_APU_VERSION_MINOR,
103098186Sgordon                apuver.major, apuver.minor);
103198186Sgordon        abort();
103298186Sgordon    }
103398186Sgordon
103498186Sgordon    if (henv)
103598186Sgordon        return;
103698186Sgordon
103798186Sgordon    step = "SQLAllocHandle (SQL_HANDLE_ENV)";
103898186Sgordon    rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
103998186Sgordon    apr_pool_cleanup_register(pool, henv, odbc_close_env, apr_pool_cleanup_null);
104098186Sgordon    if (SQL_SUCCEEDED(rc)) {
104198186Sgordon        step = "SQLSetEnvAttr";
104298186Sgordon        rc = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
104398186Sgordon                          (SQLPOINTER)SQL_OV_ODBC3, 0);
104498186Sgordon    }
104598186Sgordon    else {
1046119166Smtm        apr_dbd_t tmp_dbc;
1047123344Smtm        SQLHANDLE err_h = henv;
1048123344Smtm
1049123344Smtm        tmp_dbc.pool = pool;
1050123344Smtm        tmp_dbc.dbname = NULL;
1051123344Smtm        CHECK_ERROR(&tmp_dbc, step, rc, SQL_HANDLE_ENV, err_h);
1052119166Smtm    }
1053123344Smtm}
1054119166Smtm
1055123344Smtm/** native_handle: return the native database handle of the underlying db **/
1056123344Smtmstatic void *odbc_native_handle(apr_dbd_t *handle)
1057123344Smtm{
1058123344Smtm    return handle->dbc;
1059123344Smtm}
1060119166Smtm
1061123344Smtm/** open: obtain a database connection from the server rec. **/
1062123344Smtm
1063119166Smtm/* It would be more efficient to allocate a single statement handle
1064119166Smtm * here - but SQL_ATTR_CURSOR_SCROLLABLE must be set before
1065123344Smtm * SQLPrepare, and we don't know whether random-access is
1066123344Smtm * specified until SQLExecute so we cannot.
1067119166Smtm */
1068119166Smtm
1069119166Smtmstatic apr_dbd_t *odbc_open(apr_pool_t *pool, const char *params, const char **error)
1070123344Smtm{
1071119166Smtm    SQLRETURN rc;
1072119166Smtm    SQLHANDLE hdbc = NULL;
1073119166Smtm    apr_dbd_t *handle;
1074119166Smtm    char *err_step;
1075119166Smtm    int err_htype, i;
1076119166Smtm    int defaultBufferSize = DEFAULT_BUFFER_SIZE;
1077119166Smtm    SQLHANDLE err_h = NULL;
1078119166Smtm    SQLCHAR  *datasource = (SQLCHAR *)"", *user = (SQLCHAR *)"",
1079119166Smtm             *password = (SQLCHAR *)"";
1080119166Smtm    int nattrs = 0, *attrs = NULL,  connect = 0;
1081119166Smtm    ODBC_INTPTR_T *attrvals = NULL;
1082119166Smtm
1083119166Smtm    err_step = "SQLAllocHandle (SQL_HANDLE_DBC)";
1084119166Smtm    err_htype = SQL_HANDLE_ENV;
1085119166Smtm    err_h = henv;
1086119166Smtm    rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
1087119166Smtm    if (SQL_SUCCEEDED(rc)) {
1088119166Smtm        err_step = "Invalid DBD Parameters - open";
1089119166Smtm        err_htype = SQL_HANDLE_DBC;
1090119166Smtm        err_h = hdbc;
1091119166Smtm        rc = odbc_parse_params(pool, params, &connect, &datasource, &user,
1092119166Smtm                               &password, &defaultBufferSize, &nattrs, &attrs,
1093119166Smtm                               &attrvals);
1094119166Smtm    }
1095119166Smtm    if (SQL_SUCCEEDED(rc)) {
1096119166Smtm        for (i = 0; i < nattrs && SQL_SUCCEEDED(rc); i++) {
1097119166Smtm            err_step = "SQLSetConnectAttr (from DBD Parameters)";
1098119166Smtm            err_htype = SQL_HANDLE_DBC;
1099119166Smtm            err_h = hdbc;
1100119166Smtm            rc = SQLSetConnectAttr(hdbc, attrs[i], (SQLPOINTER)attrvals[i], 0);
1101119166Smtm        }
1102119166Smtm    }
1103119166Smtm    if (SQL_SUCCEEDED(rc)) {
1104119166Smtm        if (connect) {
1105119166Smtm            SQLCHAR out[1024];
1106119166Smtm            SQLSMALLINT outlen;
1107119166Smtm
1108119166Smtm            err_step = "SQLDriverConnect";
1109119166Smtm            err_htype = SQL_HANDLE_DBC;
1110119166Smtm            err_h = hdbc;
1111119166Smtm            rc = SQLDriverConnect(hdbc, NULL, datasource,
1112119166Smtm                        (SQLSMALLINT)strlen((char *)datasource),
1113119166Smtm                        out, sizeof(out), &outlen, SQL_DRIVER_NOPROMPT);
1114119166Smtm        }
1115119166Smtm        else {
1116119166Smtm            err_step = "SQLConnect";
1117119166Smtm            err_htype = SQL_HANDLE_DBC;
1118119166Smtm            err_h = hdbc;
1119119166Smtm            rc = SQLConnect(hdbc, datasource,
1120119166Smtm                        (SQLSMALLINT)strlen((char *)datasource),
1121119166Smtm                        user, (SQLSMALLINT)strlen((char *)user),
1122119166Smtm                        password, (SQLSMALLINT)strlen((char *)password));
1123119166Smtm        }
1124119166Smtm    }
1125119166Smtm    if (SQL_SUCCEEDED(rc)) {
1126119166Smtm        handle = apr_pcalloc(pool, sizeof(apr_dbd_t));
1127119166Smtm        handle->dbname = apr_pstrdup(pool, (char *)datasource);
1128119166Smtm        handle->dbc = hdbc;
1129119166Smtm        handle->pool = pool;
1130119166Smtm        handle->defaultBufferSize = defaultBufferSize;
1131119166Smtm        CHECK_ERROR(handle, "SQLConnect", rc, SQL_HANDLE_DBC, handle->dbc);
1132119166Smtm        handle->default_transaction_mode = 0;
1133119166Smtm        handle->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
1134119166Smtm        SQLGetInfo(hdbc, SQL_DEFAULT_TXN_ISOLATION,
1135119166Smtm                   &(handle->default_transaction_mode), sizeof(ODBC_INTPTR_T), NULL);
1136119166Smtm        handle->transaction_mode = handle->default_transaction_mode;
1137119166Smtm        SQLGetInfo(hdbc, SQL_GETDATA_EXTENSIONS ,&(handle->dboptions),
1138119166Smtm                   sizeof(ODBC_INTPTR_T), NULL);
1139119166Smtm        apr_pool_cleanup_register(pool, handle, odbc_close_cleanup, apr_pool_cleanup_null);
1140119166Smtm        return handle;
1141119166Smtm    }
1142119166Smtm    else {
1143119166Smtm        apr_dbd_t tmp_dbc;
1144119166Smtm
1145119166Smtm        tmp_dbc.pool = pool;
1146119166Smtm        tmp_dbc.dbname = NULL;
1147119166Smtm        CHECK_ERROR(&tmp_dbc, err_step, rc, err_htype, err_h);
1148119166Smtm        if (error)
1149119166Smtm            *error = apr_pstrdup(pool, tmp_dbc.lastError);
1150119166Smtm        if (hdbc)
1151119166Smtm            SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
1152119166Smtm        return NULL;
1153119166Smtm    }
1154119166Smtm}
1155119166Smtm
1156119166Smtm/** check_conn: check status of a database connection **/
1157119166Smtmstatic apr_status_t odbc_check_conn(apr_pool_t *pool, apr_dbd_t *handle)
1158119166Smtm{
1159119166Smtm    SQLUINTEGER isDead;
1160119166Smtm    SQLRETURN   rc;
1161119166Smtm
1162119166Smtm    rc = SQLGetConnectAttr(handle->dbc, SQL_ATTR_CONNECTION_DEAD, &isDead,
1163119166Smtm                            sizeof(SQLUINTEGER), NULL);
1164119166Smtm    CHECK_ERROR(handle, "SQLGetConnectAttr (SQL_ATTR_CONNECTION_DEAD)", rc,
1165119166Smtm                SQL_HANDLE_DBC, handle->dbc);
1166119166Smtm    /* if driver cannot check connection, say so */
1167119166Smtm    if (rc != SQL_SUCCESS)
1168119166Smtm        return APR_ENOTIMPL;
1169119166Smtm
1170119166Smtm    return (isDead == SQL_CD_FALSE) ? APR_SUCCESS : APR_EGENERAL;
1171119166Smtm}
1172119166Smtm
1173119166Smtm/** set_dbname: select database name.  May be a no-op if not supported. **/
1174119166Smtmstatic int odbc_set_dbname(apr_pool_t*pool, apr_dbd_t *handle,
1175119166Smtm                           const char *name)
1176119166Smtm{
1177119166Smtm    if (apr_strnatcmp(name, handle->dbname)) {
1178119166Smtm        return APR_EGENERAL;        /* It's illegal to change dbname in ODBC */
1179119166Smtm    }
1180119166Smtm    CHECK_ERROR(handle, "set_dbname (no-op)", SQL_SUCCESS, SQL_HANDLE_DBC,
1181119166Smtm                handle->dbc);
1182119166Smtm    return APR_SUCCESS;             /* OK if it's the same name */
1183119166Smtm}
1184119166Smtm
1185119166Smtm/** transaction: start a transaction.  May be a no-op. **/
1186119166Smtmstatic int odbc_start_transaction(apr_pool_t *pool, apr_dbd_t *handle,
1187119166Smtm                                  apr_dbd_transaction_t **trans)
1188119166Smtm{
1189119166Smtm    SQLRETURN rc = SQL_SUCCESS;
1190119166Smtm
1191119166Smtm    if (handle->transaction_mode) {
1192119166Smtm        rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_TXN_ISOLATION,
1193119166Smtm                               (SQLPOINTER)handle->transaction_mode, 0);
1194119166Smtm        CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_TXN_ISOLATION)", rc,
1195119166Smtm                    SQL_HANDLE_DBC, handle->dbc);
1196119166Smtm    }
1197119166Smtm    if (SQL_SUCCEEDED(rc)) {
1198119166Smtm        /* turn off autocommit for transactions */
1199119166Smtm        rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_AUTOCOMMIT,
1200119166Smtm                               SQL_AUTOCOMMIT_OFF, 0);
1201119166Smtm        CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)", rc,
1202119166Smtm                    SQL_HANDLE_DBC, handle->dbc);
1203119166Smtm    }
1204119166Smtm    if (SQL_SUCCEEDED(rc)) {
1205119166Smtm        *trans = apr_palloc(pool, sizeof(apr_dbd_transaction_t));
1206119166Smtm        (*trans)->dbc = handle->dbc;
1207119166Smtm        (*trans)->apr_dbd = handle;
1208119166Smtm    }
1209119166Smtm    handle->can_commit = APR_DBD_TRANSACTION_COMMIT;
1210119166Smtm    return APR_FROM_SQL_RESULT(rc);
1211119166Smtm}
1212119166Smtm
1213119166Smtm/** end_transaction: end a transaction **/
1214119166Smtmstatic int odbc_end_transaction(apr_dbd_transaction_t *trans)
1215119166Smtm{
1216119166Smtm    SQLRETURN rc;
1217119166Smtm    int action = (trans->apr_dbd->can_commit != APR_DBD_TRANSACTION_ROLLBACK)
1218119166Smtm        ? SQL_COMMIT : SQL_ROLLBACK;
1219119166Smtm
1220119166Smtm    rc = SQLEndTran(SQL_HANDLE_DBC, trans->dbc, action);
1221119166Smtm    CHECK_ERROR(trans->apr_dbd, "SQLEndTran", rc, SQL_HANDLE_DBC, trans->dbc);
1222119166Smtm    if (SQL_SUCCEEDED(rc)) {
1223119166Smtm        rc = SQLSetConnectAttr(trans->dbc, SQL_ATTR_AUTOCOMMIT,
1224119166Smtm                               (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
1225119166Smtm        CHECK_ERROR(trans->apr_dbd, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)",
1226119166Smtm                    rc, SQL_HANDLE_DBC, trans->dbc);
1227119166Smtm    }
1228119166Smtm    trans->apr_dbd->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
1229119166Smtm    return APR_FROM_SQL_RESULT(rc);
1230119166Smtm}
1231119166Smtm
1232119166Smtm/** query: execute an SQL statement which doesn't return a result set **/
1233119166Smtmstatic int odbc_query(apr_dbd_t *handle, int *nrows, const char *statement)
1234119166Smtm{
1235119166Smtm    SQLRETURN rc;
1236119166Smtm    SQLHANDLE hstmt = NULL;
1237119166Smtm    size_t len = strlen(statement);
1238119166Smtm
1239119166Smtm    if (odbc_check_rollback(handle))
1240124797Scperciva        return APR_EGENERAL;
1241119166Smtm
1242119166Smtm    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
1243119166Smtm    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
1244119166Smtm                handle->dbc);
1245119166Smtm    if (!SQL_SUCCEEDED(rc))
1246119166Smtm        return APR_FROM_SQL_RESULT(rc);
1247119166Smtm
1248119166Smtm    rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
1249119166Smtm    CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
1250119166Smtm
1251119166Smtm    if (SQL_SUCCEEDED(rc)) {
1252119166Smtm        SQLLEN rowcount;
1253119166Smtm
1254119166Smtm        rc = SQLRowCount(hstmt, &rowcount);
1255119166Smtm        *nrows = (int)rowcount;
1256119166Smtm        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT, hstmt);
1257119166Smtm    }
1258119166Smtm
1259119166Smtm    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
1260119166Smtm    return APR_FROM_SQL_RESULT(rc);
1261119166Smtm}
1262119166Smtm
1263119166Smtm/** select: execute an SQL statement which returns a result set **/
1264119166Smtmstatic int odbc_select(apr_pool_t *pool, apr_dbd_t *handle,
1265119166Smtm                       apr_dbd_results_t **res, const char *statement,
1266                       int random)
1267{
1268    SQLRETURN rc;
1269    SQLHANDLE hstmt;
1270    apr_dbd_prepared_t *stmt;
1271    size_t len = strlen(statement);
1272
1273    if (odbc_check_rollback(handle))
1274        return APR_EGENERAL;
1275
1276    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
1277    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
1278                handle->dbc);
1279    if (!SQL_SUCCEEDED(rc))
1280        return APR_FROM_SQL_RESULT(rc);
1281    /* Prepare an apr_dbd_prepared_t for pool cleanup, even though this
1282     * is not a prepared statement.  We want the same cleanup mechanism.
1283     */
1284    stmt = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
1285    stmt->apr_dbd = handle;
1286    stmt->dbc = handle->dbc;
1287    stmt->stmt = hstmt;
1288    apr_pool_cleanup_register(pool, stmt, odbc_close_pstmt, apr_pool_cleanup_null);
1289    if (random) {
1290        rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
1291                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1292        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)", rc,
1293                    SQL_HANDLE_STMT, hstmt);
1294    }
1295    if (SQL_SUCCEEDED(rc)) {
1296        rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
1297        CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
1298    }
1299    if (SQL_SUCCEEDED(rc)) {
1300        rc = odbc_create_results(handle, hstmt, pool, random, res);
1301        apr_pool_cleanup_register(pool, *res,
1302                                  odbc_close_results, apr_pool_cleanup_null);
1303    }
1304    return APR_FROM_SQL_RESULT(rc);
1305}
1306
1307/** num_cols: get the number of columns in a results set **/
1308static int odbc_num_cols(apr_dbd_results_t *res)
1309{
1310    return res->ncols;
1311}
1312
1313/** num_tuples: get the number of rows in a results set **/
1314static int odbc_num_tuples(apr_dbd_results_t *res)
1315{
1316    SQLRETURN rc;
1317    SQLLEN nrows;
1318
1319    rc = SQLRowCount(res->stmt, &nrows);
1320    CHECK_ERROR(res->apr_dbd, "SQLRowCount", rc, SQL_HANDLE_STMT, res->stmt);
1321    return SQL_SUCCEEDED(rc) ? (int)nrows : -1;
1322}
1323
1324/** get_row: get a row from a result set **/
1325static int odbc_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
1326                        apr_dbd_row_t **row, int rownum)
1327{
1328    SQLRETURN rc;
1329    char *fetchtype;
1330    int c;
1331
1332    *row = apr_pcalloc(pool, sizeof(apr_dbd_row_t));
1333    (*row)->stmt = res->stmt;
1334    (*row)->dbc = res->dbc;
1335    (*row)->res = res;
1336    (*row)->pool = res->pool;
1337
1338    /* mark all the columns as needing SQLGetData unless they are bound  */
1339    for (c = 0; c < res->ncols; c++) {
1340        if (res->colstate[c] != COL_BOUND) {
1341            res->colstate[c] = COL_AVAIL;
1342        }
1343        /* some drivers do not null-term zero-len CHAR data */
1344        if (res->colptrs[c])
1345            *(char *)res->colptrs[c] = 0;
1346    }
1347
1348    if (res->random && (rownum > 0)) {
1349        fetchtype = "SQLFetchScroll";
1350        rc = SQLFetchScroll(res->stmt, SQL_FETCH_ABSOLUTE, rownum);
1351    }
1352    else {
1353        fetchtype = "SQLFetch";
1354        rc = SQLFetch(res->stmt);
1355    }
1356    CHECK_ERROR(res->apr_dbd, fetchtype, rc, SQL_HANDLE_STMT, res->stmt);
1357    (*row)->stmt = res->stmt;
1358    if (!SQL_SUCCEEDED(rc) && !res->random) {
1359        /* early close on any error (usually SQL_NO_DATA) if fetching
1360         * sequentially to release resources ASAP
1361         */
1362        odbc_close_results(res);
1363        return -1;
1364    }
1365    return SQL_SUCCEEDED(rc) ? 0 : -1;
1366}
1367
1368/** datum_get: get a binary entry from a row **/
1369static apr_status_t odbc_datum_get(const apr_dbd_row_t *row, int col,
1370                                   apr_dbd_type_e dbdtype, void *data)
1371{
1372    SQLSMALLINT sqltype;
1373    void *p;
1374    int len;
1375
1376    if (col >= row->res->ncols)
1377        return APR_EGENERAL;
1378
1379    if (dbdtype < 0 || dbdtype >= NUM_APR_DBD_TYPES) {
1380        data = NULL;            /* invalid type */
1381        return APR_EGENERAL;
1382    }
1383
1384    len = sqlSizes[dbdtype];
1385    sqltype = sqlCtype[dbdtype];
1386
1387    /* must not memcpy a brigade, sentinals are relative to orig loc */
1388    if (IS_LOB(sqltype))
1389        return odbc_create_bucket(row, col, sqltype, data);
1390
1391    p = odbc_get(row, col, sqltype);
1392    if (p == (void *)-1)
1393        return APR_EGENERAL;
1394
1395    if (p == NULL)
1396        return APR_ENOENT;          /* SQL NULL value */
1397
1398    if (len < 0)
1399       *(char**)data = (char *)p;
1400    else
1401        memcpy(data, p, len);
1402
1403    return APR_SUCCESS;
1404
1405}
1406
1407/** get_entry: get an entry from a row (string data) **/
1408static const char *odbc_get_entry(const apr_dbd_row_t *row, int col)
1409{
1410    void *p;
1411
1412    if (col >= row->res->ncols)
1413        return NULL;
1414
1415    p = odbc_get(row, col, SQL_C_CHAR);
1416
1417    /* NULL or invalid (-1) */
1418    if (p == NULL || p == (void *)-1)
1419        return p;
1420    else
1421        return apr_pstrdup(row->pool, p);
1422}
1423
1424/** error: get current error message (if any) **/
1425static const char *odbc_error(apr_dbd_t *handle, int errnum)
1426{
1427    return (handle) ? handle->lastError : "[dbd_odbc]No error message available";
1428}
1429
1430/** escape: escape a string so it is safe for use in query/select **/
1431static const char *odbc_escape(apr_pool_t *pool, const char *s,
1432                               apr_dbd_t *handle)
1433{
1434    char *newstr, *src, *dst, *sq;
1435    int qcount;
1436
1437    /* return the original if there are no single-quotes */
1438    if (!(sq = strchr(s, '\'')))
1439        return (char *)s;
1440    /* count the single-quotes and allocate a new buffer */
1441    for (qcount = 1; (sq = strchr(sq + 1, '\'')); )
1442        qcount++;
1443    newstr = apr_palloc(pool, strlen(s) + qcount + 1);
1444
1445    /* move chars, doubling all single-quotes */
1446    src = (char *)s;
1447    for (dst = newstr; *src; src++) {
1448        if ((*dst++ = *src) == '\'')
1449            *dst++ = '\'';
1450    }
1451    *dst = 0;
1452    return newstr;
1453}
1454
1455/** prepare: prepare a statement **/
1456static int odbc_prepare(apr_pool_t *pool, apr_dbd_t *handle,
1457                        const char *query, const char *label, int nargs,
1458                        int nvals, apr_dbd_type_e *types,
1459                        apr_dbd_prepared_t **statement)
1460{
1461    SQLRETURN rc;
1462    size_t len = strlen(query);
1463
1464    if (odbc_check_rollback(handle))
1465        return APR_EGENERAL;
1466
1467    *statement = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
1468    (*statement)->dbc = handle->dbc;
1469    (*statement)->apr_dbd = handle;
1470    (*statement)->nargs = nargs;
1471    (*statement)->nvals = nvals;
1472    (*statement)->types =
1473        apr_pmemdup(pool, types, nargs * sizeof(apr_dbd_type_e));
1474    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &((*statement)->stmt));
1475    apr_pool_cleanup_register(pool, *statement,
1476                              odbc_close_pstmt, apr_pool_cleanup_null);
1477    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc,
1478                SQL_HANDLE_DBC, handle->dbc);
1479    rc = SQLPrepare((*statement)->stmt, (SQLCHAR *)query, (SQLINTEGER)len);
1480    CHECK_ERROR(handle, "SQLPrepare", rc, SQL_HANDLE_STMT,
1481                (*statement)->stmt);
1482    return APR_FROM_SQL_RESULT(rc);
1483}
1484
1485/** pquery: query using a prepared statement + args **/
1486static int odbc_pquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1487                       apr_dbd_prepared_t *statement, const char **args)
1488{
1489    SQLRETURN rc = SQL_SUCCESS;
1490    int i, argp;
1491
1492    if (odbc_check_rollback(handle))
1493        return APR_EGENERAL;
1494
1495    for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1496        rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1497                             &argp, (const void **)args, TEXTMODE);
1498    }
1499    if (SQL_SUCCEEDED(rc)) {
1500        rc = SQLExecute(statement->stmt);
1501        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1502                    statement->stmt);
1503    }
1504    if (SQL_SUCCEEDED(rc)) {
1505        SQLLEN rowcount;
1506
1507        rc = SQLRowCount(statement->stmt, &rowcount);
1508        *nrows = (int)rowcount;
1509        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
1510                    statement->stmt);
1511    }
1512    return APR_FROM_SQL_RESULT(rc);
1513}
1514
1515/** pvquery: query using a prepared statement + args **/
1516static int odbc_pvquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1517                        apr_dbd_prepared_t *statement, va_list args)
1518{
1519    const char **values;
1520    int i;
1521
1522    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1523    for (i = 0; i < statement->nvals; i++)
1524        values[i] = va_arg(args, const char *);
1525    return odbc_pquery(pool, handle, nrows, statement, values);
1526}
1527
1528/** pselect: select using a prepared statement + args **/
1529static int odbc_pselect(apr_pool_t *pool, apr_dbd_t *handle,
1530                        apr_dbd_results_t **res, apr_dbd_prepared_t *statement,
1531                        int random, const char **args)
1532{
1533    SQLRETURN rc = SQL_SUCCESS;
1534    int i, argp;
1535
1536    if (odbc_check_rollback(handle))
1537        return APR_EGENERAL;
1538
1539    if (random) {
1540        rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
1541                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1542        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
1543                    rc, SQL_HANDLE_STMT, statement->stmt);
1544    }
1545    if (SQL_SUCCEEDED(rc)) {
1546        for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1547            rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1548                                 &argp, (const void **)args, TEXTMODE);
1549        }
1550    }
1551    if (SQL_SUCCEEDED(rc)) {
1552        rc = SQLExecute(statement->stmt);
1553        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1554                    statement->stmt);
1555    }
1556    if (SQL_SUCCEEDED(rc)) {
1557        rc = odbc_create_results(handle, statement->stmt, pool, random, res);
1558        apr_pool_cleanup_register(pool, *res,
1559                                  odbc_close_results, apr_pool_cleanup_null);
1560    }
1561    return APR_FROM_SQL_RESULT(rc);
1562}
1563
1564/** pvselect: select using a prepared statement + args **/
1565static int odbc_pvselect(apr_pool_t *pool, apr_dbd_t *handle,
1566                         apr_dbd_results_t **res,
1567                         apr_dbd_prepared_t *statement, int random,
1568                         va_list args)
1569{
1570    const char **values;
1571    int i;
1572
1573    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1574    for (i = 0; i < statement->nvals; i++)
1575        values[i] = va_arg(args, const char *);
1576    return odbc_pselect(pool, handle, res, statement, random, values);
1577}
1578
1579/** get_name: get a column title from a result set **/
1580static const char *odbc_get_name(const apr_dbd_results_t *res, int col)
1581{
1582    SQLRETURN rc;
1583    char buffer[MAX_COLUMN_NAME];
1584    SQLSMALLINT colnamelength, coltype, coldecimal, colnullable;
1585    SQLULEN colsize;
1586
1587    if (col >= res->ncols)
1588        return NULL;            /* bogus column number */
1589    if (res->colnames[col] != NULL)
1590        return res->colnames[col];      /* we already retrieved it */
1591    rc = SQLDescribeCol(res->stmt, col + 1,
1592                        (SQLCHAR *)buffer, sizeof(buffer), &colnamelength,
1593                        &coltype, &colsize, &coldecimal, &colnullable);
1594    CHECK_ERROR(res->apr_dbd, "SQLDescribeCol", rc,
1595                SQL_HANDLE_STMT, res->stmt);
1596    res->colnames[col] = apr_pstrdup(res->pool, buffer);
1597    return res->colnames[col];
1598}
1599
1600/** transaction_mode_get: get the mode of transaction **/
1601static int odbc_transaction_mode_get(apr_dbd_transaction_t *trans)
1602{
1603    return (int)trans->apr_dbd->can_commit;
1604}
1605
1606/** transaction_mode_set: set the mode of transaction **/
1607static int odbc_transaction_mode_set(apr_dbd_transaction_t *trans, int mode)
1608{
1609    int legal = (  APR_DBD_TRANSACTION_IGNORE_ERRORS
1610                 | APR_DBD_TRANSACTION_COMMIT
1611                 | APR_DBD_TRANSACTION_ROLLBACK);
1612
1613    if ((mode & legal) != mode)
1614        return APR_EGENERAL;
1615
1616    trans->apr_dbd->can_commit = mode;
1617    return APR_SUCCESS;
1618}
1619
1620/** pbquery: query using a prepared statement + binary args **/
1621static int odbc_pbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1622                        apr_dbd_prepared_t *statement, const void **args)
1623{
1624    SQLRETURN rc = SQL_SUCCESS;
1625    int i, argp;
1626
1627    if (odbc_check_rollback(handle))
1628        return APR_EGENERAL;
1629
1630    for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++)
1631        rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1632                             &argp, args, BINARYMODE);
1633
1634    if (SQL_SUCCEEDED(rc)) {
1635        rc = SQLExecute(statement->stmt);
1636        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1637                    statement->stmt);
1638    }
1639    if (SQL_SUCCEEDED(rc)) {
1640        SQLLEN rowcount;
1641
1642        rc = SQLRowCount(statement->stmt, &rowcount);
1643        *nrows = (int)rowcount;
1644        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
1645                    statement->stmt);
1646    }
1647    return APR_FROM_SQL_RESULT(rc);
1648}
1649
1650/** pbselect: select using a prepared statement + binary args **/
1651static int odbc_pbselect(apr_pool_t *pool, apr_dbd_t *handle,
1652                         apr_dbd_results_t **res,
1653                         apr_dbd_prepared_t *statement,
1654                         int random, const void **args)
1655{
1656    SQLRETURN rc = SQL_SUCCESS;
1657    int i, argp;
1658
1659    if (odbc_check_rollback(handle))
1660        return APR_EGENERAL;
1661
1662    if (random) {
1663        rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
1664                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1665        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
1666                    rc, SQL_HANDLE_STMT, statement->stmt);
1667    }
1668    if (SQL_SUCCEEDED(rc)) {
1669        for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1670            rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1671                                 &argp, args, BINARYMODE);
1672        }
1673    }
1674    if (SQL_SUCCEEDED(rc)) {
1675        rc = SQLExecute(statement->stmt);
1676        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1677                    statement->stmt);
1678    }
1679    if (SQL_SUCCEEDED(rc)) {
1680        rc = odbc_create_results(handle, statement->stmt, pool, random, res);
1681        apr_pool_cleanup_register(pool, *res,
1682                                  odbc_close_results, apr_pool_cleanup_null);
1683    }
1684
1685    return APR_FROM_SQL_RESULT(rc);
1686}
1687
1688/** pvbquery: query using a prepared statement + binary args **/
1689static int odbc_pvbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1690                         apr_dbd_prepared_t *statement, va_list args)
1691{
1692    const char **values;
1693    int i;
1694
1695    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1696    for (i = 0; i < statement->nvals; i++)
1697        values[i] = va_arg(args, const char *);
1698    return odbc_pbquery(pool, handle, nrows, statement, (const void **)values);
1699}
1700
1701/** pvbselect: select using a prepared statement + binary args **/
1702static int odbc_pvbselect(apr_pool_t *pool, apr_dbd_t *handle,
1703                          apr_dbd_results_t **res,
1704                          apr_dbd_prepared_t *statement,
1705                          int random, va_list args)
1706{
1707    const char **values;
1708    int i;
1709
1710    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1711    for (i = 0; i < statement->nvals; i++)
1712        values[i] = va_arg(args, const char *);
1713    return odbc_pbselect(pool, handle, res, statement, random, (const void **)values);
1714}
1715
1716APU_MODULE_DECLARE_DATA const apr_dbd_driver_t ODBC_DRIVER_ENTRY = {
1717    ODBC_DRIVER_STRING,
1718    odbc_init,
1719    odbc_native_handle,
1720    odbc_open,
1721    odbc_check_conn,
1722    odbc_close,
1723    odbc_set_dbname,
1724    odbc_start_transaction,
1725    odbc_end_transaction,
1726    odbc_query,
1727    odbc_select,
1728    odbc_num_cols,
1729    odbc_num_tuples,
1730    odbc_get_row,
1731    odbc_get_entry,
1732    odbc_error,
1733    odbc_escape,
1734    odbc_prepare,
1735    odbc_pvquery,
1736    odbc_pvselect,
1737    odbc_pquery,
1738    odbc_pselect,
1739    odbc_get_name,
1740    odbc_transaction_mode_get,
1741    odbc_transaction_mode_set,
1742    "?",
1743    odbc_pvbquery,
1744    odbc_pvbselect,
1745    odbc_pbquery,
1746    odbc_pbselect,
1747    odbc_datum_get
1748};
1749
1750#endif
1751