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