1251876Speter/* Licensed to the Apache Software Foundation (ASF) under one or more
2251876Speter * contributor license agreements.  See the NOTICE file distributed with
3251876Speter * this work for additional information regarding copyright ownership.
4251876Speter * The ASF licenses this file to You under the Apache License, Version 2.0
5251876Speter * (the "License"); you may not use this file except in compliance with
6251876Speter * the License.  You may obtain a copy of the License at
7251876Speter *
8251876Speter *     http://www.apache.org/licenses/LICENSE-2.0
9251876Speter *
10251876Speter * Unless required by applicable law or agreed to in writing, software
11251876Speter * distributed under the License is distributed on an "AS IS" BASIS,
12251876Speter * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13251876Speter * See the License for the specific language governing permissions and
14251876Speter * limitations under the License.
15251876Speter */
16251876Speter
17251876Speter#include "apu.h"
18251876Speter#if APU_HAVE_ODBC
19251876Speter
20251876Speter#include "apr.h"
21251876Speter#include "apr_strings.h"
22251876Speter#include "apr_buckets.h"
23251876Speter#include "apr_env.h"
24251876Speter#include "apr_file_io.h"
25251876Speter#include "apr_file_info.h"
26251876Speter#include "apr_dbd_internal.h"
27251876Speter#include "apr_thread_proc.h"
28251876Speter#include "apu_version.h"
29251876Speter#include "apu_config.h"
30251876Speter
31251876Speter#include <stdlib.h>
32251876Speter
33251876Speter/* If library is ODBC-V2, use macros for limited ODBC-V2 support
34251876Speter * No random access in V2.
35251876Speter */
36251876Speter#ifdef ODBCV2
37251876Speter#define ODBCVER 0x0200
38251876Speter#include "apr_dbd_odbc_v2.h"
39251876Speter#endif
40251876Speter
41251876Speter/* standard ODBC include files */
42251876Speter#ifdef HAVE_SQL_H
43251876Speter#include <sql.h>
44251876Speter#include <sqlext.h>
45251876Speter#elif defined(HAVE_ODBC_SQL_H)
46251876Speter#include <odbc/sql.h>
47251876Speter#include <odbc/sqlext.h>
48251876Speter#endif
49251876Speter
50289166Speter/*
51289166Speter* MSVC6 does not support intptr_t (C99)
52289166Speter* APR does not have a signed inptr type until 2.0  (r1557720)
53289166Speter*/
54289166Speter#if defined(_MSC_VER) && _MSC_VER < 1400
55289166Speter#if APR_SIZEOF_VOIDP == 8
56289166Speter#define   ODBC_INTPTR_T  apr_int64_t
57289166Speter#else
58289166Speter#define   ODBC_INTPTR_T  apr_int32_t
59289166Speter#endif
60289166Speter#else
61289166Speter#define   ODBC_INTPTR_T  intptr_t
62289166Speter#endif
63289166Speter
64289166Speter
65251876Speter/* Driver name is "odbc" and the entry point is 'apr_dbd_odbc_driver'
66251876Speter * unless ODBC_DRIVER_NAME is defined and it is linked with another db library which
67251876Speter * is ODBC source-compatible. e.g. DB2, Informix, TimesTen, mysql.
68251876Speter */
69251876Speter#ifndef ODBC_DRIVER_NAME
70251876Speter#define ODBC_DRIVER_NAME odbc
71251876Speter#endif
72251876Speter#define STRINGIFY(x) #x
73251876Speter#define NAMIFY2(n) apr_dbd_##n##_driver
74251876Speter#define NAMIFY1(n) NAMIFY2(n)
75251876Speter#define ODBC_DRIVER_STRING STRINGIFY(ODBC_DRIVER_NAME)
76251876Speter#define ODBC_DRIVER_ENTRY NAMIFY1(ODBC_DRIVER_NAME)
77251876Speter
78251876Speter/* Required APR version for this driver */
79251876Speter#define DRIVER_APU_VERSION_MAJOR APU_MAJOR_VERSION
80251876Speter#define DRIVER_APU_VERSION_MINOR APU_MINOR_VERSION
81251876Speter
82251876Speterstatic SQLHANDLE henv = NULL;           /* ODBC ENV handle is process-wide */
83251876Speter
84251876Speter/* Use a CHECK_ERROR macro so we can grab the source line numbers
85251876Speter * for error reports
86251876Speter */
87251876Speterstatic void check_error(apr_dbd_t *a, const char *step, SQLRETURN rc,
88251876Speter                 SQLSMALLINT type, SQLHANDLE h, int line);
89251876Speter#define CHECK_ERROR(a,s,r,t,h)  check_error(a,s,r,t,h, __LINE__)
90251876Speter
91251876Speter#define SOURCE_FILE __FILE__            /* source file for error messages */
92251876Speter#define MAX_ERROR_STRING 1024           /* max length of message in dbc */
93251876Speter#define MAX_COLUMN_NAME 256             /* longest column name recognized */
94251876Speter#define DEFAULT_BUFFER_SIZE 1024        /* value for defaultBufferSize */
95251876Speter
96251876Speter#define MAX_PARAMS  20
97251876Speter#define DEFAULTSEPS " \t\r\n,="
98251876Speter#define CSINGLEQUOTE '\''
99251876Speter#define SSINGLEQUOTE "\'"
100251876Speter
101251876Speter#define TEXTMODE 1              /* used for text (APR 1.2) mode params */
102251876Speter#define BINARYMODE 0            /* used for binary (APR 1.3+) mode params */
103251876Speter
104251876Speter/* Identify datatypes which are LOBs
105251876Speter * - DB2 DRDA driver uses undefined types -98 and -99 for CLOB & BLOB
106251876Speter */
107251876Speter#define IS_LOB(t)  (t == SQL_LONGVARCHAR \
108251876Speter     || t == SQL_LONGVARBINARY || t == SQL_VARBINARY \
109251876Speter     || t == -98 || t == -99)
110251876Speter
111251876Speter/* These types are CLOBs
112251876Speter * - DB2 DRDA driver uses undefined type -98 for CLOB
113251876Speter */
114251876Speter#define IS_CLOB(t) \
115251876Speter    (t == SQL_LONGVARCHAR || t == -98)
116251876Speter
117251876Speter/* Convert a SQL result to an APR result */
118251876Speter#define APR_FROM_SQL_RESULT(rc) \
119251876Speter    (SQL_SUCCEEDED(rc) ? APR_SUCCESS : APR_EGENERAL)
120251876Speter
121251876Speter/* DBD opaque structures */
122251876Speterstruct apr_dbd_t
123251876Speter{
124251876Speter    SQLHANDLE dbc;              /* SQL connection handle - NULL after close */
125251876Speter    apr_pool_t *pool;           /* connection lifetime pool */
126251876Speter    char *dbname;               /* ODBC datasource */
127251876Speter    int lasterrorcode;
128251876Speter    int lineNumber;
129251876Speter    char lastError[MAX_ERROR_STRING];
130251876Speter    int defaultBufferSize;      /* used for CLOBs in text mode,
131251876Speter                                 * and when fld size is indeterminate */
132289166Speter    ODBC_INTPTR_T transaction_mode;
133289166Speter    ODBC_INTPTR_T dboptions;         /* driver options re SQLGetData */
134289166Speter    ODBC_INTPTR_T default_transaction_mode;
135251876Speter    int can_commit;             /* controls end_trans behavior */
136251876Speter};
137251876Speter
138251876Speterstruct apr_dbd_results_t
139251876Speter{
140251876Speter    SQLHANDLE stmt;             /* parent sql statement handle */
141251876Speter    SQLHANDLE dbc;              /* parent sql connection handle */
142251876Speter    apr_pool_t *pool;           /* pool from query or select */
143251876Speter    apr_dbd_t *apr_dbd;         /* parent DBD connection handle */
144251876Speter    int random;                 /* random access requested */
145251876Speter    int ncols;                  /* number of columns */
146251876Speter    int isclosed;               /* cursor has been closed */
147251876Speter    char **colnames;            /* array of column names (NULL until used) */
148251876Speter    SQLPOINTER *colptrs;        /* pointers to column data */
149251876Speter    SQLINTEGER *colsizes;       /* sizes for columns (enough for txt or bin) */
150251876Speter    SQLINTEGER *coltextsizes;   /* max-sizes if converted to text */
151251876Speter    SQLSMALLINT *coltypes;      /* array of SQL data types for columns */
152251876Speter    SQLLEN *colinds;            /* array of SQL data indicator/strlens */
153251876Speter    int *colstate;              /* array of column states
154251876Speter                                 * - avail, bound, present, unavail
155251876Speter                                 */
156251876Speter    int *all_data_fetched;      /* flags data as all fetched, for LOBs  */
157251876Speter    void *data;                 /* buffer for all data for one row */
158251876Speter};
159251876Speter
160251876Speterenum                            /* results column states */
161251876Speter{
162251876Speter    COL_AVAIL,                  /* data may be retrieved with SQLGetData */
163251876Speter    COL_PRESENT,                /* data has been retrieved with SQLGetData */
164251876Speter    COL_BOUND,                  /* column is bound to colptr */
165251876Speter    COL_RETRIEVED,              /* all data from column has been returned */
166251876Speter    COL_UNAVAIL                 /* column is unavailable because ODBC driver
167251876Speter                                 *  requires that columns be retrieved
168251876Speter                                 *  in ascending order and a higher col
169251876Speter                                 *  was accessed
170251876Speter                                 */
171251876Speter};
172251876Speter
173251876Speterstruct apr_dbd_row_t {
174251876Speter    SQLHANDLE stmt;             /* parent ODBC statement handle */
175251876Speter    SQLHANDLE dbc;              /* parent ODBC connection handle */
176251876Speter    apr_pool_t *pool;           /* pool from get_row */
177251876Speter    apr_dbd_results_t *res;
178251876Speter};
179251876Speter
180251876Speterstruct apr_dbd_transaction_t {
181251876Speter    SQLHANDLE dbc;              /* parent ODBC connection handle */
182251876Speter    apr_dbd_t *apr_dbd;         /* parent DBD connection handle */
183251876Speter};
184251876Speter
185251876Speterstruct apr_dbd_prepared_t {
186251876Speter    SQLHANDLE stmt;             /* ODBC statement handle */
187251876Speter    SQLHANDLE dbc;              /* parent ODBC connection handle */
188251876Speter    apr_dbd_t *apr_dbd;
189251876Speter    int nargs;
190251876Speter    int nvals;
191251876Speter    int *types;                 /* array of DBD data types */
192251876Speter};
193251876Speter
194251876Speterstatic void odbc_lob_bucket_destroy(void *data);
195251876Speterstatic apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool);
196251876Speterstatic apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
197251876Speter                                         apr_size_t *len, apr_read_type_e block);
198251876Speter
199251876Speter/* the ODBC LOB bucket type */
200251876Speterstatic const apr_bucket_type_t odbc_bucket_type = {
201251876Speter    "ODBC_LOB", 5, APR_BUCKET_DATA,
202251876Speter    odbc_lob_bucket_destroy,
203251876Speter    odbc_lob_bucket_read,
204251876Speter    odbc_lob_bucket_setaside,
205251876Speter    apr_bucket_shared_split,
206251876Speter    apr_bucket_shared_copy
207251876Speter};
208251876Speter
209251876Speter/* ODBC LOB bucket data */
210251876Spetertypedef struct {
211251876Speter    /** Ref count for shared bucket */
212251876Speter    apr_bucket_refcount  refcount;
213251876Speter    const apr_dbd_row_t *row;
214251876Speter    int col;
215251876Speter    SQLSMALLINT type;
216251876Speter} odbc_bucket;
217251876Speter
218251876Speter/* SQL datatype mappings to DBD datatypes
219251876Speter * These tables must correspond *exactly* to the apr_dbd_type_e enum
220251876Speter * in apr_dbd.h
221251876Speter */
222251876Speter
223251876Speter/* ODBC "C" types to DBD datatypes  */
224251876Speterstatic SQLSMALLINT const sqlCtype[] = {
225251876Speter    SQL_C_DEFAULT,                  /* APR_DBD_TYPE_NONE              */
226251876Speter    SQL_C_STINYINT,                 /* APR_DBD_TYPE_TINY,       \%hhd */
227251876Speter    SQL_C_UTINYINT,                 /* APR_DBD_TYPE_UTINY,      \%hhu */
228251876Speter    SQL_C_SSHORT,                   /* APR_DBD_TYPE_SHORT,      \%hd  */
229251876Speter    SQL_C_USHORT,                   /* APR_DBD_TYPE_USHORT,     \%hu  */
230251876Speter    SQL_C_SLONG,                    /* APR_DBD_TYPE_INT,        \%d   */
231251876Speter    SQL_C_ULONG,                    /* APR_DBD_TYPE_UINT,       \%u   */
232251876Speter    SQL_C_SLONG,                    /* APR_DBD_TYPE_LONG,       \%ld  */
233251876Speter    SQL_C_ULONG,                    /* APR_DBD_TYPE_ULONG,      \%lu  */
234251876Speter    SQL_C_SBIGINT,                  /* APR_DBD_TYPE_LONGLONG,   \%lld */
235251876Speter    SQL_C_UBIGINT,                  /* APR_DBD_TYPE_ULONGLONG,  \%llu */
236251876Speter    SQL_C_FLOAT,                    /* APR_DBD_TYPE_FLOAT,      \%f   */
237251876Speter    SQL_C_DOUBLE,                   /* APR_DBD_TYPE_DOUBLE,     \%lf  */
238251876Speter    SQL_C_CHAR,                     /* APR_DBD_TYPE_STRING,     \%s   */
239251876Speter    SQL_C_CHAR,                     /* APR_DBD_TYPE_TEXT,       \%pDt */
240251876Speter    SQL_C_CHAR, /*SQL_C_TYPE_TIME,      APR_DBD_TYPE_TIME,       \%pDi */
241251876Speter    SQL_C_CHAR, /*SQL_C_TYPE_DATE,      APR_DBD_TYPE_DATE,       \%pDd */
242251876Speter    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_DATETIME,   \%pDa */
243251876Speter    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_TIMESTAMP,  \%pDs */
244251876Speter    SQL_C_CHAR, /*SQL_C_TYPE_TIMESTAMP, APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
245251876Speter    SQL_LONGVARBINARY,              /* APR_DBD_TYPE_BLOB,       \%pDb */
246251876Speter    SQL_LONGVARCHAR,                /* APR_DBD_TYPE_CLOB,       \%pDc */
247251876Speter    SQL_TYPE_NULL                   /* APR_DBD_TYPE_NULL        \%pDn */
248251876Speter};
249251876Speter#define NUM_APR_DBD_TYPES (sizeof(sqlCtype) / sizeof(sqlCtype[0]))
250251876Speter
251251876Speter/*  ODBC Base types to DBD datatypes */
252251876Speterstatic SQLSMALLINT const sqlBaseType[] = {
253251876Speter    SQL_C_DEFAULT,              /* APR_DBD_TYPE_NONE              */
254251876Speter    SQL_TINYINT,                /* APR_DBD_TYPE_TINY,       \%hhd */
255251876Speter    SQL_TINYINT,                /* APR_DBD_TYPE_UTINY,      \%hhu */
256251876Speter    SQL_SMALLINT,               /* APR_DBD_TYPE_SHORT,      \%hd  */
257251876Speter    SQL_SMALLINT,               /* APR_DBD_TYPE_USHORT,     \%hu  */
258251876Speter    SQL_INTEGER,                /* APR_DBD_TYPE_INT,        \%d   */
259251876Speter    SQL_INTEGER,                /* APR_DBD_TYPE_UINT,       \%u   */
260251876Speter    SQL_INTEGER,                /* APR_DBD_TYPE_LONG,       \%ld  */
261251876Speter    SQL_INTEGER,                /* APR_DBD_TYPE_ULONG,      \%lu  */
262251876Speter    SQL_BIGINT,                 /* APR_DBD_TYPE_LONGLONG,   \%lld */
263251876Speter    SQL_BIGINT,                 /* APR_DBD_TYPE_ULONGLONG,  \%llu */
264251876Speter    SQL_FLOAT,                  /* APR_DBD_TYPE_FLOAT,      \%f   */
265251876Speter    SQL_DOUBLE,                 /* APR_DBD_TYPE_DOUBLE,     \%lf  */
266251876Speter    SQL_CHAR,                   /* APR_DBD_TYPE_STRING,     \%s   */
267251876Speter    SQL_CHAR,                   /* APR_DBD_TYPE_TEXT,       \%pDt */
268251876Speter    SQL_CHAR, /*SQL_TIME,          APR_DBD_TYPE_TIME,       \%pDi */
269251876Speter    SQL_CHAR, /*SQL_DATE,          APR_DBD_TYPE_DATE,       \%pDd */
270251876Speter    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_DATETIME,   \%pDa */
271251876Speter    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_TIMESTAMP,  \%pDs */
272251876Speter    SQL_CHAR, /*SQL_TIMESTAMP,     APR_DBD_TYPE_ZTIMESTAMP, \%pDz */
273251876Speter    SQL_LONGVARBINARY,          /* APR_DBD_TYPE_BLOB,       \%pDb */
274251876Speter    SQL_LONGVARCHAR,            /* APR_DBD_TYPE_CLOB,       \%pDc */
275251876Speter    SQL_TYPE_NULL               /* APR_DBD_TYPE_NULL        \%pDn */
276251876Speter};
277251876Speter
278251876Speter/*  result sizes for DBD datatypes (-1 for null-terminated) */
279251876Speterstatic int const sqlSizes[] = {
280251876Speter    0,
281251876Speter    sizeof(char),               /**< \%hhd out: char* */
282251876Speter    sizeof(unsigned char),      /**< \%hhu out: unsigned char* */
283251876Speter    sizeof(short),              /**< \%hd  out: short* */
284251876Speter    sizeof(unsigned short),     /**< \%hu  out: unsigned short* */
285251876Speter    sizeof(int),                /**< \%d   out: int* */
286251876Speter    sizeof(unsigned int),       /**< \%u   out: unsigned int* */
287251876Speter    sizeof(long),               /**< \%ld  out: long* */
288251876Speter    sizeof(unsigned long),      /**< \%lu  out: unsigned long* */
289251876Speter    sizeof(apr_int64_t),        /**< \%lld out: apr_int64_t* */
290251876Speter    sizeof(apr_uint64_t),       /**< \%llu out: apr_uint64_t* */
291251876Speter    sizeof(float),              /**< \%f   out: float* */
292251876Speter    sizeof(double),             /**< \%lf  out: double* */
293251876Speter    -1,                         /**< \%s   out: char** */
294251876Speter    -1,                         /**< \%pDt out: char** */
295251876Speter    -1,                         /**< \%pDi out: char** */
296251876Speter    -1,                         /**< \%pDd out: char** */
297251876Speter    -1,                         /**< \%pDa out: char** */
298251876Speter    -1,                         /**< \%pDs out: char** */
299251876Speter    -1,                         /**< \%pDz out: char** */
300251876Speter    sizeof(apr_bucket_brigade), /**< \%pDb out: apr_bucket_brigade* */
301251876Speter    sizeof(apr_bucket_brigade), /**< \%pDc out: apr_bucket_brigade* */
302251876Speter    0                           /**< \%pDn : in: void*, out: void** */
303251876Speter};
304251876Speter
305251876Speter/*
306251876Speter * local functions
307251876Speter */
308251876Speter
309251876Speter/* close any open results for the connection */
310251876Speterstatic apr_status_t odbc_close_results(void *d)
311251876Speter{
312251876Speter    apr_dbd_results_t *dbr = (apr_dbd_results_t *)d;
313251876Speter    SQLRETURN rc = SQL_SUCCESS;
314251876Speter
315251876Speter    if (dbr && dbr->apr_dbd && dbr->apr_dbd->dbc) {
316251876Speter    	if (!dbr->isclosed)
317251876Speter            rc = SQLCloseCursor(dbr->stmt);
318251876Speter    	dbr->isclosed = 1;
319251876Speter    }
320251876Speter    return APR_FROM_SQL_RESULT(rc);
321251876Speter}
322251876Speter
323251876Speter/* close the ODBC statement handle from a  prepare */
324251876Speterstatic apr_status_t odbc_close_pstmt(void *s)
325251876Speter{
326251876Speter    SQLRETURN rc = APR_SUCCESS;
327251876Speter    apr_dbd_prepared_t *statement = s;
328251876Speter
329251876Speter    /* stmt is closed if connection has already been closed */
330251876Speter    if (statement) {
331251876Speter        SQLHANDLE hstmt = statement->stmt;
332251876Speter
333251876Speter        if (hstmt && statement->apr_dbd && statement->apr_dbd->dbc) {
334251876Speter            rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
335251876Speter        }
336251876Speter        statement->stmt = NULL;
337251876Speter    }
338251876Speter    return APR_FROM_SQL_RESULT(rc);
339251876Speter}
340251876Speter
341251876Speter/* close: close/release a connection obtained from open() */
342251876Speterstatic apr_status_t odbc_close(apr_dbd_t *handle)
343251876Speter{
344251876Speter    SQLRETURN rc = SQL_SUCCESS;
345251876Speter
346251876Speter    if (handle->dbc) {
347251876Speter        rc = SQLDisconnect(handle->dbc);
348251876Speter        CHECK_ERROR(handle, "SQLDisconnect", rc, SQL_HANDLE_DBC, handle->dbc);
349251876Speter        rc = SQLFreeHandle(SQL_HANDLE_DBC, handle->dbc);
350251876Speter        CHECK_ERROR(handle, "SQLFreeHandle (DBC)", rc, SQL_HANDLE_ENV, henv);
351251876Speter        handle->dbc = NULL;
352251876Speter    }
353251876Speter    return APR_FROM_SQL_RESULT(rc);
354251876Speter}
355251876Speter
356251876Speter/* odbc_close re-defined for passing to pool cleanup */
357251876Speterstatic apr_status_t odbc_close_cleanup(void *handle)
358251876Speter{
359251876Speter    return odbc_close((apr_dbd_t *)handle);
360251876Speter}
361251876Speter
362251876Speter/* close the ODBC environment handle at process termination */
363251876Speterstatic apr_status_t odbc_close_env(SQLHANDLE henv)
364251876Speter{
365251876Speter    SQLRETURN rc;
366251876Speter
367251876Speter    rc = SQLFreeHandle(SQL_HANDLE_ENV, henv);
368251876Speter    henv = NULL;
369251876Speter    return APR_FROM_SQL_RESULT(rc);
370251876Speter}
371251876Speter
372251876Speter/* setup the arrays in results for all the returned columns */
373251876Speterstatic SQLRETURN odbc_set_result_column(int icol, apr_dbd_results_t *res,
374251876Speter                                        SQLHANDLE stmt)
375251876Speter{
376251876Speter    SQLRETURN rc;
377289166Speter    ODBC_INTPTR_T maxsize, textsize, realsize, type, isunsigned = 1;
378251876Speter
379251876Speter    /* discover the sql type */
380251876Speter    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_UNSIGNED, NULL, 0, NULL,
381251876Speter                         (SQLPOINTER)&isunsigned);
382251876Speter    isunsigned = (isunsigned == SQL_TRUE);
383251876Speter
384251876Speter    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_TYPE, NULL, 0, NULL,
385251876Speter                         (SQLPOINTER)&type);
386251876Speter    if (!SQL_SUCCEEDED(rc) || type == SQL_UNKNOWN_TYPE) {
387251876Speter        /* MANY ODBC v2 datasources only supply CONCISE_TYPE */
388251876Speter        rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_CONCISE_TYPE, NULL,
389251876Speter                             0, NULL, (SQLPOINTER)&type);
390251876Speter    }
391251876Speter
392251876Speter    if (!SQL_SUCCEEDED(rc)) {
393251876Speter        /* if still unknown make it CHAR */
394251876Speter        type = SQL_C_CHAR;
395251876Speter    }
396251876Speter
397251876Speter    switch (type) {
398251876Speter    case SQL_INTEGER:
399251876Speter    case SQL_SMALLINT:
400251876Speter    case SQL_TINYINT:
401251876Speter    case SQL_BIGINT:
402251876Speter      /* fix these numeric binary types up as signed/unsigned for C types */
403251876Speter      type += (isunsigned) ? SQL_UNSIGNED_OFFSET : SQL_SIGNED_OFFSET;
404251876Speter      break;
405251876Speter    /* LOB types are not changed to C types */
406251876Speter    case SQL_LONGVARCHAR:
407251876Speter        type = SQL_LONGVARCHAR;
408251876Speter        break;
409251876Speter    case SQL_LONGVARBINARY:
410251876Speter        type = SQL_LONGVARBINARY;
411251876Speter        break;
412251876Speter    case SQL_FLOAT :
413251876Speter        type = SQL_C_FLOAT;
414251876Speter        break;
415251876Speter    case SQL_DOUBLE :
416251876Speter        type = SQL_C_DOUBLE;
417251876Speter        break;
418251876Speter
419251876Speter    /* DBD wants times as strings */
420251876Speter    case SQL_TIMESTAMP:
421251876Speter    case SQL_DATE:
422251876Speter    case SQL_TIME:
423251876Speter    default:
424251876Speter      type = SQL_C_CHAR;
425251876Speter    }
426251876Speter
427262253Speter    res->coltypes[icol] = (SQLSMALLINT)type;
428251876Speter
429251876Speter    /* size if retrieved as text */
430251876Speter    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0,
431251876Speter                         NULL, (SQLPOINTER)&textsize);
432251876Speter    if (!SQL_SUCCEEDED(rc) || textsize < 0) {
433251876Speter        textsize = res->apr_dbd->defaultBufferSize;
434251876Speter    }
435251876Speter    /* for null-term, which sometimes isn't included */
436251876Speter    textsize++;
437251876Speter
438251876Speter    /* real size */
439251876Speter    rc = SQLColAttribute(stmt, icol + 1, SQL_DESC_OCTET_LENGTH, NULL, 0,
440251876Speter                         NULL, (SQLPOINTER)&realsize);
441251876Speter    if (!SQL_SUCCEEDED(rc)) {
442251876Speter        realsize = textsize;
443251876Speter    }
444251876Speter
445251876Speter    maxsize = (textsize > realsize) ? textsize : realsize;
446251876Speter    if (IS_LOB(type) || maxsize <= 0) {
447251876Speter        /* LOB types are never bound and have a NULL colptr for binary.
448251876Speter         * Ingore their real (1-2gb) length & use a default - the larger
449251876Speter         * of defaultBufferSize or APR_BUCKET_BUFF_SIZE.
450251876Speter         * If not a LOB, but simply unknown length - always use defaultBufferSize.
451251876Speter         */
452251876Speter        maxsize = res->apr_dbd->defaultBufferSize;
453251876Speter        if (IS_LOB(type) && maxsize < APR_BUCKET_BUFF_SIZE) {
454251876Speter            maxsize = APR_BUCKET_BUFF_SIZE;
455251876Speter        }
456251876Speter
457251876Speter        res->colptrs[icol] =  NULL;
458251876Speter        res->colstate[icol] = COL_AVAIL;
459262253Speter        res->colsizes[icol] = (SQLINTEGER)maxsize;
460251876Speter        rc = SQL_SUCCESS;
461251876Speter    }
462251876Speter    else {
463251876Speter        res->colptrs[icol] = apr_pcalloc(res->pool, maxsize);
464262253Speter        res->colsizes[icol] = (SQLINTEGER)maxsize;
465251876Speter        if (res->apr_dbd->dboptions & SQL_GD_BOUND) {
466251876Speter            /* we are allowed to call SQLGetData if we need to */
467251876Speter            rc = SQLBindCol(stmt, icol + 1, res->coltypes[icol],
468251876Speter                            res->colptrs[icol], maxsize,
469251876Speter                            &(res->colinds[icol]));
470251876Speter            CHECK_ERROR(res->apr_dbd, "SQLBindCol", rc, SQL_HANDLE_STMT,
471251876Speter                        stmt);
472251876Speter            res->colstate[icol] = SQL_SUCCEEDED(rc) ? COL_BOUND : COL_AVAIL;
473251876Speter        }
474251876Speter        else {
475251876Speter            /* this driver won't allow us to call SQLGetData on bound
476251876Speter             * columns - so don't bind any
477251876Speter             */
478251876Speter            res->colstate[icol] = COL_AVAIL;
479251876Speter            rc = SQL_SUCCESS;
480251876Speter        }
481251876Speter    }
482251876Speter    return rc;
483251876Speter}
484251876Speter
485251876Speter/* create and populate an apr_dbd_results_t for a select */
486251876Speterstatic SQLRETURN odbc_create_results(apr_dbd_t *handle, SQLHANDLE hstmt,
487251876Speter                                     apr_pool_t *pool, const int random,
488251876Speter                                     apr_dbd_results_t **res)
489251876Speter{
490251876Speter    SQLRETURN rc;
491251876Speter    SQLSMALLINT ncols;
492251876Speter
493251876Speter    *res = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
494251876Speter    (*res)->stmt = hstmt;
495251876Speter    (*res)->dbc = handle->dbc;
496251876Speter    (*res)->pool = pool;
497251876Speter    (*res)->random = random;
498251876Speter    (*res)->apr_dbd = handle;
499251876Speter    rc = SQLNumResultCols(hstmt, &ncols);
500251876Speter    CHECK_ERROR(handle, "SQLNumResultCols", rc, SQL_HANDLE_STMT, hstmt);
501251876Speter    (*res)->ncols = ncols;
502251876Speter
503251876Speter    if (SQL_SUCCEEDED(rc)) {
504251876Speter        int i;
505251876Speter
506251876Speter        (*res)->colnames = apr_pcalloc(pool, ncols * sizeof(char *));
507251876Speter        (*res)->colptrs = apr_pcalloc(pool, ncols * sizeof(void *));
508251876Speter        (*res)->colsizes = apr_pcalloc(pool, ncols * sizeof(SQLINTEGER));
509251876Speter        (*res)->coltypes = apr_pcalloc(pool, ncols * sizeof(SQLSMALLINT));
510251876Speter        (*res)->colinds = apr_pcalloc(pool, ncols * sizeof(SQLLEN));
511251876Speter        (*res)->colstate = apr_pcalloc(pool, ncols * sizeof(int));
512251876Speter        (*res)->ncols = ncols;
513251876Speter
514251876Speter        for (i = 0; i < ncols; i++) {
515251876Speter            odbc_set_result_column(i, (*res), hstmt);
516251876Speter        }
517251876Speter    }
518251876Speter    return rc;
519251876Speter}
520251876Speter
521251876Speter
522251876Speter/* bind a parameter - input params only, does not support output parameters */
523251876Speterstatic SQLRETURN odbc_bind_param(apr_pool_t *pool,
524251876Speter                                 apr_dbd_prepared_t *statement, const int narg,
525251876Speter                                 const SQLSMALLINT type, int *argp,
526251876Speter                                 const void **args, const int textmode)
527251876Speter{
528251876Speter    SQLRETURN rc;
529251876Speter    SQLSMALLINT baseType, cType;
530251876Speter    void *ptr;
531251876Speter    SQLULEN len;
532251876Speter    SQLLEN *indicator;
533251876Speter    static SQLLEN nullValue = SQL_NULL_DATA;
534251876Speter    static SQLSMALLINT inOut = SQL_PARAM_INPUT;     /* only input params */
535251876Speter
536251876Speter    /* bind a NULL data value */
537251876Speter    if (args[*argp] == NULL || type == APR_DBD_TYPE_NULL) {
538251876Speter        baseType = SQL_CHAR;
539251876Speter        cType = SQL_C_CHAR;
540251876Speter        ptr = &nullValue;
541251876Speter        len = sizeof(SQLINTEGER);
542251876Speter        indicator = &nullValue;
543251876Speter        (*argp)++;
544251876Speter    }
545251876Speter    /* bind a non-NULL data value */
546251876Speter    else {
547251876Speter        if (type < 0 || type >= NUM_APR_DBD_TYPES) {
548251876Speter            return APR_EGENERAL;
549251876Speter        }
550251876Speter
551251876Speter        baseType = sqlBaseType[type];
552251876Speter        cType = sqlCtype[type];
553251876Speter        indicator = NULL;
554251876Speter        /* LOBs */
555251876Speter        if (IS_LOB(cType)) {
556251876Speter            ptr = (void *)args[*argp];
557251876Speter            len = (SQLULEN) * (apr_size_t *)args[*argp + 1];
558251876Speter            cType = (IS_CLOB(cType)) ? SQL_C_CHAR : SQL_C_DEFAULT;
559251876Speter            (*argp) += 4;  /* LOBs consume 4 args (last two are unused) */
560251876Speter        }
561251876Speter        /* non-LOBs */
562251876Speter        else {
563251876Speter            switch (baseType) {
564251876Speter            case SQL_CHAR:
565251876Speter            case SQL_DATE:
566251876Speter            case SQL_TIME:
567251876Speter            case SQL_TIMESTAMP:
568251876Speter                ptr = (void *)args[*argp];
569251876Speter                len = (SQLULEN)strlen(ptr);
570251876Speter                break;
571251876Speter            case SQL_TINYINT:
572251876Speter                ptr = apr_palloc(pool, sizeof(unsigned char));
573251876Speter                len = sizeof(unsigned char);
574251876Speter                *(unsigned char *)ptr =
575251876Speter                    (textmode ?
576251876Speter                     atoi(args[*argp]) : *(unsigned char *)args[*argp]);
577251876Speter                break;
578251876Speter            case SQL_SMALLINT:
579251876Speter                ptr = apr_palloc(pool, sizeof(short));
580251876Speter                len = sizeof(short);
581251876Speter                *(short *)ptr =
582251876Speter                    (textmode ? atoi(args[*argp]) : *(short *)args[*argp]);
583251876Speter                break;
584251876Speter            case SQL_INTEGER:
585251876Speter                ptr = apr_palloc(pool, sizeof(int));
586251876Speter                len = sizeof(int);
587251876Speter                *(long *)ptr =
588251876Speter                    (textmode ? atol(args[*argp]) : *(long *)args[*argp]);
589251876Speter                break;
590251876Speter            case SQL_FLOAT:
591251876Speter                ptr = apr_palloc(pool, sizeof(float));
592251876Speter                len = sizeof(float);
593251876Speter                *(float *)ptr =
594251876Speter                    (textmode ?
595251876Speter                     (float)atof(args[*argp]) : *(float *)args[*argp]);
596251876Speter                break;
597251876Speter            case SQL_DOUBLE:
598251876Speter                ptr = apr_palloc(pool, sizeof(double));
599251876Speter                len = sizeof(double);
600251876Speter                *(double *)ptr =
601251876Speter                    (textmode ? atof(args[*argp]) : *(double *)
602251876Speter                     args[*argp]);
603251876Speter                break;
604251876Speter            case SQL_BIGINT:
605251876Speter                ptr = apr_palloc(pool, sizeof(apr_int64_t));
606251876Speter                len = sizeof(apr_int64_t);
607251876Speter                *(apr_int64_t *)ptr =
608251876Speter                    (textmode ?
609251876Speter                     apr_atoi64(args[*argp]) : *(apr_int64_t *)args[*argp]);
610251876Speter                break;
611251876Speter            default:
612251876Speter                return APR_EGENERAL;
613251876Speter            }
614251876Speter            (*argp)++;          /* non LOBs consume one argument */
615251876Speter        }
616251876Speter    }
617251876Speter    rc = SQLBindParameter(statement->stmt, narg, inOut, cType,
618251876Speter                          baseType, len, 0, ptr, len, indicator);
619251876Speter    CHECK_ERROR(statement->apr_dbd, "SQLBindParameter", rc, SQL_HANDLE_STMT,
620251876Speter                statement->stmt);
621251876Speter    return rc;
622251876Speter}
623251876Speter
624251876Speter/* LOB / Bucket Brigade functions */
625251876Speter
626251876Speter/* bucket type specific destroy */
627251876Speterstatic void odbc_lob_bucket_destroy(void *data)
628251876Speter{
629251876Speter    odbc_bucket *bd = data;
630251876Speter
631251876Speter    if (apr_bucket_shared_destroy(bd))
632251876Speter        apr_bucket_free(bd);
633251876Speter}
634251876Speter
635251876Speter/* set aside a bucket if possible */
636251876Speterstatic apr_status_t odbc_lob_bucket_setaside(apr_bucket *e, apr_pool_t *pool)
637251876Speter{
638251876Speter    odbc_bucket *bd = (odbc_bucket *)e->data;
639251876Speter
640251876Speter    /* Unlikely - but if the row pool is ancestor of this pool then it is OK */
641251876Speter    if (apr_pool_is_ancestor(bd->row->pool, pool))
642251876Speter        return APR_SUCCESS;
643251876Speter
644251876Speter    return apr_bucket_setaside_notimpl(e, pool);
645251876Speter}
646251876Speter
647251876Speter/* split a bucket into a heap bucket followed by a LOB bkt w/remaining data */
648251876Speterstatic apr_status_t odbc_lob_bucket_read(apr_bucket *e, const char **str,
649251876Speter                                         apr_size_t *len, apr_read_type_e block)
650251876Speter{
651251876Speter    SQLRETURN rc;
652251876Speter    SQLLEN len_indicator;
653251876Speter    SQLSMALLINT type;
654251876Speter    odbc_bucket *bd = (odbc_bucket *)e->data;
655251876Speter    apr_bucket *nxt;
656251876Speter    void *buf;
657251876Speter    int bufsize = bd->row->res->apr_dbd->defaultBufferSize;
658251876Speter    int eos;
659251876Speter
660251876Speter    /* C type is CHAR for CLOBs, DEFAULT for BLOBs */
661251876Speter    type = bd->row->res->coltypes[bd->col];
662251876Speter    type = (type == SQL_LONGVARCHAR) ? SQL_C_CHAR : SQL_C_DEFAULT;
663251876Speter
664251876Speter    /* LOB buffers are always at least APR_BUCKET_BUFF_SIZE,
665251876Speter     *   but they may be much bigger per the BUFSIZE parameter.
666251876Speter     */
667251876Speter    if (bufsize < APR_BUCKET_BUFF_SIZE)
668251876Speter        bufsize = APR_BUCKET_BUFF_SIZE;
669251876Speter
670251876Speter    buf = apr_bucket_alloc(bufsize, e->list);
671251876Speter    *str = NULL;
672251876Speter    *len = 0;
673251876Speter
674251876Speter    rc = SQLGetData(bd->row->res->stmt, bd->col + 1,
675251876Speter                    type, buf, bufsize,
676251876Speter                    &len_indicator);
677251876Speter
678251876Speter    CHECK_ERROR(bd->row->res->apr_dbd, "SQLGetData", rc,
679251876Speter                SQL_HANDLE_STMT, bd->row->res->stmt);
680251876Speter
681251876Speter    if (rc == SQL_NO_DATA || len_indicator == SQL_NULL_DATA || len_indicator < 0)
682251876Speter        len_indicator = 0;
683251876Speter
684251876Speter    if (SQL_SUCCEEDED(rc) || rc == SQL_NO_DATA) {
685251876Speter
686251876Speter        if (rc == SQL_SUCCESS_WITH_INFO
687251876Speter            && (len_indicator == SQL_NO_TOTAL || len_indicator >= bufsize)) {
688251876Speter            /* not the last read = a full buffer. CLOBs have a null terminator */
689251876Speter            *len = bufsize - (IS_CLOB(bd->type) ? 1 : 0 );
690251876Speter
691251876Speter             eos = 0;
692251876Speter        }
693251876Speter        else {
694251876Speter            /* the last read - len_indicator is supposed to be the length,
695251876Speter             * but some driver get this wrong and return the total length.
696251876Speter             * We try to handle both interpretations.
697251876Speter             */
698251876Speter            *len =  (len_indicator > bufsize
699251876Speter                     && len_indicator >= (SQLLEN)e->start)
700251876Speter                ? (len_indicator - (SQLLEN)e->start) : len_indicator;
701251876Speter
702251876Speter            eos = 1;
703251876Speter        }
704251876Speter
705251876Speter        if (!eos) {
706251876Speter            /* Create a new LOB bucket to append and append it */
707251876Speter            nxt = apr_bucket_alloc(sizeof(apr_bucket *), e->list);
708251876Speter            APR_BUCKET_INIT(nxt);
709251876Speter            nxt->length = -1;
710251876Speter            nxt->data   = e->data;
711251876Speter            nxt->type   = &odbc_bucket_type;
712251876Speter            nxt->free   = apr_bucket_free;
713251876Speter            nxt->list   = e->list;
714251876Speter            nxt->start  = e->start + *len;
715251876Speter            APR_BUCKET_INSERT_AFTER(e, nxt);
716251876Speter        }
717251876Speter        else {
718251876Speter            odbc_lob_bucket_destroy(e->data);
719251876Speter        }
720251876Speter        /* make current bucket into a heap bucket */
721251876Speter        apr_bucket_heap_make(e, buf, *len, apr_bucket_free);
722251876Speter        *str = buf;
723251876Speter
724251876Speter        /* No data is success in this context */
725251876Speter        rc = SQL_SUCCESS;
726251876Speter    }
727251876Speter    return APR_FROM_SQL_RESULT(rc);
728251876Speter}
729251876Speter
730251876Speter/* Create a bucket brigade on the row pool for a LOB column */
731251876Speterstatic apr_status_t odbc_create_bucket(const apr_dbd_row_t *row, const int col,
732251876Speter                                       SQLSMALLINT type, apr_bucket_brigade *bb)
733251876Speter{
734251876Speter    apr_bucket_alloc_t *list = bb->bucket_alloc;
735251876Speter    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
736251876Speter    odbc_bucket *bd = apr_bucket_alloc(sizeof(odbc_bucket), list);
737251876Speter    apr_bucket *eos = apr_bucket_eos_create(list);
738251876Speter
739251876Speter    bd->row = row;
740251876Speter    bd->col = col;
741251876Speter    bd->type = type;
742251876Speter
743251876Speter    APR_BUCKET_INIT(b);
744251876Speter    b->type = &odbc_bucket_type;
745251876Speter    b->free = apr_bucket_free;
746251876Speter    b->list = list;
747251876Speter    /* LOB lengths are unknown in ODBC */
748251876Speter    b = apr_bucket_shared_make(b, bd, 0, -1);
749251876Speter
750251876Speter    APR_BRIGADE_INSERT_TAIL(bb, b);
751251876Speter    APR_BRIGADE_INSERT_TAIL(bb, eos);
752251876Speter
753251876Speter    return APR_SUCCESS;
754251876Speter}
755251876Speter
756251876Speter/* returns a data pointer for a column,  returns NULL for NULL value,
757251876Speter * return -1 if data not available
758251876Speter */
759251876Speterstatic void *odbc_get(const apr_dbd_row_t *row, const int col,
760251876Speter                      const SQLSMALLINT sqltype)
761251876Speter{
762251876Speter    SQLRETURN rc;
763251876Speter    SQLLEN indicator;
764251876Speter    int state = row->res->colstate[col];
765289166Speter    ODBC_INTPTR_T options = row->res->apr_dbd->dboptions;
766251876Speter
767251876Speter    switch (state) {
768251876Speter    case (COL_UNAVAIL):
769251876Speter        return (void *)-1;
770251876Speter    case (COL_RETRIEVED):
771251876Speter        return NULL;
772251876Speter
773251876Speter    case (COL_BOUND):
774251876Speter    case (COL_PRESENT):
775251876Speter        if (sqltype == row->res->coltypes[col]) {
776251876Speter            /* same type and we already have the data */
777251876Speter            row->res->colstate[col] = COL_RETRIEVED;
778251876Speter            return (row->res->colinds[col] == SQL_NULL_DATA) ?
779251876Speter                NULL : row->res->colptrs[col];
780251876Speter        }
781251876Speter    }
782251876Speter
783251876Speter    /* we need to get the data now */
784251876Speter    if (!(options & SQL_GD_ANY_ORDER)) {
785251876Speter        /* this ODBC driver requires columns to be retrieved in order,
786251876Speter         * so we attempt to get every prior un-gotten non-LOB column
787251876Speter         */
788251876Speter        int i;
789251876Speter        for (i = 0; i < col; i++) {
790251876Speter            if (row->res->colstate[i] == COL_AVAIL) {
791251876Speter                if (IS_LOB(row->res->coltypes[i]))
792251876Speter                       row->res->colstate[i] = COL_UNAVAIL;
793251876Speter                else {
794251876Speter                    odbc_get(row, i, row->res->coltypes[i]);
795251876Speter                    row->res->colstate[i] = COL_PRESENT;
796251876Speter                }
797251876Speter            }
798251876Speter        }
799251876Speter    }
800251876Speter
801251876Speter    if ((state == COL_BOUND && !(options & SQL_GD_BOUND)))
802251876Speter        /* this driver won't let us re-get bound columns */
803251876Speter        return (void *)-1;
804251876Speter
805251876Speter    /* a LOB might not have a buffer allocated yet - so create one */
806251876Speter    if (!row->res->colptrs[col])
807251876Speter        row->res->colptrs[col] = apr_pcalloc(row->pool, row->res->colsizes[col]);
808251876Speter
809251876Speter    rc = SQLGetData(row->res->stmt, col + 1, sqltype, row->res->colptrs[col],
810251876Speter                    row->res->colsizes[col], &indicator);
811251876Speter    CHECK_ERROR(row->res->apr_dbd, "SQLGetData", rc, SQL_HANDLE_STMT,
812251876Speter                row->res->stmt);
813251876Speter    if (indicator == SQL_NULL_DATA || rc == SQL_NO_DATA)
814251876Speter        return NULL;
815251876Speter
816251876Speter    if (SQL_SUCCEEDED(rc)) {
817251876Speter        /* whatever it was originally, it is now this sqltype */
818251876Speter        row->res->coltypes[col] = sqltype;
819251876Speter        /* this allows getting CLOBs in text mode by calling get_entry
820251876Speter         *   until it returns NULL
821251876Speter         */
822251876Speter        row->res->colstate[col] =
823251876Speter            (rc == SQL_SUCCESS_WITH_INFO) ? COL_AVAIL : COL_RETRIEVED;
824251876Speter        return row->res->colptrs[col];
825251876Speter    }
826251876Speter    else
827251876Speter        return (void *)-1;
828251876Speter}
829251876Speter
830251876Speter/* Parse the parameter string for open */
831251876Speterstatic apr_status_t odbc_parse_params(apr_pool_t *pool, const char *params,
832251876Speter                               int *connect, SQLCHAR **datasource,
833251876Speter                               SQLCHAR **user, SQLCHAR **password,
834251876Speter                               int *defaultBufferSize, int *nattrs,
835289166Speter                               int **attrs, ODBC_INTPTR_T **attrvals)
836251876Speter{
837251876Speter    char *seps, *last, *next, *name[MAX_PARAMS], *val[MAX_PARAMS];
838251876Speter    int nparams = 0, i, j;
839251876Speter
840251876Speter    *attrs = apr_pcalloc(pool, MAX_PARAMS * sizeof(char *));
841289166Speter    *attrvals = apr_pcalloc(pool, MAX_PARAMS * sizeof(ODBC_INTPTR_T));
842251876Speter    *nattrs = 0;
843251876Speter    seps = DEFAULTSEPS;
844251876Speter    name[nparams] = apr_strtok(apr_pstrdup(pool, params), seps, &last);
845251876Speter
846251876Speter    /* no params is OK here - let connect return a more useful error msg */
847251876Speter    if (!name[nparams])
848251876Speter        return SQL_SUCCESS;
849251876Speter
850251876Speter    do {
851251876Speter        if (last[strspn(last, seps)] == CSINGLEQUOTE) {
852251876Speter            last += strspn(last, seps);
853251876Speter            seps=SSINGLEQUOTE;
854251876Speter        }
855251876Speter        val[nparams] = apr_strtok(NULL, seps, &last);
856251876Speter        seps = DEFAULTSEPS;
857251876Speter
858251876Speter        ++nparams;
859251876Speter        next = apr_strtok(NULL, seps, &last);
860251876Speter        if (!next) {
861251876Speter            break;
862251876Speter        }
863251876Speter        if (nparams >= MAX_PARAMS) {
864251876Speter            /* too many parameters, no place to store */
865251876Speter            return APR_EGENERAL;
866251876Speter        }
867251876Speter        name[nparams] = next;
868251876Speter    } while (1);
869251876Speter
870251876Speter    for (j = i = 0; i < nparams; i++) {
871251876Speter        if (!apr_strnatcasecmp(name[i], "CONNECT")) {
872251876Speter            *datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
873251876Speter            *connect = 1;
874251876Speter        }
875251876Speter        else if (!apr_strnatcasecmp(name[i], "DATASOURCE")) {
876251876Speter            *datasource = (SQLCHAR *)apr_pstrdup(pool, val[i]);
877251876Speter            *connect = 0;
878251876Speter        }
879251876Speter        else if (!apr_strnatcasecmp(name[i], "USER")) {
880251876Speter            *user = (SQLCHAR *)apr_pstrdup(pool, val[i]);
881251876Speter        }
882251876Speter        else if (!apr_strnatcasecmp(name[i], "PASSWORD")) {
883251876Speter            *password = (SQLCHAR *)apr_pstrdup(pool, val[i]);
884251876Speter        }
885251876Speter        else if (!apr_strnatcasecmp(name[i], "BUFSIZE")) {
886251876Speter            *defaultBufferSize = atoi(val[i]);
887251876Speter        }
888251876Speter        else if (!apr_strnatcasecmp(name[i], "ACCESS")) {
889251876Speter            if (!apr_strnatcasecmp(val[i], "READ_ONLY"))
890251876Speter                (*attrvals)[j] = SQL_MODE_READ_ONLY;
891251876Speter            else if (!apr_strnatcasecmp(val[i], "READ_WRITE"))
892251876Speter                (*attrvals)[j] = SQL_MODE_READ_WRITE;
893251876Speter            else
894251876Speter                return SQL_ERROR;
895251876Speter            (*attrs)[j++] = SQL_ATTR_ACCESS_MODE;
896251876Speter        }
897251876Speter        else if (!apr_strnatcasecmp(name[i], "CTIMEOUT")) {
898251876Speter            (*attrvals)[j] = atoi(val[i]);
899251876Speter            (*attrs)[j++] = SQL_ATTR_LOGIN_TIMEOUT;
900251876Speter        }
901251876Speter        else if (!apr_strnatcasecmp(name[i], "STIMEOUT")) {
902251876Speter            (*attrvals)[j] = atoi(val[i]);
903251876Speter            (*attrs)[j++] = SQL_ATTR_CONNECTION_TIMEOUT;
904251876Speter        }
905251876Speter        else if (!apr_strnatcasecmp(name[i], "TXMODE")) {
906251876Speter            if (!apr_strnatcasecmp(val[i], "READ_UNCOMMITTED"))
907251876Speter                (*attrvals)[j] = SQL_TXN_READ_UNCOMMITTED;
908251876Speter            else if (!apr_strnatcasecmp(val[i], "READ_COMMITTED"))
909251876Speter                (*attrvals)[j] = SQL_TXN_READ_COMMITTED;
910251876Speter            else if (!apr_strnatcasecmp(val[i], "REPEATABLE_READ"))
911251876Speter                (*attrvals)[j] = SQL_TXN_REPEATABLE_READ;
912251876Speter            else if (!apr_strnatcasecmp(val[i], "SERIALIZABLE"))
913251876Speter                (*attrvals)[j] = SQL_TXN_SERIALIZABLE;
914251876Speter            else if (!apr_strnatcasecmp(val[i], "DEFAULT"))
915251876Speter                continue;
916251876Speter            else
917251876Speter                return SQL_ERROR;
918251876Speter            (*attrs)[j++] = SQL_ATTR_TXN_ISOLATION;
919251876Speter        }
920251876Speter        else
921251876Speter            return SQL_ERROR;
922251876Speter    }
923251876Speter    *nattrs = j;
924251876Speter    return (*datasource && *defaultBufferSize) ? APR_SUCCESS : SQL_ERROR;
925251876Speter}
926251876Speter
927251876Speter/* common handling after ODBC calls - save error info (code and text) in dbc */
928251876Speterstatic void check_error(apr_dbd_t *dbc, const char *step, SQLRETURN rc,
929251876Speter                 SQLSMALLINT type, SQLHANDLE h, int line)
930251876Speter{
931251876Speter    SQLCHAR buffer[512];
932251876Speter    SQLCHAR sqlstate[128];
933251876Speter    SQLINTEGER native;
934251876Speter    SQLSMALLINT reslength;
935251876Speter    char *res, *p, *end, *logval = NULL;
936251876Speter    int i;
937251876Speter
938251876Speter    /* set info about last error in dbc  - fast return for SQL_SUCCESS  */
939251876Speter    if (rc == SQL_SUCCESS) {
940251876Speter        char successMsg[] = "[dbd_odbc] SQL_SUCCESS ";
941251876Speter        apr_size_t successMsgLen = sizeof successMsg - 1;
942251876Speter
943251876Speter        dbc->lasterrorcode = SQL_SUCCESS;
944251876Speter        apr_cpystrn(dbc->lastError, successMsg, sizeof dbc->lastError);
945251876Speter        apr_cpystrn(dbc->lastError + successMsgLen, step,
946251876Speter                    sizeof dbc->lastError - successMsgLen);
947251876Speter        return;
948251876Speter    }
949251876Speter    switch (rc) {
950251876Speter    case SQL_INVALID_HANDLE:
951251876Speter        res = "SQL_INVALID_HANDLE";
952251876Speter        break;
953251876Speter    case SQL_ERROR:
954251876Speter        res = "SQL_ERROR";
955251876Speter        break;
956251876Speter    case SQL_SUCCESS_WITH_INFO:
957251876Speter        res = "SQL_SUCCESS_WITH_INFO";
958251876Speter        break;
959251876Speter    case SQL_STILL_EXECUTING:
960251876Speter        res = "SQL_STILL_EXECUTING";
961251876Speter        break;
962251876Speter    case SQL_NEED_DATA:
963251876Speter        res = "SQL_NEED_DATA";
964251876Speter        break;
965251876Speter    case SQL_NO_DATA:
966251876Speter        res = "SQL_NO_DATA";
967251876Speter        break;
968251876Speter    default:
969251876Speter        res = "unrecognized SQL return code";
970251876Speter    }
971251876Speter    /* these two returns are expected during normal execution */
972251876Speter    if (rc != SQL_SUCCESS_WITH_INFO && rc != SQL_NO_DATA
973251876Speter        && dbc->can_commit != APR_DBD_TRANSACTION_IGNORE_ERRORS) {
974251876Speter        dbc->can_commit = APR_DBD_TRANSACTION_ROLLBACK;
975251876Speter    }
976251876Speter    p = dbc->lastError;
977251876Speter    end = p + sizeof(dbc->lastError);
978251876Speter    dbc->lasterrorcode = rc;
979251876Speter    p += sprintf(p, "[dbd_odbc] %.64s returned %.30s (%d) at %.24s:%d ",
980251876Speter                 step, res, rc, SOURCE_FILE, line - 1);
981251876Speter    for (i = 1, rc = 0; rc == 0; i++) {
982251876Speter        rc = SQLGetDiagRec(type, h, i, sqlstate, &native, buffer,
983251876Speter                            sizeof(buffer), &reslength);
984251876Speter        if (SQL_SUCCEEDED(rc) && (p < (end - 280)))
985251876Speter            p += sprintf(p, "%.256s %.20s ", buffer, sqlstate);
986251876Speter    }
987251876Speter    apr_env_get(&logval, "apr_dbd_odbc_log", dbc->pool);
988251876Speter    /* if env var was set or call was init/open (no dbname) - log to stderr */
989251876Speter    if (logval || !dbc->dbname ) {
990251876Speter        char timestamp[APR_CTIME_LEN];
991251876Speter
992251876Speter        apr_file_t *se;
993251876Speter        apr_ctime(timestamp, apr_time_now());
994251876Speter        apr_file_open_stderr(&se, dbc->pool);
995251876Speter        apr_file_printf(se, "[%s] %s\n", timestamp, dbc->lastError);
996251876Speter    }
997251876Speter}
998251876Speter
999251876Speterstatic APR_INLINE int odbc_check_rollback(apr_dbd_t *handle)
1000251876Speter{
1001251876Speter    if (handle->can_commit == APR_DBD_TRANSACTION_ROLLBACK) {
1002251876Speter        handle->lasterrorcode = SQL_ERROR;
1003251876Speter        apr_cpystrn(handle->lastError, "[dbd_odbc] Rollback pending ",
1004251876Speter                    sizeof handle->lastError);
1005251876Speter        return 1;
1006251876Speter    }
1007251876Speter    return 0;
1008251876Speter}
1009251876Speter
1010251876Speter/*
1011251876Speter *   public functions per DBD driver API
1012251876Speter */
1013251876Speter
1014251876Speter/** init: allow driver to perform once-only initialisation. **/
1015251876Speterstatic void odbc_init(apr_pool_t *pool)
1016251876Speter{
1017251876Speter    SQLRETURN rc;
1018251876Speter    char *step;
1019251876Speter    apr_version_t apuver;
1020251876Speter
1021251876Speter    apu_version(&apuver);
1022251876Speter    if (apuver.major != DRIVER_APU_VERSION_MAJOR
1023251876Speter        || apuver.minor != DRIVER_APU_VERSION_MINOR) {
1024251876Speter            apr_file_t *se;
1025251876Speter
1026251876Speter            apr_file_open_stderr(&se, pool);
1027251876Speter            apr_file_printf(se, "Incorrect " ODBC_DRIVER_STRING " dbd driver version\n"
1028251876Speter                "Attempt to load APU version %d.%d driver with APU version %d.%d\n",
1029251876Speter                DRIVER_APU_VERSION_MAJOR, DRIVER_APU_VERSION_MINOR,
1030251876Speter                apuver.major, apuver.minor);
1031251876Speter        abort();
1032251876Speter    }
1033251876Speter
1034251876Speter    if (henv)
1035251876Speter        return;
1036251876Speter
1037251876Speter    step = "SQLAllocHandle (SQL_HANDLE_ENV)";
1038251876Speter    rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
1039251876Speter    apr_pool_cleanup_register(pool, henv, odbc_close_env, apr_pool_cleanup_null);
1040251876Speter    if (SQL_SUCCEEDED(rc)) {
1041251876Speter        step = "SQLSetEnvAttr";
1042251876Speter        rc = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
1043251876Speter                          (SQLPOINTER)SQL_OV_ODBC3, 0);
1044251876Speter    }
1045251876Speter    else {
1046251876Speter        apr_dbd_t tmp_dbc;
1047251876Speter        SQLHANDLE err_h = henv;
1048251876Speter
1049251876Speter        tmp_dbc.pool = pool;
1050251876Speter        tmp_dbc.dbname = NULL;
1051251876Speter        CHECK_ERROR(&tmp_dbc, step, rc, SQL_HANDLE_ENV, err_h);
1052251876Speter    }
1053251876Speter}
1054251876Speter
1055251876Speter/** native_handle: return the native database handle of the underlying db **/
1056251876Speterstatic void *odbc_native_handle(apr_dbd_t *handle)
1057251876Speter{
1058251876Speter    return handle->dbc;
1059251876Speter}
1060251876Speter
1061251876Speter/** open: obtain a database connection from the server rec. **/
1062251876Speter
1063251876Speter/* It would be more efficient to allocate a single statement handle
1064251876Speter * here - but SQL_ATTR_CURSOR_SCROLLABLE must be set before
1065251876Speter * SQLPrepare, and we don't know whether random-access is
1066251876Speter * specified until SQLExecute so we cannot.
1067251876Speter */
1068251876Speter
1069251876Speterstatic apr_dbd_t *odbc_open(apr_pool_t *pool, const char *params, const char **error)
1070251876Speter{
1071251876Speter    SQLRETURN rc;
1072251876Speter    SQLHANDLE hdbc = NULL;
1073251876Speter    apr_dbd_t *handle;
1074251876Speter    char *err_step;
1075251876Speter    int err_htype, i;
1076251876Speter    int defaultBufferSize = DEFAULT_BUFFER_SIZE;
1077251876Speter    SQLHANDLE err_h = NULL;
1078251876Speter    SQLCHAR  *datasource = (SQLCHAR *)"", *user = (SQLCHAR *)"",
1079251876Speter             *password = (SQLCHAR *)"";
1080262253Speter    int nattrs = 0, *attrs = NULL,  connect = 0;
1081289166Speter    ODBC_INTPTR_T *attrvals = NULL;
1082251876Speter
1083251876Speter    err_step = "SQLAllocHandle (SQL_HANDLE_DBC)";
1084251876Speter    err_htype = SQL_HANDLE_ENV;
1085251876Speter    err_h = henv;
1086251876Speter    rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
1087251876Speter    if (SQL_SUCCEEDED(rc)) {
1088251876Speter        err_step = "Invalid DBD Parameters - open";
1089251876Speter        err_htype = SQL_HANDLE_DBC;
1090251876Speter        err_h = hdbc;
1091251876Speter        rc = odbc_parse_params(pool, params, &connect, &datasource, &user,
1092251876Speter                               &password, &defaultBufferSize, &nattrs, &attrs,
1093251876Speter                               &attrvals);
1094251876Speter    }
1095251876Speter    if (SQL_SUCCEEDED(rc)) {
1096251876Speter        for (i = 0; i < nattrs && SQL_SUCCEEDED(rc); i++) {
1097251876Speter            err_step = "SQLSetConnectAttr (from DBD Parameters)";
1098251876Speter            err_htype = SQL_HANDLE_DBC;
1099251876Speter            err_h = hdbc;
1100251876Speter            rc = SQLSetConnectAttr(hdbc, attrs[i], (SQLPOINTER)attrvals[i], 0);
1101251876Speter        }
1102251876Speter    }
1103251876Speter    if (SQL_SUCCEEDED(rc)) {
1104251876Speter        if (connect) {
1105251876Speter            SQLCHAR out[1024];
1106251876Speter            SQLSMALLINT outlen;
1107251876Speter
1108251876Speter            err_step = "SQLDriverConnect";
1109251876Speter            err_htype = SQL_HANDLE_DBC;
1110251876Speter            err_h = hdbc;
1111251876Speter            rc = SQLDriverConnect(hdbc, NULL, datasource,
1112251876Speter                        (SQLSMALLINT)strlen((char *)datasource),
1113251876Speter                        out, sizeof(out), &outlen, SQL_DRIVER_NOPROMPT);
1114251876Speter        }
1115251876Speter        else {
1116251876Speter            err_step = "SQLConnect";
1117251876Speter            err_htype = SQL_HANDLE_DBC;
1118251876Speter            err_h = hdbc;
1119251876Speter            rc = SQLConnect(hdbc, datasource,
1120251876Speter                        (SQLSMALLINT)strlen((char *)datasource),
1121251876Speter                        user, (SQLSMALLINT)strlen((char *)user),
1122251876Speter                        password, (SQLSMALLINT)strlen((char *)password));
1123251876Speter        }
1124251876Speter    }
1125251876Speter    if (SQL_SUCCEEDED(rc)) {
1126251876Speter        handle = apr_pcalloc(pool, sizeof(apr_dbd_t));
1127251876Speter        handle->dbname = apr_pstrdup(pool, (char *)datasource);
1128251876Speter        handle->dbc = hdbc;
1129251876Speter        handle->pool = pool;
1130251876Speter        handle->defaultBufferSize = defaultBufferSize;
1131251876Speter        CHECK_ERROR(handle, "SQLConnect", rc, SQL_HANDLE_DBC, handle->dbc);
1132251876Speter        handle->default_transaction_mode = 0;
1133251876Speter        handle->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
1134251876Speter        SQLGetInfo(hdbc, SQL_DEFAULT_TXN_ISOLATION,
1135289166Speter                   &(handle->default_transaction_mode), sizeof(ODBC_INTPTR_T), NULL);
1136251876Speter        handle->transaction_mode = handle->default_transaction_mode;
1137251876Speter        SQLGetInfo(hdbc, SQL_GETDATA_EXTENSIONS ,&(handle->dboptions),
1138289166Speter                   sizeof(ODBC_INTPTR_T), NULL);
1139251876Speter        apr_pool_cleanup_register(pool, handle, odbc_close_cleanup, apr_pool_cleanup_null);
1140251876Speter        return handle;
1141251876Speter    }
1142251876Speter    else {
1143251876Speter        apr_dbd_t tmp_dbc;
1144251876Speter
1145251876Speter        tmp_dbc.pool = pool;
1146251876Speter        tmp_dbc.dbname = NULL;
1147251876Speter        CHECK_ERROR(&tmp_dbc, err_step, rc, err_htype, err_h);
1148251876Speter        if (error)
1149251876Speter            *error = apr_pstrdup(pool, tmp_dbc.lastError);
1150251876Speter        if (hdbc)
1151251876Speter            SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
1152251876Speter        return NULL;
1153251876Speter    }
1154251876Speter}
1155251876Speter
1156251876Speter/** check_conn: check status of a database connection **/
1157251876Speterstatic apr_status_t odbc_check_conn(apr_pool_t *pool, apr_dbd_t *handle)
1158251876Speter{
1159251876Speter    SQLUINTEGER isDead;
1160251876Speter    SQLRETURN   rc;
1161251876Speter
1162251876Speter    rc = SQLGetConnectAttr(handle->dbc, SQL_ATTR_CONNECTION_DEAD, &isDead,
1163251876Speter                            sizeof(SQLUINTEGER), NULL);
1164251876Speter    CHECK_ERROR(handle, "SQLGetConnectAttr (SQL_ATTR_CONNECTION_DEAD)", rc,
1165251876Speter                SQL_HANDLE_DBC, handle->dbc);
1166251876Speter    /* if driver cannot check connection, say so */
1167251876Speter    if (rc != SQL_SUCCESS)
1168251876Speter        return APR_ENOTIMPL;
1169251876Speter
1170251876Speter    return (isDead == SQL_CD_FALSE) ? APR_SUCCESS : APR_EGENERAL;
1171251876Speter}
1172251876Speter
1173251876Speter/** set_dbname: select database name.  May be a no-op if not supported. **/
1174251876Speterstatic int odbc_set_dbname(apr_pool_t*pool, apr_dbd_t *handle,
1175251876Speter                           const char *name)
1176251876Speter{
1177251876Speter    if (apr_strnatcmp(name, handle->dbname)) {
1178251876Speter        return APR_EGENERAL;        /* It's illegal to change dbname in ODBC */
1179251876Speter    }
1180251876Speter    CHECK_ERROR(handle, "set_dbname (no-op)", SQL_SUCCESS, SQL_HANDLE_DBC,
1181251876Speter                handle->dbc);
1182251876Speter    return APR_SUCCESS;             /* OK if it's the same name */
1183251876Speter}
1184251876Speter
1185251876Speter/** transaction: start a transaction.  May be a no-op. **/
1186251876Speterstatic int odbc_start_transaction(apr_pool_t *pool, apr_dbd_t *handle,
1187251876Speter                                  apr_dbd_transaction_t **trans)
1188251876Speter{
1189251876Speter    SQLRETURN rc = SQL_SUCCESS;
1190251876Speter
1191251876Speter    if (handle->transaction_mode) {
1192251876Speter        rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_TXN_ISOLATION,
1193251876Speter                               (SQLPOINTER)handle->transaction_mode, 0);
1194251876Speter        CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_TXN_ISOLATION)", rc,
1195251876Speter                    SQL_HANDLE_DBC, handle->dbc);
1196251876Speter    }
1197251876Speter    if (SQL_SUCCEEDED(rc)) {
1198251876Speter        /* turn off autocommit for transactions */
1199251876Speter        rc = SQLSetConnectAttr(handle->dbc, SQL_ATTR_AUTOCOMMIT,
1200251876Speter                               SQL_AUTOCOMMIT_OFF, 0);
1201251876Speter        CHECK_ERROR(handle, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)", rc,
1202251876Speter                    SQL_HANDLE_DBC, handle->dbc);
1203251876Speter    }
1204251876Speter    if (SQL_SUCCEEDED(rc)) {
1205251876Speter        *trans = apr_palloc(pool, sizeof(apr_dbd_transaction_t));
1206251876Speter        (*trans)->dbc = handle->dbc;
1207251876Speter        (*trans)->apr_dbd = handle;
1208251876Speter    }
1209251876Speter    handle->can_commit = APR_DBD_TRANSACTION_COMMIT;
1210251876Speter    return APR_FROM_SQL_RESULT(rc);
1211251876Speter}
1212251876Speter
1213251876Speter/** end_transaction: end a transaction **/
1214251876Speterstatic int odbc_end_transaction(apr_dbd_transaction_t *trans)
1215251876Speter{
1216251876Speter    SQLRETURN rc;
1217251876Speter    int action = (trans->apr_dbd->can_commit != APR_DBD_TRANSACTION_ROLLBACK)
1218251876Speter        ? SQL_COMMIT : SQL_ROLLBACK;
1219251876Speter
1220251876Speter    rc = SQLEndTran(SQL_HANDLE_DBC, trans->dbc, action);
1221251876Speter    CHECK_ERROR(trans->apr_dbd, "SQLEndTran", rc, SQL_HANDLE_DBC, trans->dbc);
1222251876Speter    if (SQL_SUCCEEDED(rc)) {
1223251876Speter        rc = SQLSetConnectAttr(trans->dbc, SQL_ATTR_AUTOCOMMIT,
1224251876Speter                               (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
1225251876Speter        CHECK_ERROR(trans->apr_dbd, "SQLSetConnectAttr (SQL_ATTR_AUTOCOMMIT)",
1226251876Speter                    rc, SQL_HANDLE_DBC, trans->dbc);
1227251876Speter    }
1228251876Speter    trans->apr_dbd->can_commit = APR_DBD_TRANSACTION_IGNORE_ERRORS;
1229251876Speter    return APR_FROM_SQL_RESULT(rc);
1230251876Speter}
1231251876Speter
1232251876Speter/** query: execute an SQL statement which doesn't return a result set **/
1233251876Speterstatic int odbc_query(apr_dbd_t *handle, int *nrows, const char *statement)
1234251876Speter{
1235251876Speter    SQLRETURN rc;
1236251876Speter    SQLHANDLE hstmt = NULL;
1237251876Speter    size_t len = strlen(statement);
1238251876Speter
1239251876Speter    if (odbc_check_rollback(handle))
1240251876Speter        return APR_EGENERAL;
1241251876Speter
1242251876Speter    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
1243251876Speter    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
1244251876Speter                handle->dbc);
1245251876Speter    if (!SQL_SUCCEEDED(rc))
1246251876Speter        return APR_FROM_SQL_RESULT(rc);
1247251876Speter
1248251876Speter    rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
1249251876Speter    CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
1250251876Speter
1251251876Speter    if (SQL_SUCCEEDED(rc)) {
1252251876Speter        SQLLEN rowcount;
1253251876Speter
1254251876Speter        rc = SQLRowCount(hstmt, &rowcount);
1255251876Speter        *nrows = (int)rowcount;
1256251876Speter        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT, hstmt);
1257251876Speter    }
1258251876Speter
1259251876Speter    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
1260251876Speter    return APR_FROM_SQL_RESULT(rc);
1261251876Speter}
1262251876Speter
1263251876Speter/** select: execute an SQL statement which returns a result set **/
1264251876Speterstatic int odbc_select(apr_pool_t *pool, apr_dbd_t *handle,
1265251876Speter                       apr_dbd_results_t **res, const char *statement,
1266251876Speter                       int random)
1267251876Speter{
1268251876Speter    SQLRETURN rc;
1269251876Speter    SQLHANDLE hstmt;
1270251876Speter    apr_dbd_prepared_t *stmt;
1271251876Speter    size_t len = strlen(statement);
1272251876Speter
1273251876Speter    if (odbc_check_rollback(handle))
1274251876Speter        return APR_EGENERAL;
1275251876Speter
1276251876Speter    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &hstmt);
1277251876Speter    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc, SQL_HANDLE_DBC,
1278251876Speter                handle->dbc);
1279251876Speter    if (!SQL_SUCCEEDED(rc))
1280251876Speter        return APR_FROM_SQL_RESULT(rc);
1281251876Speter    /* Prepare an apr_dbd_prepared_t for pool cleanup, even though this
1282251876Speter     * is not a prepared statement.  We want the same cleanup mechanism.
1283251876Speter     */
1284251876Speter    stmt = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
1285251876Speter    stmt->apr_dbd = handle;
1286251876Speter    stmt->dbc = handle->dbc;
1287251876Speter    stmt->stmt = hstmt;
1288251876Speter    apr_pool_cleanup_register(pool, stmt, odbc_close_pstmt, apr_pool_cleanup_null);
1289251876Speter    if (random) {
1290251876Speter        rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
1291251876Speter                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1292251876Speter        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)", rc,
1293251876Speter                    SQL_HANDLE_STMT, hstmt);
1294251876Speter    }
1295251876Speter    if (SQL_SUCCEEDED(rc)) {
1296251876Speter        rc = SQLExecDirect(hstmt, (SQLCHAR *)statement, (SQLINTEGER)len);
1297251876Speter        CHECK_ERROR(handle, "SQLExecDirect", rc, SQL_HANDLE_STMT, hstmt);
1298251876Speter    }
1299251876Speter    if (SQL_SUCCEEDED(rc)) {
1300251876Speter        rc = odbc_create_results(handle, hstmt, pool, random, res);
1301251876Speter        apr_pool_cleanup_register(pool, *res,
1302251876Speter                                  odbc_close_results, apr_pool_cleanup_null);
1303251876Speter    }
1304251876Speter    return APR_FROM_SQL_RESULT(rc);
1305251876Speter}
1306251876Speter
1307251876Speter/** num_cols: get the number of columns in a results set **/
1308251876Speterstatic int odbc_num_cols(apr_dbd_results_t *res)
1309251876Speter{
1310251876Speter    return res->ncols;
1311251876Speter}
1312251876Speter
1313251876Speter/** num_tuples: get the number of rows in a results set **/
1314251876Speterstatic int odbc_num_tuples(apr_dbd_results_t *res)
1315251876Speter{
1316251876Speter    SQLRETURN rc;
1317251876Speter    SQLLEN nrows;
1318251876Speter
1319251876Speter    rc = SQLRowCount(res->stmt, &nrows);
1320251876Speter    CHECK_ERROR(res->apr_dbd, "SQLRowCount", rc, SQL_HANDLE_STMT, res->stmt);
1321251876Speter    return SQL_SUCCEEDED(rc) ? (int)nrows : -1;
1322251876Speter}
1323251876Speter
1324251876Speter/** get_row: get a row from a result set **/
1325251876Speterstatic int odbc_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
1326251876Speter                        apr_dbd_row_t **row, int rownum)
1327251876Speter{
1328251876Speter    SQLRETURN rc;
1329251876Speter    char *fetchtype;
1330251876Speter    int c;
1331251876Speter
1332251876Speter    *row = apr_pcalloc(pool, sizeof(apr_dbd_row_t));
1333251876Speter    (*row)->stmt = res->stmt;
1334251876Speter    (*row)->dbc = res->dbc;
1335251876Speter    (*row)->res = res;
1336251876Speter    (*row)->pool = res->pool;
1337251876Speter
1338251876Speter    /* mark all the columns as needing SQLGetData unless they are bound  */
1339251876Speter    for (c = 0; c < res->ncols; c++) {
1340251876Speter        if (res->colstate[c] != COL_BOUND) {
1341251876Speter            res->colstate[c] = COL_AVAIL;
1342251876Speter        }
1343251876Speter        /* some drivers do not null-term zero-len CHAR data */
1344251876Speter        if (res->colptrs[c])
1345251876Speter            *(char *)res->colptrs[c] = 0;
1346251876Speter    }
1347251876Speter
1348251876Speter    if (res->random && (rownum > 0)) {
1349251876Speter        fetchtype = "SQLFetchScroll";
1350251876Speter        rc = SQLFetchScroll(res->stmt, SQL_FETCH_ABSOLUTE, rownum);
1351251876Speter    }
1352251876Speter    else {
1353251876Speter        fetchtype = "SQLFetch";
1354251876Speter        rc = SQLFetch(res->stmt);
1355251876Speter    }
1356251876Speter    CHECK_ERROR(res->apr_dbd, fetchtype, rc, SQL_HANDLE_STMT, res->stmt);
1357251876Speter    (*row)->stmt = res->stmt;
1358251876Speter    if (!SQL_SUCCEEDED(rc) && !res->random) {
1359251876Speter        /* early close on any error (usually SQL_NO_DATA) if fetching
1360251876Speter         * sequentially to release resources ASAP
1361251876Speter         */
1362251876Speter        odbc_close_results(res);
1363251876Speter        return -1;
1364251876Speter    }
1365251876Speter    return SQL_SUCCEEDED(rc) ? 0 : -1;
1366251876Speter}
1367251876Speter
1368251876Speter/** datum_get: get a binary entry from a row **/
1369251876Speterstatic apr_status_t odbc_datum_get(const apr_dbd_row_t *row, int col,
1370251876Speter                                   apr_dbd_type_e dbdtype, void *data)
1371251876Speter{
1372251876Speter    SQLSMALLINT sqltype;
1373251876Speter    void *p;
1374251876Speter    int len;
1375251876Speter
1376251876Speter    if (col >= row->res->ncols)
1377251876Speter        return APR_EGENERAL;
1378251876Speter
1379251876Speter    if (dbdtype < 0 || dbdtype >= NUM_APR_DBD_TYPES) {
1380251876Speter        data = NULL;            /* invalid type */
1381251876Speter        return APR_EGENERAL;
1382251876Speter    }
1383251876Speter
1384251876Speter    len = sqlSizes[dbdtype];
1385251876Speter    sqltype = sqlCtype[dbdtype];
1386251876Speter
1387251876Speter    /* must not memcpy a brigade, sentinals are relative to orig loc */
1388251876Speter    if (IS_LOB(sqltype))
1389251876Speter        return odbc_create_bucket(row, col, sqltype, data);
1390251876Speter
1391251876Speter    p = odbc_get(row, col, sqltype);
1392251876Speter    if (p == (void *)-1)
1393251876Speter        return APR_EGENERAL;
1394251876Speter
1395251876Speter    if (p == NULL)
1396251876Speter        return APR_ENOENT;          /* SQL NULL value */
1397251876Speter
1398251876Speter    if (len < 0)
1399251876Speter       *(char**)data = (char *)p;
1400251876Speter    else
1401251876Speter        memcpy(data, p, len);
1402251876Speter
1403251876Speter    return APR_SUCCESS;
1404251876Speter
1405251876Speter}
1406251876Speter
1407251876Speter/** get_entry: get an entry from a row (string data) **/
1408251876Speterstatic const char *odbc_get_entry(const apr_dbd_row_t *row, int col)
1409251876Speter{
1410251876Speter    void *p;
1411251876Speter
1412251876Speter    if (col >= row->res->ncols)
1413251876Speter        return NULL;
1414251876Speter
1415251876Speter    p = odbc_get(row, col, SQL_C_CHAR);
1416251876Speter
1417251876Speter    /* NULL or invalid (-1) */
1418251876Speter    if (p == NULL || p == (void *)-1)
1419251876Speter        return p;
1420251876Speter    else
1421251876Speter        return apr_pstrdup(row->pool, p);
1422251876Speter}
1423251876Speter
1424251876Speter/** error: get current error message (if any) **/
1425251876Speterstatic const char *odbc_error(apr_dbd_t *handle, int errnum)
1426251876Speter{
1427251876Speter    return (handle) ? handle->lastError : "[dbd_odbc]No error message available";
1428251876Speter}
1429251876Speter
1430251876Speter/** escape: escape a string so it is safe for use in query/select **/
1431251876Speterstatic const char *odbc_escape(apr_pool_t *pool, const char *s,
1432251876Speter                               apr_dbd_t *handle)
1433251876Speter{
1434251876Speter    char *newstr, *src, *dst, *sq;
1435251876Speter    int qcount;
1436251876Speter
1437251876Speter    /* return the original if there are no single-quotes */
1438251876Speter    if (!(sq = strchr(s, '\'')))
1439251876Speter        return (char *)s;
1440251876Speter    /* count the single-quotes and allocate a new buffer */
1441251876Speter    for (qcount = 1; (sq = strchr(sq + 1, '\'')); )
1442251876Speter        qcount++;
1443251876Speter    newstr = apr_palloc(pool, strlen(s) + qcount + 1);
1444251876Speter
1445251876Speter    /* move chars, doubling all single-quotes */
1446251876Speter    src = (char *)s;
1447251876Speter    for (dst = newstr; *src; src++) {
1448251876Speter        if ((*dst++ = *src) == '\'')
1449251876Speter            *dst++ = '\'';
1450251876Speter    }
1451251876Speter    *dst = 0;
1452251876Speter    return newstr;
1453251876Speter}
1454251876Speter
1455251876Speter/** prepare: prepare a statement **/
1456251876Speterstatic int odbc_prepare(apr_pool_t *pool, apr_dbd_t *handle,
1457251876Speter                        const char *query, const char *label, int nargs,
1458251876Speter                        int nvals, apr_dbd_type_e *types,
1459251876Speter                        apr_dbd_prepared_t **statement)
1460251876Speter{
1461251876Speter    SQLRETURN rc;
1462251876Speter    size_t len = strlen(query);
1463251876Speter
1464251876Speter    if (odbc_check_rollback(handle))
1465251876Speter        return APR_EGENERAL;
1466251876Speter
1467251876Speter    *statement = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t));
1468251876Speter    (*statement)->dbc = handle->dbc;
1469251876Speter    (*statement)->apr_dbd = handle;
1470251876Speter    (*statement)->nargs = nargs;
1471251876Speter    (*statement)->nvals = nvals;
1472251876Speter    (*statement)->types =
1473251876Speter        apr_pmemdup(pool, types, nargs * sizeof(apr_dbd_type_e));
1474251876Speter    rc = SQLAllocHandle(SQL_HANDLE_STMT, handle->dbc, &((*statement)->stmt));
1475251876Speter    apr_pool_cleanup_register(pool, *statement,
1476251876Speter                              odbc_close_pstmt, apr_pool_cleanup_null);
1477251876Speter    CHECK_ERROR(handle, "SQLAllocHandle (STMT)", rc,
1478251876Speter                SQL_HANDLE_DBC, handle->dbc);
1479251876Speter    rc = SQLPrepare((*statement)->stmt, (SQLCHAR *)query, (SQLINTEGER)len);
1480251876Speter    CHECK_ERROR(handle, "SQLPrepare", rc, SQL_HANDLE_STMT,
1481251876Speter                (*statement)->stmt);
1482251876Speter    return APR_FROM_SQL_RESULT(rc);
1483251876Speter}
1484251876Speter
1485251876Speter/** pquery: query using a prepared statement + args **/
1486251876Speterstatic int odbc_pquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1487251876Speter                       apr_dbd_prepared_t *statement, const char **args)
1488251876Speter{
1489251876Speter    SQLRETURN rc = SQL_SUCCESS;
1490251876Speter    int i, argp;
1491251876Speter
1492251876Speter    if (odbc_check_rollback(handle))
1493251876Speter        return APR_EGENERAL;
1494251876Speter
1495251876Speter    for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1496251876Speter        rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1497251876Speter                             &argp, (const void **)args, TEXTMODE);
1498251876Speter    }
1499251876Speter    if (SQL_SUCCEEDED(rc)) {
1500251876Speter        rc = SQLExecute(statement->stmt);
1501251876Speter        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1502251876Speter                    statement->stmt);
1503251876Speter    }
1504251876Speter    if (SQL_SUCCEEDED(rc)) {
1505251876Speter        SQLLEN rowcount;
1506251876Speter
1507251876Speter        rc = SQLRowCount(statement->stmt, &rowcount);
1508251876Speter        *nrows = (int)rowcount;
1509251876Speter        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
1510251876Speter                    statement->stmt);
1511251876Speter    }
1512251876Speter    return APR_FROM_SQL_RESULT(rc);
1513251876Speter}
1514251876Speter
1515251876Speter/** pvquery: query using a prepared statement + args **/
1516251876Speterstatic int odbc_pvquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1517251876Speter                        apr_dbd_prepared_t *statement, va_list args)
1518251876Speter{
1519251876Speter    const char **values;
1520251876Speter    int i;
1521251876Speter
1522251876Speter    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1523251876Speter    for (i = 0; i < statement->nvals; i++)
1524251876Speter        values[i] = va_arg(args, const char *);
1525251876Speter    return odbc_pquery(pool, handle, nrows, statement, values);
1526251876Speter}
1527251876Speter
1528251876Speter/** pselect: select using a prepared statement + args **/
1529251876Speterstatic int odbc_pselect(apr_pool_t *pool, apr_dbd_t *handle,
1530251876Speter                        apr_dbd_results_t **res, apr_dbd_prepared_t *statement,
1531251876Speter                        int random, const char **args)
1532251876Speter{
1533251876Speter    SQLRETURN rc = SQL_SUCCESS;
1534251876Speter    int i, argp;
1535251876Speter
1536251876Speter    if (odbc_check_rollback(handle))
1537251876Speter        return APR_EGENERAL;
1538251876Speter
1539251876Speter    if (random) {
1540251876Speter        rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
1541251876Speter                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1542251876Speter        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
1543251876Speter                    rc, SQL_HANDLE_STMT, statement->stmt);
1544251876Speter    }
1545251876Speter    if (SQL_SUCCEEDED(rc)) {
1546251876Speter        for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1547251876Speter            rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1548251876Speter                                 &argp, (const void **)args, TEXTMODE);
1549251876Speter        }
1550251876Speter    }
1551251876Speter    if (SQL_SUCCEEDED(rc)) {
1552251876Speter        rc = SQLExecute(statement->stmt);
1553251876Speter        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1554251876Speter                    statement->stmt);
1555251876Speter    }
1556251876Speter    if (SQL_SUCCEEDED(rc)) {
1557251876Speter        rc = odbc_create_results(handle, statement->stmt, pool, random, res);
1558251876Speter        apr_pool_cleanup_register(pool, *res,
1559251876Speter                                  odbc_close_results, apr_pool_cleanup_null);
1560251876Speter    }
1561251876Speter    return APR_FROM_SQL_RESULT(rc);
1562251876Speter}
1563251876Speter
1564251876Speter/** pvselect: select using a prepared statement + args **/
1565251876Speterstatic int odbc_pvselect(apr_pool_t *pool, apr_dbd_t *handle,
1566251876Speter                         apr_dbd_results_t **res,
1567251876Speter                         apr_dbd_prepared_t *statement, int random,
1568251876Speter                         va_list args)
1569251876Speter{
1570251876Speter    const char **values;
1571251876Speter    int i;
1572251876Speter
1573251876Speter    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1574251876Speter    for (i = 0; i < statement->nvals; i++)
1575251876Speter        values[i] = va_arg(args, const char *);
1576251876Speter    return odbc_pselect(pool, handle, res, statement, random, values);
1577251876Speter}
1578251876Speter
1579251876Speter/** get_name: get a column title from a result set **/
1580251876Speterstatic const char *odbc_get_name(const apr_dbd_results_t *res, int col)
1581251876Speter{
1582251876Speter    SQLRETURN rc;
1583251876Speter    char buffer[MAX_COLUMN_NAME];
1584251876Speter    SQLSMALLINT colnamelength, coltype, coldecimal, colnullable;
1585251876Speter    SQLULEN colsize;
1586251876Speter
1587251876Speter    if (col >= res->ncols)
1588251876Speter        return NULL;            /* bogus column number */
1589251876Speter    if (res->colnames[col] != NULL)
1590251876Speter        return res->colnames[col];      /* we already retrieved it */
1591251876Speter    rc = SQLDescribeCol(res->stmt, col + 1,
1592251876Speter                        (SQLCHAR *)buffer, sizeof(buffer), &colnamelength,
1593251876Speter                        &coltype, &colsize, &coldecimal, &colnullable);
1594251876Speter    CHECK_ERROR(res->apr_dbd, "SQLDescribeCol", rc,
1595251876Speter                SQL_HANDLE_STMT, res->stmt);
1596251876Speter    res->colnames[col] = apr_pstrdup(res->pool, buffer);
1597251876Speter    return res->colnames[col];
1598251876Speter}
1599251876Speter
1600251876Speter/** transaction_mode_get: get the mode of transaction **/
1601251876Speterstatic int odbc_transaction_mode_get(apr_dbd_transaction_t *trans)
1602251876Speter{
1603251876Speter    return (int)trans->apr_dbd->can_commit;
1604251876Speter}
1605251876Speter
1606251876Speter/** transaction_mode_set: set the mode of transaction **/
1607251876Speterstatic int odbc_transaction_mode_set(apr_dbd_transaction_t *trans, int mode)
1608251876Speter{
1609251876Speter    int legal = (  APR_DBD_TRANSACTION_IGNORE_ERRORS
1610251876Speter                 | APR_DBD_TRANSACTION_COMMIT
1611251876Speter                 | APR_DBD_TRANSACTION_ROLLBACK);
1612251876Speter
1613251876Speter    if ((mode & legal) != mode)
1614251876Speter        return APR_EGENERAL;
1615251876Speter
1616251876Speter    trans->apr_dbd->can_commit = mode;
1617251876Speter    return APR_SUCCESS;
1618251876Speter}
1619251876Speter
1620251876Speter/** pbquery: query using a prepared statement + binary args **/
1621251876Speterstatic int odbc_pbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1622251876Speter                        apr_dbd_prepared_t *statement, const void **args)
1623251876Speter{
1624251876Speter    SQLRETURN rc = SQL_SUCCESS;
1625251876Speter    int i, argp;
1626251876Speter
1627251876Speter    if (odbc_check_rollback(handle))
1628251876Speter        return APR_EGENERAL;
1629251876Speter
1630251876Speter    for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++)
1631251876Speter        rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1632251876Speter                             &argp, args, BINARYMODE);
1633251876Speter
1634251876Speter    if (SQL_SUCCEEDED(rc)) {
1635251876Speter        rc = SQLExecute(statement->stmt);
1636251876Speter        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1637251876Speter                    statement->stmt);
1638251876Speter    }
1639251876Speter    if (SQL_SUCCEEDED(rc)) {
1640251876Speter        SQLLEN rowcount;
1641251876Speter
1642251876Speter        rc = SQLRowCount(statement->stmt, &rowcount);
1643251876Speter        *nrows = (int)rowcount;
1644251876Speter        CHECK_ERROR(handle, "SQLRowCount", rc, SQL_HANDLE_STMT,
1645251876Speter                    statement->stmt);
1646251876Speter    }
1647251876Speter    return APR_FROM_SQL_RESULT(rc);
1648251876Speter}
1649251876Speter
1650251876Speter/** pbselect: select using a prepared statement + binary args **/
1651251876Speterstatic int odbc_pbselect(apr_pool_t *pool, apr_dbd_t *handle,
1652251876Speter                         apr_dbd_results_t **res,
1653251876Speter                         apr_dbd_prepared_t *statement,
1654251876Speter                         int random, const void **args)
1655251876Speter{
1656251876Speter    SQLRETURN rc = SQL_SUCCESS;
1657251876Speter    int i, argp;
1658251876Speter
1659251876Speter    if (odbc_check_rollback(handle))
1660251876Speter        return APR_EGENERAL;
1661251876Speter
1662251876Speter    if (random) {
1663251876Speter        rc = SQLSetStmtAttr(statement->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
1664251876Speter                            (SQLPOINTER)SQL_SCROLLABLE, 0);
1665251876Speter        CHECK_ERROR(handle, "SQLSetStmtAttr (SQL_ATTR_CURSOR_SCROLLABLE)",
1666251876Speter                    rc, SQL_HANDLE_STMT, statement->stmt);
1667251876Speter    }
1668251876Speter    if (SQL_SUCCEEDED(rc)) {
1669251876Speter        for (i = argp = 0; i < statement->nargs && SQL_SUCCEEDED(rc); i++) {
1670251876Speter            rc = odbc_bind_param(pool, statement, i + 1, statement->types[i],
1671251876Speter                                 &argp, args, BINARYMODE);
1672251876Speter        }
1673251876Speter    }
1674251876Speter    if (SQL_SUCCEEDED(rc)) {
1675251876Speter        rc = SQLExecute(statement->stmt);
1676251876Speter        CHECK_ERROR(handle, "SQLExecute", rc, SQL_HANDLE_STMT,
1677251876Speter                    statement->stmt);
1678251876Speter    }
1679251876Speter    if (SQL_SUCCEEDED(rc)) {
1680251876Speter        rc = odbc_create_results(handle, statement->stmt, pool, random, res);
1681251876Speter        apr_pool_cleanup_register(pool, *res,
1682251876Speter                                  odbc_close_results, apr_pool_cleanup_null);
1683251876Speter    }
1684251876Speter
1685251876Speter    return APR_FROM_SQL_RESULT(rc);
1686251876Speter}
1687251876Speter
1688251876Speter/** pvbquery: query using a prepared statement + binary args **/
1689251876Speterstatic int odbc_pvbquery(apr_pool_t *pool, apr_dbd_t *handle, int *nrows,
1690251876Speter                         apr_dbd_prepared_t *statement, va_list args)
1691251876Speter{
1692251876Speter    const char **values;
1693251876Speter    int i;
1694251876Speter
1695251876Speter    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1696251876Speter    for (i = 0; i < statement->nvals; i++)
1697251876Speter        values[i] = va_arg(args, const char *);
1698251876Speter    return odbc_pbquery(pool, handle, nrows, statement, (const void **)values);
1699251876Speter}
1700251876Speter
1701251876Speter/** pvbselect: select using a prepared statement + binary args **/
1702251876Speterstatic int odbc_pvbselect(apr_pool_t *pool, apr_dbd_t *handle,
1703251876Speter                          apr_dbd_results_t **res,
1704251876Speter                          apr_dbd_prepared_t *statement,
1705251876Speter                          int random, va_list args)
1706251876Speter{
1707251876Speter    const char **values;
1708251876Speter    int i;
1709251876Speter
1710251876Speter    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1711251876Speter    for (i = 0; i < statement->nvals; i++)
1712251876Speter        values[i] = va_arg(args, const char *);
1713251876Speter    return odbc_pbselect(pool, handle, res, statement, random, (const void **)values);
1714251876Speter}
1715251876Speter
1716251876SpeterAPU_MODULE_DECLARE_DATA const apr_dbd_driver_t ODBC_DRIVER_ENTRY = {
1717251876Speter    ODBC_DRIVER_STRING,
1718251876Speter    odbc_init,
1719251876Speter    odbc_native_handle,
1720251876Speter    odbc_open,
1721251876Speter    odbc_check_conn,
1722251876Speter    odbc_close,
1723251876Speter    odbc_set_dbname,
1724251876Speter    odbc_start_transaction,
1725251876Speter    odbc_end_transaction,
1726251876Speter    odbc_query,
1727251876Speter    odbc_select,
1728251876Speter    odbc_num_cols,
1729251876Speter    odbc_num_tuples,
1730251876Speter    odbc_get_row,
1731251876Speter    odbc_get_entry,
1732251876Speter    odbc_error,
1733251876Speter    odbc_escape,
1734251876Speter    odbc_prepare,
1735251876Speter    odbc_pvquery,
1736251876Speter    odbc_pvselect,
1737251876Speter    odbc_pquery,
1738251876Speter    odbc_pselect,
1739251876Speter    odbc_get_name,
1740251876Speter    odbc_transaction_mode_get,
1741251876Speter    odbc_transaction_mode_set,
1742251876Speter    "?",
1743251876Speter    odbc_pvbquery,
1744251876Speter    odbc_pvbselect,
1745251876Speter    odbc_pbquery,
1746251876Speter    odbc_pbselect,
1747251876Speter    odbc_datum_get
1748251876Speter};
1749251876Speter
1750251876Speter#endif
1751