/*
 * ODBC-Ruby binding
 * Copyright (c) 2001-2023 Christian Werner <chw@ch-werner.de>
 * Portions copyright (c) 2004 Ryszard Niewisiewicz <micz@fibernet.pl>
 * Portions copyright (c) 2006 Carl Blakeley <cblakeley@openlinksw.co.uk>
 *
 * See the file "COPYING" for information on usage
 * and redistribution of this file and for a
 * DISCLAIMER OF ALL WARRANTIES.
 *
 * $Id: odbc.c,v 1.81 2023/09/04 09:50:17 chw Exp chw $
 */

#undef ODBCVER

#if defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__)
#include <windows.h>
#endif
#include <stdarg.h>
#include <ctype.h>
#include "ruby.h"
#ifdef HAVE_VERSION_H
#include "version.h"
#endif
#ifdef HAVE_RUBY_VERSION_H
#include "ruby/version.h"
#endif
#ifdef HAVE_SQL_H
#include <sql.h>
#else
#error Missing include: sql.h
#endif
#ifdef HAVE_SQLEXT_H
#include <sqlext.h>
#else
#error Missing include: sqlext.h
#endif
#ifdef HAVE_ODBCINST_H
#include <odbcinst.h>
#endif

#ifdef UNICODE
#include <sqlucode.h>
#endif

#ifndef HAVE_TYPE_SQLTCHAR
#ifdef UNICODE
typedef SQLWCHAR SQLTCHAR;
#else
typedef SQLCHAR SQLTCHAR;
#endif
#endif

#ifndef HAVE_TYPE_SQLLEN
#define SQLLEN SQLINTEGER
#endif
#ifndef HAVE_TYPE_SQLULEN
#define SQLULEN SQLUINTEGER
#endif
#ifndef HAVE_TYPE_SQLROWOFFSET
#define SQLROWOFFSET SQLLEN
#endif
#ifndef HAVE_TYPE_SQLROWSETSIZE
#define SQLROWSETSIZE SQLULEN
#endif

#ifdef RUBY_VERSION_MAJOR
#if (RUBY_VERSION_MAJOR <= 1) && (RUBY_VERSION_MINOR < 9)
#define TIME_USE_USEC 1
#endif
#endif

#if (RUBY_API_VERSION_CODE >= 20500)
#define FUNCALL_NOARGS(o, m) rb_funcall((o), (m), 0)
#else
#define FUNCALL_NOARGS(o, m) rb_funcall((o), (m), 0, NULL)
#endif

#ifdef HAVE_RUBY_THREAD_H
#include "ruby/thread.h"
#endif

/*
 * Tainted strings for Ruby <= 2.7, no ops otherwise.
 */

#if (RUBY_API_VERSION_CODE < 20700)
#define RB_TAINTED_STR_NEW(s, l) rb_tainted_str_new(s, l)
#define RB_TAINTED_STR_NEW2(s) rb_tainted_str_new2(s)
#define RB_OBJ_TAINT(o) rb_obj_taint(o)
#else
#define RB_TAINTED_STR_NEW(s, l) rb_str_new(s, l)
#define RB_TAINTED_STR_NEW2(s) rb_str_new2(s)
#define RB_OBJ_TAINT(o) o
#endif

/*
 * Conditionally undefine aliases of ODBC installer UNICODE functions.
 */

#if defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW)
#undef SQLInstallerError
#endif
#if defined(UNICODE) && defined(HAVE_SQLCONFIGDATASOURCEW)
#undef SQLConfigDataSource
#endif
#if defined(UNICODE) && defined(HAVE_SQLREADFILEDSNW)
#undef SQLReadFileDSN
#endif
#if defined(UNICODE) && defined(HAVE_SQLWRITEFILEDSNW)
#undef SQLWriteFileDSN
#endif

#if defined(UNICODE) && defined(USE_DLOPEN_FOR_ODBC_LIBS)
extern int ruby_odbc_have_func(const char *name, void *addr);
#endif

#ifdef UNICODE
/*
 * Declarations of required installer APIs in case
 * header files don't provide them (unixODBC?).
 */

#ifndef HAVE_SQLINSTALLERERRORW
SQLRETURN INSTAPI SQLInstallerErrorW(WORD, DWORD *, LPWSTR, WORD, WORD *);
#endif
#ifndef HAVE_SQLCONFIGDATASOURCEW
BOOL INSTAPI SQLConfigDataSourceW(HWND, WORD, LPWSTR, LPWSTR);
#endif
#ifndef HAVE_SQLREADFILEDSNW
BOOL INSTAPI SQLReadFileDSNW(LPWSTR, LPWSTR, LPWSTR, LPWSTR, WORD, WORD *);
#endif
#ifndef HAVE_SQLWRITEFILEDSNW
BOOL INSTAPI SQLWriteFileDSNW(LPWSTR, LPWSTR, LPWSTR, LPWSTR);
#endif

#if defined(HAVE_RUBY_ENCODING_H) && HAVE_RUBY_ENCODING_H
#define USE_RB_ENC 1
#include "ruby/encoding.h"
static rb_encoding *rb_enc = NULL;
static VALUE rb_encv = Qnil;
#endif

#endif /* UNICODE */

#ifndef HAVE_RB_DEFINE_ALLOC_FUNC
#define rb_define_alloc_func(cls, func) \
    rb_define_singleton_method(cls, "new", func, -1)
#define rb_undefine_alloc_func(cls) \
    rb_undef_method(CLASS_OF(cls), "new")
#endif

#ifdef RB_CVAR_SET_4ARGS
#define CVAR_SET(x, y, z) rb_cvar_set(x, y, z, 0)
#else
#define CVAR_SET(x, y, z) rb_cvar_set(x, y, z)
#endif

#ifndef STR2CSTR
#define STR2CSTR(x) StringValueCStr(x)
#define NO_RB_STR2CSTR 1
#endif

#ifdef TRACING
static int tracing = 0;
#define tracemsg(t, x) {if (tracing & t) { x }}
static SQLRETURN tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt,
			  SQLRETURN ret, const char *m);
#else
#define tracemsg(t, x)
#define tracesql(a, b, c, d, e) d
#endif

/*
 * When to call start_gc():
 *
 *  gc_threshold  < 0:	never (the default)
 *  gc_threshold == 0:	on connection close
 *  gc_threshold >= 0:	on connection close and after
 *			fetching gc_threshold rows
 */

static int gc_threshold = -1;

#ifndef SQL_SUCCEEDED
#define SQL_SUCCEEDED(x) \
    (((x) == SQL_SUCCESS) || ((x) == SQL_SUCCESS_WITH_INFO))
#endif

#ifndef SQL_NO_DATA
#define SQL_NO_DATA SQL_NO_DATA_FOUND
#endif

typedef struct link {
    struct link *succ;
    struct link *pred;
    struct link *head;
    int offs;
} LINK;

typedef struct env {
    VALUE self;
    LINK dbcs;
    SQLHENV henv;
} ENV;

typedef struct dbc {
    LINK link;
    VALUE self;
    VALUE env;
    struct env *envp;
    LINK stmts;
    SQLHDBC hdbc;
    VALUE rbtime;
    VALUE gmtime;
    int upc;
    VALUE use_sql_column_name;
} DBC;

typedef struct {
    SQLSMALLINT type;
    SQLULEN coldef;
    SQLULEN coldef_max;
    SQLSMALLINT scale;
    SQLLEN rlen;
    SQLSMALLINT nullable;
    SQLSMALLINT iotype;
    int override;
#ifdef UNICODE
    SQLWCHAR *tofree;
#endif
    char buffer[sizeof (double) * 4 + sizeof (TIMESTAMP_STRUCT)];
    SQLSMALLINT ctype;
    SQLSMALLINT outtype;
    int outsize;
    char *outbuf;
} PARAMINFO;

typedef struct {
    int type;
    int size;
} COLTYPE;

typedef struct stmt {
    LINK link;
    VALUE self;
    VALUE dbc;
    struct dbc *dbcp;
    SQLHSTMT hstmt;
    int nump;
    PARAMINFO *paraminfo;
    int ncols;
    COLTYPE *coltypes;
    char **colnames;
    VALUE *colvals;
    char **dbufs;
    int fetchc;
    int upc;
    int usef;
} STMT;

static VALUE Modbc;
static VALUE Cobj;
static VALUE Cenv;
static VALUE Cdbc;
static VALUE Cstmt;
static VALUE Ccolumn;
static VALUE Cparam;
static VALUE Cerror;
static VALUE Cdsn;
static VALUE Cdrv;
static VALUE Cdate;
static VALUE Ctime;
static VALUE Ctimestamp;
static VALUE Cproc;
static VALUE rb_cDate;

static ID IDstart;
static ID IDatatinfo;
static ID IDataterror;
static ID IDkeys;
static ID IDatattrs;
static ID IDday;
static ID IDmonth;
static ID IDyear;
static ID IDmday;
static ID IDnsec;
static ID IDusec;
static ID IDsec;
static ID IDmin;
static ID IDhour;
static ID IDusec;
static ID IDkeyp;
static ID IDkey;
static ID IDSymbol;
static ID IDString;
static ID IDFixnum;
static ID IDtable_names;
static ID IDnew;
static ID IDnow;
static ID IDname;
static ID IDtable;
static ID IDtype;
static ID IDlength;
static ID IDnullable;
static ID IDscale;
static ID IDprecision;
static ID IDsearchable;
static ID IDunsigned;
static ID IDiotype;
static ID IDoutput_size;
static ID IDoutput_type;
static ID IDdescr;
static ID IDstatement;
static ID IDreturn_output_param;
static ID IDattrs;
static ID IDNULL;
static ID IDdefault;
#ifdef USE_RB_ENC
static ID IDencode;
#endif
static ID IDparse;
static ID IDutc;
static ID IDlocal;
static ID IDto_s;

/*
 * Modes for dbc_info
 */

#define INFO_TABLES   0
#define INFO_COLUMNS  1
#define INFO_PRIMKEYS 2
#define INFO_INDEXES  3
#define INFO_TYPES    4
#define INFO_FORKEYS  5
#define INFO_TPRIV    6
#define INFO_PROCS    7
#define INFO_PROCCOLS 8
#define INFO_SPECCOLS 9

/*
 * Modes for make_result/stmt_exec_int
 */

#define MAKERES_BLOCK   1
#define MAKERES_NOCLOSE 2
#define MAKERES_PREPARE 4
#define MAKERES_EXECD   8
#define EXEC_PARMXNULL(x) (16 | ((x) << 5))
#define EXEC_PARMXOUT(x)  (((x) & 16) ? ((x) >> 5) : -1)

/*
 * Modes for do_fetch
 */

#define DOFETCH_ARY    0
#define DOFETCH_HASH   1
#define DOFETCH_HASH2  2
#define DOFETCH_HASHK  3
#define DOFETCH_HASHK2 4
#define DOFETCH_HASHN  5
#define DOFETCH_MODES  7
#define DOFETCH_BANG   8

/*
 * Size of segment when SQL_NO_TOTAL
 */

#define SEGSIZE 65536

/*
 * Forward declarations.
 */

static SQLRETURN callsql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt,
			 SQLRETURN ret, const char *m);

static VALUE stmt_exec(int argc, VALUE *argv, VALUE self);
static VALUE stmt_each(VALUE self);
static VALUE stmt_each_hash(int argc, VALUE *argv, VALUE self);
static VALUE stmt_close(VALUE self);
static VALUE stmt_drop(VALUE self);

/*
 * Column name buffers on statement.
 */

static const char *colnamebuf[] = {
    "@_c0", "@_c1", "@_c2", "@_c3"
};

/*
 * Macro to align buffers.
 */

#define LEN_ALIGN(x) \
    ((x) + sizeof (double) - (((x) + sizeof (double)) % sizeof (double)))


/*
 *----------------------------------------------------------------------
 *
 *	Wrappers for long running SQL APIs with GVL released.
 *
 *----------------------------------------------------------------------
 */

#ifdef RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_

static void
empty_ubf(void *args)
{
}

struct S_SQLCONNECT {
    SQLHDBC hdbc;
    SQLTCHAR *dsn;
    SQLSMALLINT dsn_len;
    SQLTCHAR *usr;
    SQLSMALLINT usr_len;
    SQLTCHAR *pwd;
    SQLSMALLINT pwd_len;
};

static void *
F_SQLCONNECT(void *args)
{
    size_t ret;
    struct S_SQLCONNECT *argp = (struct S_SQLCONNECT *) args;

    ret = SQLConnect(argp->hdbc, argp->dsn, argp->dsn_len,
		     argp->usr, argp->usr_len, argp->pwd, argp->pwd_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLCONNECT(SQLHDBC hdbc, SQLTCHAR *dsn, SQLSMALLINT dsn_len, SQLTCHAR *usr,
	   SQLSMALLINT usr_len, SQLTCHAR *pwd, SQLSMALLINT pwd_len)
{
    size_t ret;
    struct S_SQLCONNECT arg;

    arg.hdbc = hdbc;
    arg.dsn = dsn;
    arg.dsn_len = dsn_len;
    arg.usr = usr;
    arg.usr_len = usr_len;
    arg.pwd = pwd;
    arg.pwd_len = pwd_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLCONNECT, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLDRIVERCONNECT {
    SQLHDBC hdbc;
    SQLHWND hwnd;
    SQLTCHAR *connin;
    SQLSMALLINT connin_len;
    SQLTCHAR *connout;
    SQLSMALLINT connout_max;
    SQLSMALLINT *connout_len;
    SQLUSMALLINT compl;
};

static void *
F_SQLDRIVERCONNECT(void *args)
{
    size_t ret;
    struct S_SQLDRIVERCONNECT *argp = (struct S_SQLDRIVERCONNECT *) args;

    ret = SQLDriverConnect(argp->hdbc, argp->hwnd, argp->connin,
			   argp->connin_len, argp->connout, argp->connout_max,
			   argp->connout_len, argp->compl);
    return (void *) ret;
}

static inline SQLRETURN
SQLDRIVERCONNECT(SQLHDBC hdbc, SQLHWND hwnd, SQLTCHAR *connin,
		 SQLSMALLINT connin_len, SQLTCHAR *connout,
		 SQLSMALLINT connout_max, SQLSMALLINT *connout_len,
		 SQLUSMALLINT compl)
{
    size_t ret;
    struct S_SQLDRIVERCONNECT arg;

    arg.hdbc = hdbc;
    arg.hwnd = hwnd;
    arg.connin = connin;
    arg.connin_len = connin_len;
    arg.connout = connout;
    arg.connout_max = connout_max;
    arg.connout_len = connout_len;
    arg.compl = compl;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLDRIVERCONNECT, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLDISCONNECT {
    SQLHSTMT hstmt;
};

static void *
F_SQLDISCONNECT(void *args)
{
    size_t ret;
    struct S_SQLDISCONNECT *argp = (struct S_SQLDISCONNECT *) args;

    ret = SQLDisconnect(argp->hstmt);
    return (void *) ret;
}

static inline SQLRETURN
SQLDISCONNECT(SQLHSTMT hstmt)
{
    size_t ret;
    struct S_SQLDISCONNECT arg;

    arg.hstmt = hstmt;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLDISCONNECT, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLTABLES {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
    SQLTCHAR *typ;
    SQLSMALLINT typ_len;
};

static void *
F_SQLTABLES(void *args)
{
    size_t ret;
    struct S_SQLTABLES *argp = (struct S_SQLTABLES *) args;

    ret = SQLTables(argp->hdbc, argp->cat, argp->cat_len,
		    argp->sch, argp->sch_len, argp->tbl,
		    argp->tbl_len, argp->typ, argp->typ_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLTABLES(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
	  SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *tbl,
	  SQLSMALLINT tbl_len, SQLTCHAR *typ, SQLSMALLINT typ_len)
{
    size_t ret;
    struct S_SQLTABLES arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    arg.typ = typ;
    arg.typ_len = typ_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLTABLES, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLCOLUMNS {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
    SQLTCHAR *col;
    SQLSMALLINT col_len;
};

static void *
F_SQLCOLUMNS(void *args)
{
    size_t ret;
    struct S_SQLCOLUMNS *argp = (struct S_SQLCOLUMNS *) args;

    ret = SQLColumns(argp->hdbc, argp->cat, argp->cat_len,
		    argp->sch, argp->sch_len, argp->tbl,
		    argp->tbl_len, argp->col, argp->col_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLCOLUMNS(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
	  SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *tbl,
	  SQLSMALLINT tbl_len, SQLTCHAR *col, SQLSMALLINT col_len)
{
    size_t ret;
    struct S_SQLCOLUMNS arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    arg.col = col;
    arg.col_len = col_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLCOLUMNS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLPRIMARYKEYS {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
};

static void *
F_SQLPRIMARYKEYS(void *args)
{
    size_t ret;
    struct S_SQLPRIMARYKEYS *argp = (struct S_SQLPRIMARYKEYS *) args;

    ret = SQLPrimaryKeys(argp->hdbc, argp->cat, argp->cat_len,
			 argp->sch, argp->sch_len, argp->tbl, argp->tbl_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLPRIMARYKEYS(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
	       SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *tbl,
	       SQLSMALLINT tbl_len)
{
    size_t ret;
    struct S_SQLPRIMARYKEYS arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLPRIMARYKEYS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLFOREIGNKEYS {
    SQLHSTMT hdbc;
    SQLTCHAR *pkcat;
    SQLSMALLINT pkcat_len;
    SQLTCHAR *pksch;
    SQLSMALLINT pksch_len;
    SQLTCHAR *pktbl;
    SQLSMALLINT pktbl_len;
    SQLTCHAR *fkcat;
    SQLSMALLINT fkcat_len;
    SQLTCHAR *fksch;
    SQLSMALLINT fksch_len;
    SQLTCHAR *fktbl;
    SQLSMALLINT fktbl_len;
};

static void *
F_SQLFOREIGNKEYS(void *args)
{
    size_t ret;
    struct S_SQLFOREIGNKEYS *argp = (struct S_SQLFOREIGNKEYS *) args;

    ret = SQLForeignKeys(argp->hdbc, argp->pkcat, argp->pkcat_len,
			 argp->pksch, argp->pksch_len,
			 argp->pktbl, argp->pktbl_len,
			 argp->fkcat, argp->fkcat_len,
			 argp->fksch, argp->fksch_len,
			 argp->fktbl, argp->fktbl_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLFOREIGNKEYS(SQLHDBC hdbc, SQLTCHAR *pkcat, SQLSMALLINT pkcat_len,
	       SQLTCHAR *pksch, SQLSMALLINT pksch_len, SQLTCHAR *pktbl,
	       SQLSMALLINT pktbl_len, SQLTCHAR *fkcat, SQLSMALLINT fkcat_len,
	       SQLTCHAR *fksch, SQLSMALLINT fksch_len, SQLTCHAR *fktbl,
	       SQLSMALLINT fktbl_len)
{
    size_t ret;
    struct S_SQLFOREIGNKEYS arg;

    arg.hdbc = hdbc;
    arg.pkcat = pkcat;
    arg.pkcat_len = pkcat_len;
    arg.pksch = pksch;
    arg.pksch_len = pksch_len;
    arg.pktbl = pktbl;
    arg.pktbl_len = pktbl_len;
    arg.fkcat = fkcat;
    arg.fkcat_len = fkcat_len;
    arg.fksch = fksch;
    arg.fksch_len = fksch_len;
    arg.fktbl = fktbl;
    arg.fktbl_len = fktbl_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLFOREIGNKEYS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLPROCEDURES {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *prc;
    SQLSMALLINT prc_len;
};

static void *
F_SQLPROCEDURES(void *args)
{
    size_t ret;
    struct S_SQLPROCEDURES *argp = (struct S_SQLPROCEDURES *) args;

    ret = SQLProcedures(argp->hdbc, argp->cat, argp->cat_len,
			argp->sch, argp->sch_len, argp->prc, argp->prc_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLPROCEDURES(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
	      SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *prc,
	      SQLSMALLINT prc_len)
{
    size_t ret;
    struct S_SQLPROCEDURES arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.prc = prc;
    arg.prc_len = prc_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLPROCEDURES, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLPROCEDURECOLUMNS {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *prc;
    SQLSMALLINT prc_len;
    SQLTCHAR *col;
    SQLSMALLINT col_len;
};

static void *
F_SQLPROCEDURECOLUMNS(void *args)
{
    size_t ret;
    struct S_SQLPROCEDURECOLUMNS *argp = (struct S_SQLPROCEDURECOLUMNS *) args;

    ret = SQLProcedureColumns(argp->hdbc, argp->cat, argp->cat_len,
			      argp->sch, argp->sch_len, argp->prc,
			      argp->prc_len, argp->col, argp->col_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLPROCEDURECOLUMNS(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
		    SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *prc,
		    SQLSMALLINT prc_len, SQLTCHAR *col, SQLSMALLINT col_len)
{
    size_t ret;
    struct S_SQLPROCEDURECOLUMNS arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.prc = prc;
    arg.prc_len = prc_len;
    arg.col = col;
    arg.col_len = col_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLPROCEDURECOLUMNS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLTABLEPRIVILEGES {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
};

static void *
F_SQLTABLEPRIVILEGES(void *args)
{
    size_t ret;
    struct S_SQLTABLEPRIVILEGES *argp = (struct S_SQLTABLEPRIVILEGES *) args;

    ret = SQLTablePrivileges(argp->hdbc, argp->cat, argp->cat_len,
			     argp->sch, argp->sch_len, argp->tbl,
			     argp->tbl_len);
    return (void *) ret;
}

static inline SQLRETURN
SQLTABLEPRIVILEGES(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
		   SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *tbl,
		   SQLSMALLINT tbl_len)
{
    size_t ret;
    struct S_SQLTABLEPRIVILEGES arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLTABLEPRIVILEGES, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLSTATISTICS {
    SQLHSTMT hdbc;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
    SQLUSMALLINT uniq;
    SQLUSMALLINT resv;
};

static void *
F_SQLSTATISTICS(void *args)
{
    size_t ret;
    struct S_SQLSTATISTICS *argp = (struct S_SQLSTATISTICS *) args;

    ret = SQLStatistics(argp->hdbc, argp->cat, argp->cat_len,
			argp->sch, argp->sch_len, argp->tbl,
			argp->tbl_len, argp->uniq, argp->resv);
    return (void *) ret;
}

static inline SQLRETURN
SQLSTATISTICS(SQLHDBC hdbc, SQLTCHAR *cat, SQLSMALLINT cat_len,
	      SQLTCHAR *sch, SQLSMALLINT sch_len, SQLTCHAR *tbl,
	      SQLSMALLINT tbl_len, SQLUSMALLINT uniq, SQLUSMALLINT resv)
{
    size_t ret;
    struct S_SQLSTATISTICS arg;

    arg.hdbc = hdbc;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    arg.uniq = uniq;
    arg.resv = resv;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLSTATISTICS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLSPECIALCOLUMNS {
    SQLHSTMT hdbc;
    SQLUSMALLINT idtyp;
    SQLTCHAR *cat;
    SQLSMALLINT cat_len;
    SQLTCHAR *sch;
    SQLSMALLINT sch_len;
    SQLTCHAR *tbl;
    SQLSMALLINT tbl_len;
    SQLUSMALLINT scope;
    SQLUSMALLINT nulbl;
};

static void *
F_SQLSPECIALCOLUMNS(void *args)
{
    size_t ret;
    struct S_SQLSPECIALCOLUMNS *argp = (struct S_SQLSPECIALCOLUMNS *) args;

    ret = SQLSpecialColumns(argp->hdbc, argp->idtyp, argp->cat, argp->cat_len,
			    argp->sch, argp->sch_len, argp->tbl,
			    argp->tbl_len, argp->scope, argp->nulbl);
    return (void *) ret;
}

static inline SQLRETURN
SQLSPECIALCOLUMNS(SQLHDBC hdbc, SQLUSMALLINT idtyp, SQLTCHAR *cat,
		  SQLSMALLINT cat_len, SQLTCHAR *sch, SQLSMALLINT sch_len,
		  SQLTCHAR *tbl, SQLSMALLINT tbl_len,
		  SQLUSMALLINT scope, SQLUSMALLINT nulbl)
{
    size_t ret;
    struct S_SQLSPECIALCOLUMNS arg;

    arg.hdbc = hdbc;
    arg.idtyp = idtyp;
    arg.cat = cat;
    arg.cat_len = cat_len;
    arg.sch = sch;
    arg.sch_len = sch_len;
    arg.tbl = tbl;
    arg.tbl_len = tbl_len;
    arg.scope = scope;
    arg.nulbl = nulbl;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLSPECIALCOLUMNS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLGETTYPEINFO {
    SQLHSTMT hdbc;
    SQLSMALLINT type;
};

static void *
F_SQLGETTYPEINFO(void *args)
{
    size_t ret;
    struct S_SQLGETTYPEINFO *argp = (struct S_SQLGETTYPEINFO *) args;

    ret = SQLGetTypeInfo(argp->hdbc, argp->type);
    return (void *) ret;
}

static inline SQLRETURN
SQLGETTYPEINFO(SQLHDBC hdbc, SQLSMALLINT type)
{
    size_t ret;
    struct S_SQLGETTYPEINFO arg;

    arg.hdbc = hdbc;
    arg.type = type;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLGETTYPEINFO, &arg,
					      empty_ubf, &arg);
    return ret;
}

#if (ODBCVER >= 0x0300)

struct S_SQLENDTRAN {
    SQLSMALLINT htype;
    SQLHANDLE handle;
    SQLSMALLINT op;
};

static void *
F_SQLENDTRAN(void *args)
{
    size_t ret;
    struct S_SQLENDTRAN *argp = (struct S_SQLENDTRAN *) args;

    ret = SQLEndTran(argp->htype, argp->handle, argp->op);
    return (void *) ret;
}

static inline SQLRETURN
SQLENDTRAN(SQLUSMALLINT htype, SQLHANDLE handle, SQLUSMALLINT op)
{
    size_t ret;
    struct S_SQLENDTRAN arg;

    arg.htype = htype;
    arg.handle = handle;
    arg.op = op;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLENDTRAN, &arg,
					      empty_ubf, &arg);
    return ret;
}

#else

struct S_SQLTRANSACT {
    SQLHENV henv;
    SQLHDBC hdbc;
    SQLSMALLINT op;
};

static void *
F_SQLTRANSACT(void *args)
{
    size_t ret;
    struct S_SQLTRANSACT *argp = (struct S_SQLTRANSACT *) args;

    ret = SQLTransact(argp->henv, argp->hdbc, argp->op);
    return (void *) ret;
}

static inline SQLRETURN
SQLTRANSACT(SQLHENV henv, SQLHDBC hdbc, SQLUSMALLINT op)
{
    size_t ret;
    struct S_SQLTRANSACT arg;

    arg.henv = henv;
    arg.hdbc = hdbc;
    arg.op = op;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLTRANCACT, &arg,
					      empty_ubf, &arg);
    return ret;
}

#endif

struct S_SQLEXECDIRECT {
    SQLHSTMT hstmt;
    SQLTCHAR *sql;
    SQLINTEGER len;
};

static void *
F_SQLEXECDIRECT(void *args)
{
    size_t ret;
    struct S_SQLEXECDIRECT *argp = (struct S_SQLEXECDIRECT *) args;

    ret = SQLExecDirect(argp->hstmt, argp->sql, argp->len);
    return (void *) ret;
}

static void
F_SQLEXECDIRECT_UBF(void *args)
{
    struct S_SQLEXECDIRECT *argp = (struct S_SQLEXECDIRECT *) args;

    SQLCancel(argp->hstmt);
}

static inline SQLRETURN
SQLEXECDIRECT(SQLHSTMT hstmt, SQLTCHAR *sql, SQLINTEGER len)
{
    size_t ret;
    struct S_SQLEXECDIRECT arg;

    arg.hstmt = hstmt;
    arg.sql = sql;
    arg.len = len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLEXECDIRECT, &arg,
					      F_SQLEXECDIRECT_UBF, &arg);
    return ret;
}

struct S_SQLEXECUTE {
    SQLHSTMT hstmt;
};

static void *
F_SQLEXECUTE(void *args)
{
    size_t ret;
    struct S_SQLEXECUTE *argp = (struct S_SQLEXECUTE *) args;

    ret = SQLExecute(argp->hstmt);
    return (void *) ret;
}

static void
F_SQLEXECUTE_UBF(void *args)
{
    struct S_SQLEXECUTE *argp = (struct S_SQLEXECUTE *) args;

    SQLCancel(argp->hstmt);
}

static inline SQLRETURN
SQLEXECUTE(SQLHSTMT hstmt)
{
    size_t ret;
    struct S_SQLEXECUTE arg;

    arg.hstmt = hstmt;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLEXECUTE, &arg,
					      F_SQLEXECUTE_UBF, &arg);
    return ret;
}

struct S_SQLPREPARE {
    SQLHSTMT hstmt;
    SQLTCHAR *sql;
    SQLINTEGER len;
};

static void *
F_SQLPREPARE(void *args)
{
    size_t ret;
    struct S_SQLPREPARE *argp = (struct S_SQLPREPARE *) args;

    ret = SQLPrepare(argp->hstmt, argp->sql, argp->len);
    return (void *) ret;
}

static void
F_SQLPREPARE_UBF(void *args)
{
    struct S_SQLPREPARE *argp = (struct S_SQLPREPARE *) args;

    SQLCancel(argp->hstmt);
}

static inline SQLRETURN
SQLPREPARE(SQLHSTMT hstmt, SQLTCHAR *sql, SQLINTEGER len)
{
    size_t ret;
    struct S_SQLPREPARE arg;

    arg.hstmt = hstmt;
    arg.sql = sql;
    arg.len = len;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLPREPARE, &arg,
					      F_SQLPREPARE_UBF, &arg);
    return ret;
}

struct S_SQLMORERESULTS {
    SQLHSTMT hstmt;
};

static void *
F_SQLMORERESULTS(void *args)
{
    size_t ret;
    struct S_SQLMORERESULTS *argp = (struct S_SQLMORERESULTS *) args;

    ret = SQLMoreResults(argp->hstmt);
    return (void *) ret;
}

static inline SQLRETURN
SQLMORERESULTS(SQLHSTMT hstmt)
{
    size_t ret;
    struct S_SQLMORERESULTS arg;

    arg.hstmt = hstmt;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLMORERESULTS, &arg,
					      empty_ubf, &arg);
    return ret;
}

struct S_SQLFETCH {
    SQLHSTMT hstmt;
};

static void *
F_SQLFETCH(void *args)
{
    size_t ret;
    struct S_SQLFETCH *argp = (struct S_SQLFETCH *) args;

    ret = SQLFetch(argp->hstmt);
    return (void *) ret;
}

static inline SQLRETURN
SQLFETCH(SQLHSTMT hstmt)
{
    size_t ret;
    struct S_SQLFETCH arg;

    arg.hstmt = hstmt;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLFETCH, &arg,
					      empty_ubf, &arg);
    return ret;
}

#if (ODBCVER >= 0x0300)

struct S_SQLFETCHSCROLL {
    SQLHSTMT hstmt;
    SQLSMALLINT dir;
    SQLROWOFFSET offs;
};

static void *
F_SQLFETCHSCROLL(void *args)
{
    size_t ret;
    struct S_SQLFETCHSCROLL *argp = (struct S_SQLFETCHSCROLL *) args;

    ret = SQLFetchScroll(argp->hstmt, argp->dir, argp->offs);
    return (void *) ret;
}

static inline SQLRETURN
SQLFETCHSCROLL(SQLHSTMT hstmt, SQLSMALLINT dir, SQLROWOFFSET offs)
{
    size_t ret;
    struct S_SQLFETCHSCROLL arg;

    arg.hstmt = hstmt;
    arg.dir = dir;
    arg.offs = offs;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLFETCHSCROLL, &arg,
					      empty_ubf, &arg);
    return ret;
}

#else

struct S_SQLEXTENDEDFETCH {
    SQLHSTMT hstmt;
    SQLUSMALLINT type;
    SQLROWOFFSET row;
    SQLROWSETSIZE *rowp;
    SQLUSMALLINT *status
};

static void *
F_SQLEXTENDEDFETCH(void *args)
{
    size_t ret;
    struct S_SQLEXTENDEDFETCH *argp = (struct S_SQLEXTENDEDFETCH *) args;

    ret = SQLExtendedFetch(argp->hstmt, argp->type, argp->row, argp->rowp,
			   argp->status);
    return (void *) ret;
}

static inline SQLRETURN
SQLEXTENDEDFETCH(SQLHSTMT hstmt, SQLUSMALLINT type, SQLROWOFFSET row,
		 SQLROWSETSIZE *rowp, SQLUSMALLINT *status)
{
    size_t ret;
    struct S_SQLEXTENDEDFETCH arg;

    arg.hstmt = hstmt;
    arg.type = type;
    arg.row = row;
    arg.rowp = rowp;
    arg.status = status;
    ret = (size_t) rb_thread_call_without_gvl(F_SQLEXTENDEDFETCH, &arg,
					      empty_ubf, &arg);
    return ret;
}

#endif

#else

#define SQLCONNECT		SQLConnect
#define SQLDRIVERCONNECT	SQLDriverConnect
#define SQLDISCONNECT		SQLDisconnect
#define SQLTABLES		SQLTables
#define SQLCOLUMNS		SQLColumns
#define SQLPRIMARYKEYS		SQLPrimaryKeys
#define SQLFOREIGNKEYS		SQLForeignKeys
#define SQLPROCEDURES		SQLProcedures
#define SQLPROCEDURECOLUMNS	SQLProcedureColumns
#define SQLTABLEPRIVILEGES	SQLTablePrivileges
#define SQLSTATISTICS		SQLStatistics
#define SQLSPECIALCOLUMNS	SQLSpecialColumns
#define SQLGETTYPEINFO		SQLGetTypeInfo
#if (ODBCVER >= 0x0300)
#define SQLENDTRAN		SQLEndTran
#else
#define SQLTRANSACT		SQLTransact
#endif
#define SQLEXECDIRECT		SQLExecDirect
#define SQLEXECUTE		SQLExecute
#define SQLPREPARE		SQLPrepare
#define SQLMORERESULTS		SQLMoreResults
#define SQLFETCH		SQLFetch
#if (ODBCVER >= 0x0300)
#define SQLFETCHSCROLL		SQLFetchScroll
#else
#define SQLEXTENDEDFETCH	SQLExtendedFetch
#endif

#endif


/*
 *----------------------------------------------------------------------
 *
 *	UNICODE converters et.al.
 *
 *----------------------------------------------------------------------
 */

#ifdef UNICODE

static int
uc_strlen(SQLWCHAR *str)
{
    int len = 0;

    if (str != NULL) {
	while (*str != '\0') {
	    ++len;
	    ++str;
	}
    }
    return len;
}

static SQLWCHAR *
uc_strchr(SQLWCHAR *str, SQLWCHAR c)
{
    if (str != NULL) {
	while ((*str != '\0') && (*str != c)) {
	    ++str;
	}
	str = (*str == c) ? str : NULL;
    }
    return str;
}

static int
mkutf(char *dest, SQLWCHAR *src, int len)
{
    int i;
    char *cp = dest;

    for (i = 0; i < len; i++) {
	unsigned long c = src[i];

	if (sizeof (SQLWCHAR) == (2 * sizeof (char))) {
	    c &= 0xffff;
	}
	if (c < 0x80) {
	    *cp++ = c;
	} else if (c < 0x800) {
	    *cp++ = 0xc0 | ((c >> 6) & 0x1f);
	    *cp++ = 0x80 | (c & 0x3f);
	} else if (c < 0x10000) {
	    if ((sizeof (SQLWCHAR) == (2 * sizeof (char))) &&
		(c >= 0xd800) && (c <= 0xdbff) && ((i + 1) < len)) {
		unsigned long c2 = src[i + 1] & 0xffff;

		if ((c2 >= 0xdc00) && (c <= 0xdfff)) {
		    c = ((c & 0x3ff) | ((c2 & 0x3ff) << 10)) + 0x10000;
		    *cp++ = 0xf0 | ((c >> 18) & 0x07);
		    *cp++ = 0x80 | ((c >> 12) & 0x3f);
		    *cp++ = 0x80 | ((c >> 6) & 0x3f);
		    *cp++ = 0x80 | (c & 0x3f);
		    ++i;
		    continue;
		}
	    }
	    *cp++ = 0xe0 | ((c >> 12) & 0x0f);
	    *cp++ = 0x80 | ((c >> 6) & 0x3f);
	    *cp++ = 0x80 | (c & 0x3f);
	} else if (c < 0x200000) {
	    *cp++ = 0xf0 | ((c >> 18) & 0x07);
	    *cp++ = 0x80 | ((c >> 12) & 0x3f);
	    *cp++ = 0x80 | ((c >> 6) & 0x3f);
	    *cp++ = 0x80 | (c & 0x3f);
	} else if (c < 0x4000000) {
	    *cp++ = 0xf8 | ((c >> 24) & 0x03);
	    *cp++ = 0x80 | ((c >> 18) & 0x3f);
	    *cp++ = 0x80 | ((c >> 12) & 0x3f);
	    *cp++ = 0x80 | ((c >> 6) & 0x3f);
	    *cp++ = 0x80 | (c & 0x3f);
	} else if (c < 0x80000000) {
	    *cp++ = 0xfc | ((c >> 31) & 0x01);
	    *cp++ = 0x80 | ((c >> 24) & 0x3f);
	    *cp++ = 0x80 | ((c >> 18) & 0x3f);
	    *cp++ = 0x80 | ((c >> 12) & 0x3f);
	    *cp++ = 0x80 | ((c >> 6) & 0x3f);
	    *cp++ = 0x80 | (c & 0x3f);
	}
    }
    *cp = '\0';
    return cp - dest;
}

static VALUE
uc_tainted_str_new(SQLWCHAR *str, int len)
{
    VALUE v;
    char *cp = xmalloc(len * 6 + 1);
    int ulen = 0;

    if ((cp != NULL) && (str != NULL)) {
	ulen = mkutf(cp, str, len);
    }
    v = RB_TAINTED_STR_NEW((cp != NULL) ? cp : "", ulen);
#ifdef USE_RB_ENC
    rb_enc_associate(v, rb_enc);
#endif
    if (cp != NULL) {
	xfree(cp);
    }
    return v;
}

static VALUE
uc_tainted_str_new2(SQLWCHAR *str)
{
    return uc_tainted_str_new(str, uc_strlen(str));
}

static VALUE
uc_str_new(SQLWCHAR *str, int len)
{
    VALUE v;
    char *cp = xmalloc(len * 6 + 1);
    int ulen = 0;

    if ((cp != NULL) && (str != NULL)) {
	ulen = mkutf(cp, str, len);
    }
#ifdef USE_RB_ENC
    v = rb_enc_str_new((cp != NULL) ? cp : "", ulen, rb_enc);
#else
    v = rb_str_new((cp != NULL) ? cp : "", ulen);
#endif
    if (cp != NULL) {
	xfree(cp);
    }
    return v;
}

static VALUE
uc_str_new2(SQLWCHAR *str)
{
    return uc_str_new(str, uc_strlen(str));
}

static VALUE
uc_str_cat(VALUE v, SQLWCHAR *str, int len)
{
    VALUE vv = v;
    char *cp = xmalloc(len * 6 + 1);
    int ulen = 0;

    if ((cp != NULL) && (str != NULL)) {
	ulen = mkutf(cp, str, len);
    }
    if (cp != NULL) {
	vv = rb_str_cat(v, cp, ulen);
	xfree(cp);
    }
    return vv;
}

static SQLWCHAR *
uc_from_utf(unsigned char *str, int len)
{
    SQLWCHAR *uc = NULL;

    if (str != NULL) {
	int i = 0;
	unsigned char *strend;

	if (len < 0) {
	    len = strlen((char *) str);
	}
	strend = str + len;
	uc = ALLOC_N(SQLWCHAR, len + 1);
	if (uc != NULL) {
	    while (str < strend) {
		unsigned char c = str[0];

		if (c < 0x80) {
		    uc[i++] = c;
		    ++str;
		} else if ((c <= 0xc1) || (c >= 0xf5)) {
		    /* illegal, ignored */
		    ++str;
		} else if (c < 0xe0) {
		    if ((str[1] & 0xc0) == 0x80) {
			unsigned long t = ((c & 0x1f) << 6) | (str[1] & 0x3f);

			uc[i++] = t;
			str += 2;
		    } else {
			uc[i++] = c;
			++str;
		    }
		} else if (c < 0xf0) {
		    if (((str[1] & 0xc0) == 0x80) &&
			((str[2] & 0xc0) == 0x80)) {
			unsigned long t = ((c & 0x0f) << 12) |
			    ((str[1] & 0x3f) << 6) | (str[2] & 0x3f);

			uc[i++] = t;
			str += 3;
		    } else {
			uc[i++] = c;
			++str;
		    }
		} else if (c < 0xf8) {
		    if (((str[1] & 0xc0) == 0x80) &&
			((str[2] & 0xc0) == 0x80) &&
			((str[3] & 0xc0) == 0x80)) {
			unsigned long t = ((c & 0x03) << 18) |
			    ((str[1] & 0x3f) << 12) | ((str[2] & 0x3f) << 6) |
			    (str[4] & 0x3f);

			if ((sizeof (SQLWCHAR) == (2 * sizeof (char))) &&
			    (t >= 0x10000)) {
			    t -= 0x10000;
			    uc[i++] = 0xd800 | (t & 0x3ff);
			    t = 0xdc00 | ((t >> 10) & 0x3ff);
			}
			uc[i++] = t;
			str += 4;
		    } else {
			uc[i++] = c;
			++str;
		    }
		} else if (c < 0xfc) {
		    if (((str[1] & 0xc0) == 0x80) &&
			((str[2] & 0xc0) == 0x80) &&
			((str[3] & 0xc0) == 0x80) &&
			((str[4] & 0xc0) == 0x80)) {
			unsigned long t = ((c & 0x01) << 24) |
			    ((str[1] & 0x3f) << 18) | ((str[2] & 0x3f) << 12) |
			    ((str[4] & 0x3f) << 6) | (str[5] & 0x3f);

			if ((sizeof (SQLWCHAR) == (2 * sizeof (char))) &&
			    (t >= 0x10000)) {
			    t -= 0x10000;
			    uc[i++] = 0xd800 | (t & 0x3ff);
			    t = 0xdc00 | ((t >> 10) & 0x3ff);
			}
			uc[i++] = t;
			str += 5;
		    } else {
			uc[i++] = c;
			++str;
		    }
		} else {
		    /* ignore */
		    ++str;
		}
	    }
	    uc[i] = 0;
	}
    }
    return uc;
}

static void
uc_free(SQLWCHAR *str)
{
    if (str != NULL) {
	xfree(str);
    }
}

#endif


/*
 *----------------------------------------------------------------------
 *
 *	Things for ODBC::DSN
 *
 *----------------------------------------------------------------------
 */

#ifndef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
dsn_new(VALUE self)
{
    VALUE obj = rb_obj_alloc(Cdsn);

    rb_obj_call_init(obj, 0, NULL);
    return obj;
}
#endif

static VALUE
dsn_init(VALUE self)
{
    rb_iv_set(self, "@name", Qnil);
    rb_iv_set(self, "@descr", Qnil);
    return self;
}

/*
 *----------------------------------------------------------------------
 *
 *	Things for ODBC::Driver
 *
 *----------------------------------------------------------------------
 */

#ifndef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
drv_new(VALUE self)
{
    VALUE obj = rb_obj_alloc(Cdrv);

    rb_obj_call_init(obj, 0, NULL);
    return obj;
}
#endif

static VALUE
drv_init(VALUE self)
{
    rb_iv_set(self, "@name", Qnil);
    rb_iv_set(self, "@attrs", rb_hash_new());
    return self;
}

/*
 *----------------------------------------------------------------------
 *
 *	Cleanup routines and GC mark/free callbacks.
 *
 *----------------------------------------------------------------------
 */

static void
list_init(LINK *link, int offs)
{
    link->succ = link->pred = link->head = NULL;
    link->offs = offs;
}

static void
list_add(LINK *link, LINK *head)
{
    if (link->head != NULL) {
	rb_fatal("RubyODBC: already in list");
    }
    if (head == NULL) {
	rb_fatal("RubyODBC: invalid list head");
    }
    link->head = head;
    link->pred = NULL;
    link->succ = head->succ;
    head->succ = link;
    if (link->succ != NULL) {
	link->succ->pred = link;
    }
}

static void
list_del(LINK *link)
{
    if (link == NULL) {
	rb_fatal("RubyODBC: invalid list item");
    }
    if (link->head == NULL) {
	rb_fatal("RubyODBC: item not in list");
    }
    if (link->succ != NULL) {
	link->succ->pred = link->pred;
    }
    if (link->pred != NULL) {
	link->pred->succ = link->succ;
    } else {
	link->head->succ = link->succ;
    }
    link->succ = link->pred = link->head = NULL;
}

static void *
list_first(LINK *head)
{
    if (head->succ == NULL) {
	return NULL;
    }
    return (void *) ((char *) head->succ - head->offs);
}

static int
list_empty(LINK *head)
{
    return head->succ == NULL;
}

static void
free_env(ENV *e)
{
    e->self = Qnil;
    if (!list_empty(&e->dbcs)) {
	return;
    }
    tracemsg(2, fprintf(stderr, "ObjFree: ENV %p\n", e););
    if (e->henv != SQL_NULL_HENV) {
	callsql(SQL_NULL_HENV, e->henv, SQL_NULL_HSTMT,
		SQLFreeEnv(e->henv), "SQLFreeEnv");
	e->henv = SQL_NULL_HENV;
    }
    xfree(e);
}

static void
link_dbc(DBC *p, ENV *e)
{
    p->envp = e;
    list_add(&p->link, &e->dbcs);
}

static void
unlink_dbc(DBC *p)
{
    if (p == NULL) {
	return;
    }
    p->env = Qnil;
    if (p->envp != NULL) {
	ENV *e = p->envp;

	list_del(&p->link);
	if (e->self == Qnil) {
	    free_env(e);
	}
	p->envp = NULL;
    }
}

static void
free_dbc(DBC *p)
{
    p->self = p->env = Qnil;
    if (!list_empty(&p->stmts)) {
	return;
    }
    tracemsg(2, fprintf(stderr, "ObjFree: DBC %p\n", p););
    if (p->hdbc != SQL_NULL_HDBC) {
	callsql(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		SQLDISCONNECT(p->hdbc), "SQLDisconnect");
	callsql(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		SQLFreeConnect(p->hdbc), "SQLFreeConnect");
	p->hdbc = SQL_NULL_HDBC;
    }
    unlink_dbc(p);
    xfree(p);
}

static void
free_stmt_sub(STMT *q, int withp)
{
    int i;

    if (withp) {
	if (q->paraminfo != NULL) {
	    for (i = 0; i < q->nump; i++) {
		if (q->paraminfo[i].outbuf != NULL) {
		    xfree(q->paraminfo[i].outbuf);
		}
	    }
	    xfree(q->paraminfo);
	    q->paraminfo = NULL;
	}
	q->nump = 0;
    }
    q->ncols = 0;
    if (q->coltypes != NULL) {
	xfree(q->coltypes);
	q->coltypes = NULL;
    }
    if (q->colnames != NULL) {
	xfree(q->colnames);
	q->colnames = NULL;
    }
    if (q->colvals != NULL) {
	xfree(q->colvals);
	q->colvals = NULL;
    }
    if (q->dbufs != NULL) {
	xfree(q->dbufs);
	q->dbufs = NULL;
    }
    if (q->self != Qnil) {
	VALUE v;

	v = rb_iv_get(q->self, "@_a");
	if (v != Qnil) {
	    rb_ary_clear(v);
	}
	v = rb_iv_get(q->self, "@_h");
	if (v != Qnil) {
	    rb_iv_set(q->self, "@_h", rb_hash_new());
	}
	for (i = 0; i < 4; i++) {
	    v = rb_iv_get(q->self, colnamebuf[i]);
	    if (v != Qnil) {
		rb_iv_set(q->self, colnamebuf[i], rb_hash_new());
	    }
	}
    }
}

static void
link_stmt(STMT *q, DBC *p)
{
    q->dbcp = p;
    list_add(&q->link, &p->stmts);
}

static void
unlink_stmt(STMT *q)
{
    if (q == NULL) {
	return;
    }
    q->dbc = Qnil;
    if (q->dbcp != NULL) {
	DBC *p = q->dbcp;

	list_del(&q->link);
	if (p->self == Qnil) {
	    free_dbc(p);
	}
	q->dbcp = NULL;
    }
}

static void
free_stmt(STMT *q)
{
    VALUE qself = q->self;

    q->self = q->dbc = Qnil;
    free_stmt_sub(q, 1);
    tracemsg(2, fprintf(stderr, "ObjFree: STMT %p\n", q););
    if (q->hstmt != SQL_NULL_HSTMT) {
	/* Issue warning message. */
	fprintf(stderr,	"WARNING: #<ODBC::Statement:0x%lx> was not dropped"
		" before garbage collection.\n", (long) qself);
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		SQLFreeStmt(q->hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
	q->hstmt = SQL_NULL_HSTMT;
    }
    unlink_stmt(q);
    xfree(q);
}

static void
start_gc()
{
    FUNCALL_NOARGS(rb_mGC, IDstart);
}

static void
mark_dbc(DBC *p)
{
    if (p->env != Qnil) {
	rb_gc_mark(p->env);
    }
}

static void
mark_stmt(STMT *q)
{
    if (q->dbc != Qnil) {
	rb_gc_mark(q->dbc);
    }
}

/*
 *----------------------------------------------------------------------
 *
 *	Set internal error (or warning) message.
 *
 *----------------------------------------------------------------------
 */

static char *
set_err(const char *msg, int warn)
{
    VALUE a, v = rb_str_new2("INTERN (0) [RubyODBC]");

    v = rb_str_cat2(v, msg);
#ifdef USE_RB_ENC
    rb_enc_associate(v, rb_enc);
#endif
    a = rb_ary_new2(1);
    rb_ary_push(a, RB_OBJ_TAINT(v));
    CVAR_SET(Cobj, warn ? IDatatinfo : IDataterror, a);
    return STR2CSTR(v);
}

/*
 *----------------------------------------------------------------------
 *
 *	Functions to retrieve last SQL error or warning.
 *
 *----------------------------------------------------------------------
 */

static char *
get_err_or_info(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, int isinfo)
{
#ifdef UNICODE
    SQLWCHAR msg[SQL_MAX_MESSAGE_LENGTH], state[6 + 1];
#else
    SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH], state[6 + 1];
#endif
    char buf[32], tmp[SQL_MAX_MESSAGE_LENGTH];
    SQLRETURN err;
    SQLINTEGER nativeerr;
    SQLSMALLINT len;
    VALUE v0 = Qnil, a = Qnil, v;
    int done = 0;

    while (!done) {
	v = Qnil;
	err = tracesql(henv, hdbc, hstmt,
		       SQLError(henv, hdbc, hstmt, state, &nativeerr, msg,
		       SQL_MAX_MESSAGE_LENGTH - 1, &len),
		       "SQLError");
	state[6] = '\0';
	msg[SQL_MAX_MESSAGE_LENGTH - 1] = '\0';
	switch (err) {
	case SQL_SUCCESS:
	case SQL_SUCCESS_WITH_INFO:
#ifdef UNICODE
	    v = uc_str_new2(state);
#else
	    v = rb_str_new2((char *) state);
#endif
	    sprintf(buf, " (%d) ", (int) nativeerr);
	    v = rb_str_cat2(v, buf);
#ifdef UNICODE
	    v = uc_str_cat(v, msg, len);
#else
	    v = rb_str_cat(v, (char *) msg, len);
#endif
	    break;
	case SQL_NO_DATA:
	    if ((v0 == Qnil) && (!isinfo)) {
		v = rb_str_new2("INTERN (0) [RubyODBC]No data found");
	    } else {
		v = Qnil;
	    }
	    done = 1;
	    break;
	case SQL_INVALID_HANDLE:
	    v = rb_str_new2("INTERN (0) [RubyODBC]Invalid handle");
	    done = 1;
	    break;
	case SQL_ERROR:
	    v = rb_str_new2("INTERN (0) [RubyODBC]Error reading error message");
	    done = 1;
	    break;
	default:
	    sprintf(tmp, "INTERN (0) [RubyODBC]Unknown error %d", (int) err);
	    v = rb_str_new2(tmp);
	    done = 1;
	    break;
	}
	if (v != Qnil) {
	    if (v0 == Qnil) {
		v0 = v;
		a = rb_ary_new();
	    }
	    rb_ary_push(a, RB_OBJ_TAINT(v));
	    tracemsg(1, fprintf(stderr, "  | %s\n", STR2CSTR(v)););
	}
    }
    CVAR_SET(Cobj, isinfo ? IDatatinfo : IDataterror, a);
    if (isinfo) {
	return NULL;
    }
    return (v0 == Qnil) ? NULL : STR2CSTR(v0);
}

#if defined(HAVE_SQLINSTALLERERROR) || (defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW))
static char *
get_installer_err()
{
#ifdef UNICODE
#ifdef HAVE_SQLINSTALLERERRORW
    int have_w = 1;
#else
    int have_w = 0;
#endif
    SQLWCHAR msg[SQL_MAX_MESSAGE_LENGTH];
#else
    char msg[SQL_MAX_MESSAGE_LENGTH];
#endif
    char buf[128];
    SQLRETURN err;
    VALUE v0 = Qnil, a = Qnil, v;
    int done = 0;
    WORD i, len;
    DWORD insterrcode;

    for (i = 1; (!done) && (i <= 8); i++) {
	v = Qnil;
#ifdef UNICODE
#ifdef USE_DLOPEN_FOR_ODBC_LIBS
	have_w = ruby_odbc_have_func("SQLInstallerErrorW", SQLInstallerErrorW);
#endif
	if (have_w) {
	    err = tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, SQL_NULL_HSTMT,
			   SQLInstallerErrorW(i, &insterrcode, msg,
			   SQL_MAX_MESSAGE_LENGTH, &len),
			   "SQLInstallerErrorW");
	    msg[SQL_MAX_MESSAGE_LENGTH - 1] = 0;
	} else {
	    err = tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, SQL_NULL_HSTMT,
			   SQLInstallerError(i, &insterrcode, (char *) msg,
			   SQL_MAX_MESSAGE_LENGTH, &len),
			   "SQLInstallerErrorW");
	    ((char *) msg)[SQL_MAX_MESSAGE_LENGTH - 1] = '\0';
	}
#else
	err = tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		       SQLInstallerError(i, &insterrcode, msg,
					 SQL_MAX_MESSAGE_LENGTH, &len),
		       "SQLInstallerError");
	msg[SQL_MAX_MESSAGE_LENGTH - 1] = '\0';
#endif
	switch (err) {
	case SQL_SUCCESS:
	case SQL_SUCCESS_WITH_INFO:
	    sprintf(buf, "INSTALLER (%d) ", (int) insterrcode);
	    v = rb_str_new2(buf);
#ifdef UNICODE
	    if (have_w) {
#ifdef USE_RB_ENC
		rb_enc_associate(v, rb_enc);
#endif
		v = uc_str_cat(v, msg, len);
	    } else {
		v = rb_str_cat(v, (char *) msg, len);
	    }
#else
	    v = rb_str_cat(v, msg, len);
#endif
	    break;
	case SQL_NO_DATA:
	    done = 1;
	    break;
	case SQL_ERROR:
	    v = rb_str_new2("INTERN (0) [RubyODBC]");
	    v = rb_str_cat2(v, "Error reading installer error message");
	    done = 1;
	    break;
	default:
	    v = rb_str_new2("INTERN (0) [RubyODBC]");
	    sprintf(buf, "Unknown installer error %d", (int) err);
	    v = rb_str_cat2(v, buf);
	    done = 1;
	    break;
	}
	if (v != Qnil) {
	    if (v0 == Qnil) {
		v0 = v;
		a = rb_ary_new();
	    }
	    rb_ary_push(a, RB_OBJ_TAINT(v));
	    tracemsg(1, fprintf(stderr, "  | %s\n", STR2CSTR(v)););
	}
    }
    CVAR_SET(Cobj, IDataterror, a);
    return (v0 == Qnil) ? NULL : STR2CSTR(v0);
}
#endif

static char *
get_err(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt)
{
    return get_err_or_info(henv, hdbc, hstmt, 0);
}

#ifdef TRACING
static void
trace_sql_ret(SQLRETURN ret)
{
    char msg[32];
    const char *p;

    switch (ret) {
    case SQL_SUCCESS:
	p = "SQL_SUCCESS";
	break;
    case SQL_SUCCESS_WITH_INFO:
	p = "SQL_SUCCESS_WITH_INFO";
	break;
    case SQL_NO_DATA:
	p = "SQL_NO_DATA";
	break;
    case SQL_ERROR:
	p = "SQL_ERROR";
	break;
    case SQL_INVALID_HANDLE:
	p = "SQL_INVALID_HANDLE";
	break;
    default:
	sprintf(msg, "SQL_RETURN=%d", (int) ret);
	p = msg;
	break;
    }
    fprintf(stderr, "  < %s\n", p);
}

static SQLRETURN
tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret,
	 const char *m)
{
    if (tracing & 1) {
	fprintf(stderr, "SQLCall: %s", m);
	fprintf(stderr, "\n  > HENV=0x%lx, HDBC=0x%lx, HSTMT=0x%lx\n",
		(long) henv, (long) hdbc, (long) hstmt);
	trace_sql_ret(ret);
    }
    return ret;
}
#endif

static SQLRETURN
callsql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret,
	const char *m)
{
    SQLRETURN err;

    err = tracesql(henv, hdbc, hstmt, ret, m);
    if (err != SQL_SUCCESS) {
#ifdef UNICODE
	SQLWCHAR msg[SQL_MAX_MESSAGE_LENGTH], state[6 + 1];
#else
	SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH], state[6 + 1];
#endif
	SQLINTEGER nativeerr;
	SQLSMALLINT len;
	int done = 0;

	while (!done) {
	    err = tracesql(henv, hdbc, hstmt,
			   SQLError(henv, hdbc, hstmt, state, &nativeerr, msg,
				    SQL_MAX_MESSAGE_LENGTH - 1, &len),
			   "SQLError");
	    switch (err) {
	    case SQL_SUCCESS:
		break;
	    case SQL_NO_DATA:
	    case SQL_INVALID_HANDLE:
	    case SQL_ERROR:
	    default:
		done = 1;
		break;
	    }
	}
    }
    return ret;
}

static int
succeeded_common(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret,
		 char **msgp)
{
    if (!SQL_SUCCEEDED(ret)) {
	char *dummy;

	if (msgp == NULL) {
	    msgp = &dummy;
	}
	*msgp = get_err_or_info(henv, hdbc, hstmt, 0);
	return 0;
    }
    if (ret == SQL_SUCCESS_WITH_INFO) {
	get_err_or_info(henv, hdbc, hstmt, 1);
    } else {
	CVAR_SET(Cobj, IDatatinfo, Qnil);
    }
    return 1;
}

static int
succeeded(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret,
	  char **msgp, const char *m, ...)
{
#ifdef TRACING
    va_list args;

    if (tracing & 1) {
	va_start(args, m);
	fprintf(stderr, "SQLCall: ");
	vfprintf(stderr, m, args);
	va_end(args);
	fprintf(stderr, "\n  > HENV=0x%lx, HDBC=0x%lx, HSTMT=0x%lx\n",
		(long) henv, (long) hdbc, (long) hstmt);
	trace_sql_ret(ret);
    }
#endif
    return succeeded_common(henv, hdbc, hstmt, ret, msgp);
}

static int
succeeded_nodata(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret,
		 char **msgp, const char *m, ...)
{
#ifdef TRACING
    va_list args;

    if (tracing & 1) {
	va_start(args, m);
	fprintf(stderr, "SQLCall: ");
	vfprintf(stderr, m, args);
	va_end(args);
	fprintf(stderr, "\n  > HENV=0x%lx, HDBC=0x%lx, HSTMT=0x%lx\n",
		(long) henv, (long) hdbc, (long) hstmt);
	trace_sql_ret(ret);
    }
#endif
    if (ret == SQL_NO_DATA) {
	CVAR_SET(Cobj, IDatatinfo, Qnil);
	return 1;
    }
    return succeeded_common(henv, hdbc, hstmt, ret, msgp);
}

/*
 *----------------------------------------------------------------------
 *
 *	Return ENV from VALUE.
 *
 *----------------------------------------------------------------------
 */

static VALUE
env_of(VALUE self)
{
    if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) {
	STMT *q;

	Data_Get_Struct(self, STMT, q);
	self = q->dbc;
	if (self == Qnil) {
	    rb_raise(Cerror, "%s", set_err("Stale ODBC::Statement", 0));
	}
    }
    if (rb_obj_is_kind_of(self, Cdbc) == Qtrue) {
	DBC *p;

	Data_Get_Struct(self, DBC, p);
	self = p->env;
	if (self == Qnil) {
	    rb_raise(Cerror, "%s", set_err("Stale ODBC::Database", 0));
	}
    }
    return self;
}

static ENV *
get_env(VALUE self)
{
    ENV *e;

    Data_Get_Struct(env_of(self), ENV, e);
    return e;
}

/*
 *----------------------------------------------------------------------
 *
 *	Return DBC from VALUE.
 *
 *----------------------------------------------------------------------
 */

static DBC *
get_dbc(VALUE self)
{
    DBC *p;

    if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) {
	STMT *q;

	Data_Get_Struct(self, STMT, q);
	self = q->dbc;
	if (self == Qnil) {
	    rb_raise(Cerror, "%s", set_err("Stale ODBC::Statement", 0));
	}
    }
    Data_Get_Struct(self, DBC, p);
    return p;
}

/*
 *----------------------------------------------------------------------
 *
 *	Raise ODBC error from Ruby.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_raise(VALUE self, VALUE msg)
{
    VALUE a, v;
    char buf[SQL_MAX_MESSAGE_LENGTH + 1], *p;

    if (TYPE(msg) == T_STRING) {
	v = msg;
    } else {
	v = rb_any_to_s(msg);
    }
    strcpy(buf, "INTERN (1) [RubyODBC]");
    p = STR2CSTR(v);
    strncat(buf, p, SQL_MAX_MESSAGE_LENGTH - strlen(buf));
    buf[SQL_MAX_MESSAGE_LENGTH] = '\0';
    v = rb_str_new2(buf);
    a = rb_ary_new2(1);
    rb_ary_push(a, RB_OBJ_TAINT(v));
    CVAR_SET(Cobj, IDataterror, a);
    rb_raise(Cerror, "%s", buf);
    return Qnil;
}

/*
 *----------------------------------------------------------------------
 *
 *	Obtain an ENV.
 *
 *----------------------------------------------------------------------
 */

static VALUE
env_new(VALUE self)
{
    ENV *e;
    SQLHENV henv = SQL_NULL_HENV;
    VALUE obj;

    if (TYPE(self) == T_MODULE) {
	self = Cobj;
    }
    if (self == Cobj) {
	self = Cenv;
    }
    if ((!SQL_SUCCEEDED(SQLAllocEnv(&henv))) || (henv == SQL_NULL_HENV)) {
	rb_raise(Cerror, "%s", set_err("Cannot allocate SQLHENV", 0));
    }
    obj = Data_Make_Struct(self, ENV, NULL, free_env, e);
    tracemsg(2, fprintf(stderr, "ObjAlloc: ENV %p\n", e););
    e->self = obj;
    e->henv = henv;
    list_init(&e->dbcs, offsetof(DBC, link));
#if defined(UNICODE) && defined(SQL_OV_ODBC3)
    callsql(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
	    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
			  (SQLPOINTER) SQL_OV_ODBC3, 0),
	    "SQLSetEnvAttr(SQL_OV_ODBC3)");
#endif
    return obj;
}

/*
 *----------------------------------------------------------------------
 *
 *	Obtain array of known DSNs.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_dsns(VALUE self)
{
#ifdef UNICODE
    SQLWCHAR dsn[SQL_MAX_DSN_LENGTH], descr[SQL_MAX_MESSAGE_LENGTH * 2];
#else
    char dsn[SQL_MAX_DSN_LENGTH], descr[SQL_MAX_MESSAGE_LENGTH * 2];
#endif
    SQLSMALLINT dsnLen = 0, descrLen = 0;
    int first = 1;
    VALUE env, aret;
    ENV *e;

    env = env_new(Cenv);
    Data_Get_Struct(env, ENV, e);
    aret = rb_ary_new();
    while (succeeded(e->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		     SQLDataSources(e->henv, (SQLUSMALLINT) (first ?
				    SQL_FETCH_FIRST : SQL_FETCH_NEXT),
				    (SQLTCHAR *) dsn,
				    (SQLSMALLINT) sizeof (dsn), &dsnLen,
				    (SQLTCHAR *) descr,
				    (SQLSMALLINT) sizeof (descr),
				    &descrLen),
		     NULL, "SQLDataSources")) {
	VALUE odsn = rb_obj_alloc(Cdsn);

#ifdef UNICODE
	dsnLen = (dsnLen == 0) ? (SQLSMALLINT) uc_strlen(dsn) :
	    (SQLSMALLINT) (dsnLen / sizeof (SQLWCHAR));
	descrLen = (descrLen == 0) ?
	    (SQLSMALLINT) uc_strlen(descr) :
	    (SQLSMALLINT) (descrLen / sizeof (SQLWCHAR));
	rb_iv_set(odsn, "@name", uc_tainted_str_new(dsn, dsnLen));
	rb_iv_set(odsn, "@descr", uc_tainted_str_new(descr, descrLen));
#else
	dsnLen = (dsnLen == 0) ? (SQLSMALLINT) strlen(dsn) : dsnLen;
	descrLen = (descrLen == 0) ? (SQLSMALLINT) strlen(descr) : descrLen;
	rb_iv_set(odsn, "@name", RB_TAINTED_STR_NEW(dsn, dsnLen));
	rb_iv_set(odsn, "@descr", RB_TAINTED_STR_NEW(descr, descrLen));
#endif
	rb_ary_push(aret, odsn);
	first = dsnLen = descrLen = 0;
    }
    return aret;
}

/*
 *----------------------------------------------------------------------
 *
 *	Obtain array of known drivers.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_drivers(VALUE self)
{
#ifdef UNICODE
    SQLWCHAR driver[SQL_MAX_MESSAGE_LENGTH], attrs[SQL_MAX_MESSAGE_LENGTH * 2];
    SQLWCHAR *attr;
#else
    char driver[SQL_MAX_MESSAGE_LENGTH], attrs[SQL_MAX_MESSAGE_LENGTH * 2];
    char *attr;
#endif
    SQLSMALLINT driverLen = 0, attrsLen = 0;
    int first = 1;
    VALUE env, aret;
    ENV *e;

    env = env_new(Cenv);
    Data_Get_Struct(env, ENV, e);
    aret = rb_ary_new();
    while (succeeded(e->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		     SQLDrivers(e->henv, (SQLUSMALLINT) (first ?
				SQL_FETCH_FIRST : SQL_FETCH_NEXT),
				(SQLTCHAR *) driver,
				(SQLSMALLINT) sizeof (driver), &driverLen,
				(SQLTCHAR *) attrs,
				(SQLSMALLINT) sizeof (attrs), &attrsLen),
		     NULL, "SQLDrivers")) {
	VALUE odrv = rb_obj_alloc(Cdrv);
	VALUE h = rb_hash_new();
	int count = 0;

#ifdef UNICODE
	driverLen = (driverLen == 0) ?
	    (SQLSMALLINT) uc_strlen(driver) :
	    (SQLSMALLINT) (driverLen / sizeof (SQLWCHAR));
	rb_iv_set(odrv, "@name", uc_tainted_str_new(driver, driverLen));
	for (attr = attrs; *attr; attr += uc_strlen(attr) + 1) {
	    SQLWCHAR *p = uc_strchr(attr, (SQLWCHAR) '=');

	    if ((p != NULL) && (p != attr)) {
		rb_hash_aset(h,
			     uc_tainted_str_new(attr, (p - attr) /
						 sizeof (SQLWCHAR)),
			     uc_tainted_str_new2(p + 1));
		count++;
	    }
	}
#else
	driverLen = (driverLen == 0) ? (SQLSMALLINT) strlen(driver) : driverLen;
	rb_iv_set(odrv, "@name", RB_TAINTED_STR_NEW(driver, driverLen));
	for (attr = attrs; *attr; attr += strlen(attr) + 1) {
	    char *p = strchr(attr, '=');

	    if ((p != NULL) && (p != attr)) {
		rb_hash_aset(h, RB_TAINTED_STR_NEW(attr, p - attr),
			     RB_TAINTED_STR_NEW2(p + 1));
		count++;
	    }
	}
#endif
	if (count > 0) {
	    rb_iv_set(odrv, "@attrs", h);
	}
	rb_ary_push(aret, odrv);
	first = driverLen = attrsLen = 0;
    }
    return aret;
}

/*
 *----------------------------------------------------------------------
 *
 *	Management methods.
 *
 *----------------------------------------------------------------------
 */

#ifdef HAVE_ODBCINST_H
static VALUE
conf_dsn(int argc, VALUE *argv, VALUE self, int op)
{
    VALUE drv, attr, issys, astr;
#ifdef UNICODE
#ifdef HAVE_SQLCONFIGDATASOURCEW
    int have_w = 1;
#else
    int have_w = 0;
#endif
    SQLWCHAR *sdrv, *sastr;
#else
    char *sdrv, *sastr;
#endif

    rb_scan_args(argc, argv, "12", &drv, &attr, &issys);
    if (rb_obj_is_kind_of(drv, Cdrv) == Qtrue) {
	VALUE a, x;

	if (argc > 2) {
	    rb_raise(rb_eArgError, "wrong # of arguments");
	}
	x = rb_iv_get(drv, "@name");
	a = rb_iv_get(drv, "@attrs");
	issys = attr;
	drv = x;
	attr = a;
    }
    Check_Type(drv, T_STRING);
    if (RTEST(issys)) {
	switch (op) {
	case ODBC_ADD_DSN:	op = ODBC_ADD_SYS_DSN; break;
	case ODBC_CONFIG_DSN:	op = ODBC_CONFIG_SYS_DSN; break;
	case ODBC_REMOVE_DSN:	op = ODBC_REMOVE_SYS_DSN; break;
	}
    }
    astr = rb_str_new2("");
    if (rb_obj_is_kind_of(attr, rb_cHash) == Qtrue) {
	VALUE a, x;

	a = FUNCALL_NOARGS(attr, IDkeys);
	while ((x = rb_ary_shift(a)) != Qnil) {
	    VALUE v = rb_hash_aref(attr, x);

	    astr = rb_str_concat(astr, x);
	    astr = rb_str_cat2(astr, "=");
	    astr = rb_str_concat(astr, v);
	    astr = rb_str_cat(astr, "", 1);
	}
    }
    astr = rb_str_cat(astr, "", 1);
#ifdef UNICODE
#ifdef USE_DLOPEN_FOR_ODBC_LIBS
    have_w = ruby_odbc_have_func("SQLConfigDataSourceW", SQLConfigDataSourceW);
#endif
    if (have_w) {
#ifdef USE_RB_ENC
	drv = rb_funcall(drv, IDencode, 1, rb_encv);
	astr = rb_funcall(astr, IDencode, 1, rb_encv);
#endif
	sdrv = uc_from_utf((unsigned char *) STR2CSTR(drv), -1);
	sastr = uc_from_utf((unsigned char *) STR2CSTR(astr), -1);
	if ((sdrv == NULL) || (sastr == NULL)) {
	    uc_free(sdrv);
	    uc_free(sastr);
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
	if (SQLConfigDataSourceW(NULL, (WORD) op,
				 (LPWSTR) sdrv, (LPWSTR) sastr)) {
	    uc_free(sdrv);
	    uc_free(sastr);
	    return Qnil;
	}
	uc_free(sdrv);
	uc_free(sastr);
    } else {
	sdrv = (SQLWCHAR *) STR2CSTR(drv);
	sastr = (SQLWCHAR *) STR2CSTR(astr);
	if (SQLConfigDataSource(NULL, (WORD) op,
				(LPCSTR) sdrv, (LPCSTR) sastr)) {
	    return Qnil;
	}
    }
#else
    sdrv = STR2CSTR(drv);
    sastr = STR2CSTR(astr);
    if (SQLConfigDataSource(NULL, (WORD) op, sdrv, sastr)) {
	return Qnil;
    }
#endif
#if defined(HAVE_SQLINSTALLERERROR) || (defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW))
    rb_raise(Cerror, "%s", set_err(get_installer_err(), 0));
#else
    rb_raise(Cerror, "%s", set_err("DSN configuration error", 0));
#endif
    return Qnil;
}
#endif

static VALUE
dbc_adddsn(int argc, VALUE *argv, VALUE self)
{
#ifdef HAVE_ODBCINST_H
    return conf_dsn(argc, argv, self, ODBC_ADD_DSN);
#else
    rb_raise(Cerror, "%s", set_err("ODBC::add_dsn not supported", 0));
    return Qnil;
#endif
}

static VALUE
dbc_confdsn(int argc, VALUE *argv, VALUE self)
{
#ifdef HAVE_ODBCINST_H
    return conf_dsn(argc, argv, self, ODBC_CONFIG_DSN);
#else
    rb_raise(Cerror, "%s", set_err("ODBC::config_dsn not supported", 0));
    return Qnil;
#endif
}

static VALUE
dbc_deldsn(int argc, VALUE *argv, VALUE self)
{
#ifdef HAVE_ODBCINST_H
    return conf_dsn(argc, argv, self, ODBC_REMOVE_DSN);
#else
    rb_raise(Cerror, "%s", set_err("ODBC::del_dsn not supported", 0));
    return Qnil;
#endif
}

static VALUE
dbc_wfdsn(int argc, VALUE *argv, VALUE self)
{
#ifdef HAVE_ODBCINST_H
    VALUE fname, aname, kname, val;
#ifdef UNICODE
#ifdef HAVE_SQLWRITEFILEDSNW
    int have_w = 1;
#else
    int have_w = 0;
#endif
    SQLWCHAR *sfname, *saname, *skname, *sval = NULL;
#else
    char *sfname, *saname, *skname, *sval = NULL;
#endif

    rb_scan_args(argc, argv, "31", &fname, &aname, &kname, &val);
    Check_Type(fname, T_STRING);
    Check_Type(aname, T_STRING);
    Check_Type(kname, T_STRING);
    if (val != Qnil) {
	Check_Type(val, T_STRING);
    }
#ifdef UNICODE
#ifdef USE_DLOPEN_FOR_ODBC_LIBS
    have_w = ruby_odbc_have_func("SQLWriteFileDSNW", SQLWriteFileDSNW);
#endif
    if (have_w) {
	BOOL rc;

#ifdef USE_RB_ENC
	fname = rb_funcall(fname, IDencode, 1, rb_encv);
	aname = rb_funcall(aname, IDencode, 1, rb_encv);
	kname = rb_funcall(kname, IDencode, 1, rb_encv);
	if (val != Qnil) {
	    val = rb_funcall(val, IDencode, 1, rb_encv);
	}
#endif
	sfname = uc_from_utf((unsigned char *) STR2CSTR(fname), -1);
	saname = uc_from_utf((unsigned char *) STR2CSTR(aname), -1);
	skname = uc_from_utf((unsigned char *) STR2CSTR(kname), -1);
	if ((sfname == NULL) || (saname == NULL) || (skname == NULL)) {
nomem:
	    uc_free(sfname);
	    uc_free(saname);
	    uc_free(skname);
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
	if (val != Qnil) {
	    sval = uc_from_utf((unsigned char *) STR2CSTR(val), -1);
	    if (sval == NULL) {
		goto nomem;
	    }
	}
	rc = SQLWriteFileDSNW(sfname, saname, skname, sval);
	uc_free(sfname);
	uc_free(saname);
	uc_free(skname);
	uc_free(sval);
	if (rc) {
	    return Qnil;
	}
    } else {
	sfname = (SQLWCHAR *) STR2CSTR(fname);
	saname = (SQLWCHAR *) STR2CSTR(aname);
	skname = (SQLWCHAR *) STR2CSTR(kname);
	if (val != Qnil) {
	    sval = (SQLWCHAR *) STR2CSTR(val);
	}
	if (SQLWriteFileDSN((LPCSTR) sfname, (LPCSTR) saname,
			    (LPCSTR) skname, (LPCSTR) sval)) {
	    return Qnil;
	}
    }
#else
    sfname = STR2CSTR(fname);
    saname = STR2CSTR(aname);
    skname = STR2CSTR(kname);
    if (val != Qnil) {
	sval = STR2CSTR(val);
    }
    if (SQLWriteFileDSN(sfname, saname, skname, sval)) {
	return Qnil;
    }
#endif
#if defined(HAVE_SQLINSTALLERERROR) || (defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW))
    rb_raise(Cerror, "%s", set_err(get_installer_err(), 0));
#else
    rb_raise(Cerror, "%s", set_err("File DSN configuration error", 0));
#endif
#else
    rb_raise(Cerror, "%s", set_err("ODBC::write_file_dsn not supported", 0));
#endif
    return Qnil;
}

static VALUE
dbc_rfdsn(int argc, VALUE *argv, VALUE self)
{
#ifdef HAVE_ODBCINST_H
    VALUE fname, aname, kname;
#ifdef UNICODE
#ifdef HAVE_SQLREADFILEDSNW
    int have_w = 1;
#else
    int have_w = 0;
#endif
    SQLWCHAR *sfname, *saname, *skname, valbuf[SQL_MAX_MESSAGE_LENGTH];
#else
    char *sfname, *saname, *skname, valbuf[SQL_MAX_MESSAGE_LENGTH];
#endif

    rb_scan_args(argc, argv, "30", &fname, &aname, &kname);
    Check_Type(fname, T_STRING);
    Check_Type(aname, T_STRING);
    Check_Type(kname, T_STRING);
#ifdef UNICODE
#ifdef USE_DLOPEN_FOR_ODBC_LIBS
    have_w = ruby_odbc_have_func("SQLReadFileDSNW", SQLReadFileDSNW);
#endif
    if (have_w) {
	BOOL rc;

#ifdef USE_RB_ENC
	fname = rb_funcall(fname, IDencode, 1, rb_encv);
	aname = rb_funcall(aname, IDencode, 1, rb_encv);
	kname = rb_funcall(kname, IDencode, 1, rb_encv);
#endif
	sfname = uc_from_utf((unsigned char *) STR2CSTR(fname), -1);
	saname = uc_from_utf((unsigned char *) STR2CSTR(aname), -1);
	skname = uc_from_utf((unsigned char *) STR2CSTR(kname), -1);
	valbuf[0] = 0;
	if ((sfname == NULL) || (saname == NULL) || (skname == NULL)) {
	    uc_free(sfname);
	    uc_free(saname);
	    uc_free(skname);
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
	rc = SQLReadFileDSNW(sfname, saname, skname, valbuf,
			     sizeof (valbuf), NULL);
	uc_free(sfname);
	uc_free(saname);
	uc_free(skname);
	if (rc) {
	    return uc_tainted_str_new2(valbuf);
	}
    } else {
	sfname = (SQLWCHAR *) STR2CSTR(fname);
	saname = (SQLWCHAR *) STR2CSTR(aname);
	skname = (SQLWCHAR *) STR2CSTR(kname);
	valbuf[0] = '\0';
	if (SQLReadFileDSN((LPCSTR) sfname, (LPCSTR) saname,
			   (LPCSTR) skname, (LPSTR) valbuf,
			   sizeof (valbuf), NULL)) {
	    return RB_TAINTED_STR_NEW2((char *) valbuf);
	}
    }
#else
    sfname = STR2CSTR(fname);
    saname = STR2CSTR(aname);
    skname = STR2CSTR(kname);
    valbuf[0] = '\0';
    if (SQLReadFileDSN(sfname, saname, skname, valbuf,
		       sizeof (valbuf), NULL)) {
	return RB_TAINTED_STR_NEW2(valbuf);
    }
#endif
#if defined(HAVE_SQLINSTALLERERROR) || (defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW))
    rb_raise(Cerror, "%s", set_err(get_installer_err(), 0));
#else
    rb_raise(Cerror, "%s", set_err("File DSN configuration error", 0));
#endif
#else
    rb_raise(Cerror, "%s", set_err("ODBC::read_file_dsn not supported", 0));
    return Qnil;
#endif
}

/*
 *----------------------------------------------------------------------
 *
 *	Return last ODBC error or warning.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_error(VALUE self)
{
    return rb_cvar_get(Cobj, IDataterror);
}

static VALUE
dbc_warn(VALUE self)
{
    return rb_cvar_get(Cobj, IDatatinfo);
}

static VALUE
dbc_clrerror(VALUE self)
{
    CVAR_SET(Cobj, IDataterror, Qnil);
    CVAR_SET(Cobj, IDatatinfo, Qnil);
    return Qnil;
}

/*
 *----------------------------------------------------------------------
 *
 *	Connection instance initializer.
 *
 *----------------------------------------------------------------------
 */

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
dbc_alloc(VALUE self)
{
    DBC *p;
    VALUE obj = Data_Make_Struct(self, DBC, mark_dbc, free_dbc, p);

    tracemsg(2, fprintf(stderr, "ObjAlloc: DBC %p\n", p););
    list_init(&p->link, offsetof(DBC, link));
    p->self = obj;
    p->env = Qnil;
    p->envp = NULL;
    list_init(&p->stmts, offsetof(STMT, link));
    p->hdbc = SQL_NULL_HDBC;
    p->rbtime = Qfalse;
    p->gmtime = Qfalse;
    return obj;
}
#endif

static VALUE
dbc_new(int argc, VALUE *argv, VALUE self)
{
    DBC *p;
    VALUE obj, env = Qnil;

    if (TYPE(self) == T_MODULE) {
	self = Cobj;
    }
    if (self == Cobj) {
	self = Cdbc;
    }
    if (rb_obj_is_kind_of(self, Cenv) == Qtrue) {
	env = env_of(self);
	self = Cdbc;
    }
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    obj = rb_obj_alloc(Cdbc);
    Data_Get_Struct(obj, DBC, p);
    p->env = env;
#else
    obj = Data_Make_Struct(self, DBC, mark_dbc, free_dbc, p);
    tracemsg(2, fprintf(stderr, "ObjAlloc: DBC %p\n", p););
    list_init(&p->link, offsetof(DBC, link));
    p->self = obj;
    p->env = env;
    p->envp = NULL;
    list_init(&p->stmts, offsetof(STMT, link));
    p->hdbc = SQL_NULL_HDBC;
    p->upc = 0;
    p->use_sql_column_name = Qfalse;
#endif
    if (env != Qnil) {
	ENV *e;

	Data_Get_Struct(env, ENV, e);
	link_dbc(p, e);
    }
    if (argc > 0) {
	rb_obj_call_init(obj, argc, argv);
    }
    return obj;
}

/*
 *----------------------------------------------------------------------
 *
 *	Connect to data source.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_connect(int argc, VALUE *argv, VALUE self)
{
    ENV *e;
    DBC *p;
    VALUE dsn, user, passwd;
#ifdef UNICODE
    SQLWCHAR *sdsn = NULL, *suser = NULL, *spasswd = NULL;
#else
    char *sdsn, *suser = NULL, *spasswd = NULL;
#endif
    char *msg;
    SQLHDBC dbc;

    rb_scan_args(argc, argv, "03", &dsn, &user, &passwd);
    if (dsn != Qnil) {
	if (rb_obj_is_kind_of(dsn, Cdsn) == Qtrue) {
	    dsn = rb_iv_get(dsn, "@name");
	}
	Check_Type(dsn, T_STRING);
    }
    if (user != Qnil) {
	Check_Type(user, T_STRING);
    }
    if (passwd != Qnil) {
	Check_Type(passwd, T_STRING);
    }
    p = get_dbc(self);
    if (p->hdbc != SQL_NULL_HDBC) {
	rb_raise(Cerror, "%s", set_err("Already connected", 0));
    }
    if (p->env == Qnil) {
	p->env = env_new(Cenv);
	e = get_env(p->env);
	link_dbc(p, e);
    } else {
	e = get_env(p->env);
    }
    if (dsn == Qnil) {
	return self;
    }
#ifdef UNICODE
    if (user != Qnil) {
#ifdef USE_RB_ENC
	user = rb_funcall(user, IDencode, 1, rb_encv);
#endif
	suser = uc_from_utf((unsigned char *) STR2CSTR(user), -1);
    }
    if (passwd != Qnil) {
#ifdef USE_RB_ENC
	passwd = rb_funcall(passwd, IDencode, 1, rb_encv);
#endif
	spasswd = uc_from_utf((unsigned char *) STR2CSTR(passwd), -1);
    }
#ifdef USE_RB_ENC
    dsn = rb_funcall(dsn, IDencode, 1, rb_encv);
#endif
    sdsn = uc_from_utf((unsigned char *) STR2CSTR(dsn), -1);
    if (((suser == NULL) && (user != Qnil)) ||
	((spasswd == NULL) && (passwd != Qnil)) ||
	(sdsn == NULL)) {
	uc_free(sdsn);
	uc_free(suser);
	uc_free(spasswd);
	rb_raise(Cerror, "%s", set_err("Out of memory", 0));
    }
#else
    if (user != Qnil) {
	suser = STR2CSTR(user);
    }
    if (passwd != Qnil) {
	spasswd = STR2CSTR(passwd);
    }
    sdsn = STR2CSTR(dsn);
#endif
    if (!succeeded(e->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		   SQLAllocConnect(e->henv, &dbc), &msg, "SQLAllocConnect")) {
#ifdef UNICODE
	uc_free(sdsn);
	uc_free(suser);
	uc_free(spasswd);
#endif
	rb_raise(Cerror, "%s", msg);
    }
    if (!succeeded(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT,
		   SQLCONNECT(dbc, (SQLTCHAR *) sdsn, SQL_NTS,
			      (SQLTCHAR *) suser,
			      (SQLSMALLINT) (suser ? SQL_NTS : 0),
			      (SQLTCHAR *) spasswd,
			      (SQLSMALLINT) (spasswd ? SQL_NTS : 0)),
		   &msg,
		   "SQLConnect('%s')", sdsn)) {
#ifdef UNICODE
	uc_free(sdsn);
	uc_free(suser);
	uc_free(spasswd);
#endif
	callsql(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT,
		SQLFreeConnect(dbc), "SQLFreeConnect");
	rb_raise(Cerror, "%s", msg);
    }
#ifdef UNICODE
    uc_free(sdsn);
    uc_free(suser);
    uc_free(spasswd);
#endif
    p->hdbc = dbc;
    return self;
}

static VALUE
dbc_drvconnect(VALUE self, VALUE drv)
{
    ENV *e;
    DBC *p;
#ifdef UNICODE
    SQLWCHAR *sdrv;
#else
    char *sdrv;
#endif
    char *msg;
    SQLHDBC dbc;

    if (rb_obj_is_kind_of(drv, Cdrv) == Qtrue) {
	VALUE d, a, x;

	d = rb_str_new2("");
	a = FUNCALL_NOARGS(rb_iv_get(drv, "@attrs"), IDkeys);
	while ((x = rb_ary_shift(a)) != Qnil) {
	    VALUE v = rb_hash_aref(rb_iv_get(drv, "@attrs"), x);

	    d = rb_str_concat(d, x);
	    d = rb_str_cat2(d, "=");
	    d = rb_str_concat(d, v);
	    d = rb_str_cat2(d, ";");
	}
	drv = d;
    }
    Check_Type(drv, T_STRING);
    p = get_dbc(self);
    if (p->hdbc != SQL_NULL_HDBC) {
	rb_raise(Cerror, "%s", set_err("Already connected", 0));
    }
    if (p->env == Qnil) {
	p->env = env_new(Cenv);
	e = get_env(p->env);
	link_dbc(p, e);
    } else {
	e = get_env(p->env);
    }
#ifdef UNICODE
#ifdef USE_RB_ENC
    drv = rb_funcall(drv, IDencode, 1, rb_encv);
#endif
    sdrv = uc_from_utf((unsigned char *) STR2CSTR(drv), -1);
    if (sdrv == NULL) {
	rb_raise(Cerror, "%s", set_err("Out of memory", 0));
    }
#else
    sdrv = STR2CSTR(drv);
#endif
    if (!succeeded(e->henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		   SQLAllocConnect(e->henv, &dbc), &msg, "SQLAllocConnect")) {
#ifdef UNICODE
	uc_free(sdrv);
#endif
	rb_raise(Cerror, "%s", msg);
    }
    if (!succeeded(e->henv, dbc, SQL_NULL_HSTMT,
		   SQLDRIVERCONNECT(dbc, NULL, (SQLTCHAR *) sdrv, SQL_NTS,
				    NULL, 0, NULL, SQL_DRIVER_NOPROMPT),
		   &msg, "SQLDriverConnect")) {
#ifdef UNICODE
	uc_free(sdrv);
#endif
	callsql(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT,
		SQLFreeConnect(dbc), "SQLFreeConnect");
	rb_raise(Cerror, "%s", msg);
    }
#ifdef UNICODE
    uc_free(sdrv);
#endif
    p->hdbc = dbc;
    return self;
}

static VALUE
dbc_connected(VALUE self)
{
    DBC *p = get_dbc(self);

    return (p->hdbc == SQL_NULL_HDBC) ? Qfalse : Qtrue;
}

static VALUE
dbc_timefmt(int argc, VALUE *argv, VALUE self)
{
    DBC *p = get_dbc(self);
    VALUE val;

    if (argc > 0) {
	rb_scan_args(argc, argv, "1", &val);
	p->rbtime = (val != Qnil && val != Qfalse) ? Qtrue : Qfalse;
    }
    return p->rbtime;
}

static VALUE
dbc_timeutc(int argc, VALUE *argv, VALUE self)
{
    DBC *p = get_dbc(self);
    VALUE val;

    if (argc > 0) {
	rb_scan_args(argc, argv, "1", &val);
	p->gmtime = (val != Qnil && val != Qfalse) ? Qtrue : Qfalse;
    }
    return p->gmtime;
}

static VALUE
dbc_use_scn(int argc, VALUE *argv, VALUE self)
{
    DBC *p = get_dbc(self);
    VALUE val;

    if (argc > 0) {
	rb_scan_args(argc, argv, "1", &val);
	p->use_sql_column_name =
	    (val != Qnil && val != Qfalse) ? Qtrue : Qfalse;
    }
    return p->use_sql_column_name;
}

/*
 *----------------------------------------------------------------------
 *
 *	Drop all active statements from data source.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_dropall(VALUE self)
{
    DBC *p = get_dbc(self);

    while (!list_empty(&p->stmts)) {
	STMT *q = list_first(&p->stmts);

	if (q->self == Qnil) {
	    rb_fatal("RubyODBC: invalid stmt in dropall");
	}
	stmt_drop(q->self);
    }
    return self;
}

/*
 *----------------------------------------------------------------------
 *
 *	Disconnect from data source.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_disconnect(int argc, VALUE *argv, VALUE self)
{
    DBC *p = get_dbc(self);
    VALUE nodrop = Qfalse;
    char *msg;

    rb_scan_args(argc, argv, "01", &nodrop);
    if (!RTEST(nodrop)) {
	dbc_dropall(self);
    }
    if (p->hdbc == SQL_NULL_HDBC) {
	return Qtrue;
    }
    if (list_empty(&p->stmts)) {
	callsql(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		SQLDISCONNECT(p->hdbc), "SQLDisconnect");
	if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		       SQLFreeConnect(p->hdbc), &msg, "SQLFreeConnect")) {
	    rb_raise(Cerror, "%s", msg);
	}
	p->hdbc = SQL_NULL_HDBC;
	unlink_dbc(p);
	if (gc_threshold >= 0) {
	   start_gc();
	}
	return Qtrue;
    }
    return Qfalse;
}

/*
 *----------------------------------------------------------------------
 *
 *	Database meta data via SQLGetInfo()
 *
 *----------------------------------------------------------------------
 */

#ifndef SQL_DTC_TRANSITION_COST
#define SQL_DTC_TRANSITION_COST 1750
#endif
#ifndef SQL_DTC_ENLIST_EXPENDSIZE
#define SQL_DTC_ENLIST_EXPENDSIZE 1
#endif
#ifndef SQL_DTC_UNENLIST_EXPENDSIZE
#define SQL_DTC_UNENLIST_EXPENDSIZE 2
#endif

#define GI_CONST_SINT(x) { #x, x, SQL_C_SHORT }
#define GI_CONST_INT(x)  { #x, x, SQL_C_LONG }
#define GI_CONST_BITS(x) { #x, x, SQL_C_LONG }
#define GI_CONST_STR(x)  { #x, x, SQL_C_CHAR }
#define GI_CONST_END     { NULL, -1, -1 }
static struct {
    const char *name;
    int info;
    int maptype;
} get_info_map[] = {

    /* yielding ints */
    GI_CONST_SINT(SQL_ACTIVE_ENVIRONMENTS),
    GI_CONST_SINT(SQL_ACTIVE_CONNECTIONS),
    GI_CONST_SINT(SQL_ACTIVE_STATEMENTS),
    GI_CONST_INT(SQL_ASYNC_MODE),
    GI_CONST_SINT(SQL_CATALOG_LOCATION),
    GI_CONST_SINT(SQL_CONCAT_NULL_BEHAVIOR),
    GI_CONST_SINT(SQL_CORRELATION_NAME),
    GI_CONST_SINT(SQL_CURSOR_COMMIT_BEHAVIOR),
    GI_CONST_SINT(SQL_CURSOR_ROLLBACK_BEHAVIOR),
    GI_CONST_INT(SQL_CURSOR_SENSITIVITY),
    GI_CONST_INT(SQL_DDL_INDEX),
    GI_CONST_INT(SQL_DEFAULT_TXN_ISOLATION),
    GI_CONST_INT(SQL_DRIVER_HDBC),
    GI_CONST_INT(SQL_DRIVER_HENV),
    GI_CONST_INT(SQL_DRIVER_HDESC),
    GI_CONST_INT(SQL_DRIVER_HLIB),
    GI_CONST_INT(SQL_DRIVER_HSTMT),
    GI_CONST_SINT(SQL_FILE_USAGE),
    GI_CONST_SINT(SQL_GROUP_BY),
    GI_CONST_SINT(SQL_IDENTIFIER_CASE),
    GI_CONST_INT(SQL_MAX_ASYNC_CONCURRENT_STATEMENTS),
    GI_CONST_INT(SQL_MAX_BINARY_LITERAL_LEN),
    GI_CONST_SINT(SQL_MAX_CATALOG_NAME_LEN),
    GI_CONST_INT(SQL_MAX_CHAR_LITERAL_LEN),
    GI_CONST_SINT(SQL_MAX_COLUMN_NAME_LEN),
    GI_CONST_SINT(SQL_MAX_COLUMNS_IN_GROUP_BY),
    GI_CONST_SINT(SQL_MAX_COLUMNS_IN_INDEX),
    GI_CONST_SINT(SQL_MAX_COLUMNS_IN_ORDER_BY),
    GI_CONST_SINT(SQL_MAX_COLUMNS_IN_SELECT),
    GI_CONST_SINT(SQL_MAX_COLUMNS_IN_TABLE),
    GI_CONST_SINT(SQL_MAX_CONCURRENT_ACTIVITIES),
    GI_CONST_SINT(SQL_MAX_CURSOR_NAME_LEN),
    GI_CONST_SINT(SQL_MAX_DRIVER_CONNECTIONS),
    GI_CONST_SINT(SQL_MAX_IDENTIFIER_LEN),
    GI_CONST_INT(SQL_MAX_INDEX_SIZE),
    GI_CONST_SINT(SQL_MAX_OWNER_NAME_LEN),
    GI_CONST_SINT(SQL_MAX_PROCEDURE_NAME_LEN),
    GI_CONST_SINT(SQL_MAX_QUALIFIER_NAME_LEN),
    GI_CONST_INT(SQL_MAX_ROW_SIZE),
    GI_CONST_SINT(SQL_MAX_SCHEMA_NAME_LEN),
    GI_CONST_INT(SQL_MAX_STATEMENT_LEN),
    GI_CONST_SINT(SQL_MAX_TABLE_NAME_LEN),
    GI_CONST_SINT(SQL_MAX_TABLES_IN_SELECT),
    GI_CONST_SINT(SQL_MAX_USER_NAME_LEN),
    GI_CONST_SINT(SQL_NON_NULLABLE_COLUMNS),
    GI_CONST_SINT(SQL_NULL_COLLATION),
    GI_CONST_SINT(SQL_ODBC_API_CONFORMANCE),
    GI_CONST_INT(SQL_ODBC_INTERFACE_CONFORMANCE),
    GI_CONST_SINT(SQL_ODBC_SAG_CLI_CONFORMANCE),
    GI_CONST_SINT(SQL_ODBC_SQL_CONFORMANCE),
    GI_CONST_INT(SQL_PARAM_ARRAY_ROW_COUNTS),
    GI_CONST_INT(SQL_PARAM_ARRAY_SELECTS),
    GI_CONST_SINT(SQL_QUALIFIER_LOCATION),
    GI_CONST_SINT(SQL_QUOTED_IDENTIFIER_CASE),
    GI_CONST_INT(SQL_SQL_CONFORMANCE),
    GI_CONST_SINT(SQL_TXN_CAPABLE),

    /* yielding ints (but bitmasks) */
    GI_CONST_BITS(SQL_AGGREGATE_FUNCTIONS),
    GI_CONST_BITS(SQL_ALTER_DOMAIN),
    GI_CONST_BITS(SQL_ALTER_TABLE),
    GI_CONST_BITS(SQL_BATCH_ROW_COUNT),
    GI_CONST_BITS(SQL_BATCH_SUPPORT),
    GI_CONST_BITS(SQL_BOOKMARK_PERSISTENCE),
    GI_CONST_BITS(SQL_CATALOG_USAGE),
    GI_CONST_BITS(SQL_CONVERT_BINARY),
    GI_CONST_BITS(SQL_CONVERT_BIT),
    GI_CONST_BITS(SQL_CONVERT_CHAR),
#ifdef SQL_CONVERT_GUID
    GI_CONST_BITS(SQL_CONVERT_GUID),
#endif
    GI_CONST_BITS(SQL_CONVERT_DATE),
    GI_CONST_BITS(SQL_CONVERT_DECIMAL),
    GI_CONST_BITS(SQL_CONVERT_DOUBLE),
    GI_CONST_BITS(SQL_CONVERT_FLOAT),
    GI_CONST_BITS(SQL_CONVERT_FUNCTIONS),
    GI_CONST_BITS(SQL_CONVERT_INTEGER),
    GI_CONST_BITS(SQL_CONVERT_INTERVAL_YEAR_MONTH),
    GI_CONST_BITS(SQL_CONVERT_INTERVAL_DAY_TIME),
    GI_CONST_BITS(SQL_CONVERT_LONGVARBINARY),
    GI_CONST_BITS(SQL_CONVERT_LONGVARCHAR),
    GI_CONST_BITS(SQL_CONVERT_NUMERIC),
    GI_CONST_BITS(SQL_CONVERT_REAL),
    GI_CONST_BITS(SQL_CONVERT_SMALLINT),
    GI_CONST_BITS(SQL_CONVERT_TIME),
    GI_CONST_BITS(SQL_CONVERT_TIMESTAMP),
    GI_CONST_BITS(SQL_CONVERT_TINYINT),
    GI_CONST_BITS(SQL_CONVERT_VARBINARY),
    GI_CONST_BITS(SQL_CONVERT_VARCHAR),
    GI_CONST_BITS(SQL_CONVERT_WCHAR),
    GI_CONST_BITS(SQL_CONVERT_WLONGVARCHAR),
    GI_CONST_BITS(SQL_CONVERT_WVARCHAR),
    GI_CONST_BITS(SQL_CREATE_ASSERTION),
    GI_CONST_BITS(SQL_CREATE_CHARACTER_SET),
    GI_CONST_BITS(SQL_CREATE_COLLATION),
    GI_CONST_BITS(SQL_CREATE_DOMAIN),
    GI_CONST_BITS(SQL_CREATE_SCHEMA),
    GI_CONST_BITS(SQL_CREATE_TABLE),
    GI_CONST_BITS(SQL_CREATE_TRANSLATION),
    GI_CONST_BITS(SQL_CREATE_VIEW),
    GI_CONST_BITS(SQL_DATETIME_LITERALS),
    GI_CONST_BITS(SQL_DROP_ASSERTION),
    GI_CONST_BITS(SQL_DROP_CHARACTER_SET),
    GI_CONST_BITS(SQL_DROP_COLLATION),
    GI_CONST_BITS(SQL_DROP_DOMAIN),
    GI_CONST_BITS(SQL_DROP_SCHEMA),
    GI_CONST_BITS(SQL_DROP_TABLE),
    GI_CONST_BITS(SQL_DROP_TRANSLATION),
    GI_CONST_BITS(SQL_DROP_VIEW),
    GI_CONST_BITS(SQL_DTC_TRANSITION_COST),
    GI_CONST_BITS(SQL_DYNAMIC_CURSOR_ATTRIBUTES1),
    GI_CONST_BITS(SQL_DYNAMIC_CURSOR_ATTRIBUTES2),
    GI_CONST_BITS(SQL_FETCH_DIRECTION),
    GI_CONST_BITS(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1),
    GI_CONST_BITS(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2),
    GI_CONST_BITS(SQL_GETDATA_EXTENSIONS),
    GI_CONST_BITS(SQL_KEYSET_CURSOR_ATTRIBUTES1),
    GI_CONST_BITS(SQL_KEYSET_CURSOR_ATTRIBUTES2),
    GI_CONST_BITS(SQL_INDEX_KEYWORDS),
    GI_CONST_BITS(SQL_INFO_SCHEMA_VIEWS),
    GI_CONST_BITS(SQL_INSERT_STATEMENT),
    GI_CONST_BITS(SQL_LOCK_TYPES),
    GI_CONST_BITS(SQL_NUMERIC_FUNCTIONS),
    GI_CONST_BITS(SQL_OJ_CAPABILITIES),
    GI_CONST_BITS(SQL_OWNER_USAGE),
    GI_CONST_BITS(SQL_POS_OPERATIONS),
    GI_CONST_BITS(SQL_POSITIONED_STATEMENTS),
    GI_CONST_BITS(SQL_QUALIFIER_USAGE),
    GI_CONST_BITS(SQL_SCHEMA_USAGE),
    GI_CONST_BITS(SQL_SCROLL_CONCURRENCY),
    GI_CONST_BITS(SQL_SCROLL_OPTIONS),
    GI_CONST_BITS(SQL_SQL92_DATETIME_FUNCTIONS),
    GI_CONST_BITS(SQL_SQL92_FOREIGN_KEY_DELETE_RULE),
    GI_CONST_BITS(SQL_SQL92_FOREIGN_KEY_UPDATE_RULE),
    GI_CONST_BITS(SQL_SQL92_GRANT),
    GI_CONST_BITS(SQL_SQL92_NUMERIC_VALUE_FUNCTIONS),
    GI_CONST_BITS(SQL_SQL92_PREDICATES),
    GI_CONST_BITS(SQL_SQL92_RELATIONAL_JOIN_OPERATORS),
    GI_CONST_BITS(SQL_SQL92_REVOKE),
    GI_CONST_BITS(SQL_SQL92_ROW_VALUE_CONSTRUCTOR),
    GI_CONST_BITS(SQL_SQL92_STRING_FUNCTIONS),
    GI_CONST_BITS(SQL_SQL92_VALUE_EXPRESSIONS),
    GI_CONST_BITS(SQL_STANDARD_CLI_CONFORMANCE),
    GI_CONST_BITS(SQL_STATIC_CURSOR_ATTRIBUTES1),
    GI_CONST_BITS(SQL_STATIC_CURSOR_ATTRIBUTES2),
    GI_CONST_BITS(SQL_STATIC_SENSITIVITY),
    GI_CONST_BITS(SQL_STRING_FUNCTIONS),
    GI_CONST_BITS(SQL_SUBQUERIES),
    GI_CONST_BITS(SQL_SYSTEM_FUNCTIONS),
    GI_CONST_BITS(SQL_TIMEDATE_ADD_INTERVALS),
    GI_CONST_BITS(SQL_TIMEDATE_DIFF_INTERVALS),
    GI_CONST_BITS(SQL_TIMEDATE_FUNCTIONS),
    GI_CONST_BITS(SQL_TXN_ISOLATION_OPTION),
    GI_CONST_BITS(SQL_UNION),

    /* yielding strings */
    GI_CONST_STR(SQL_ACCESSIBLE_PROCEDURES),
    GI_CONST_STR(SQL_ACCESSIBLE_TABLES),
    GI_CONST_STR(SQL_CATALOG_NAME),
    GI_CONST_STR(SQL_CATALOG_NAME_SEPARATOR),
    GI_CONST_STR(SQL_CATALOG_TERM),
    GI_CONST_STR(SQL_COLLATION_SEQ),
    GI_CONST_STR(SQL_COLUMN_ALIAS),
    GI_CONST_STR(SQL_DATA_SOURCE_NAME),
    GI_CONST_STR(SQL_DATA_SOURCE_READ_ONLY),
    GI_CONST_STR(SQL_DATABASE_NAME),
    GI_CONST_STR(SQL_DBMS_NAME),
    GI_CONST_STR(SQL_DBMS_VER),
    GI_CONST_STR(SQL_DESCRIBE_PARAMETER),
    GI_CONST_STR(SQL_DM_VER),
    GI_CONST_STR(SQL_DRIVER_NAME),
    GI_CONST_STR(SQL_DRIVER_ODBC_VER),
    GI_CONST_STR(SQL_DRIVER_VER),
    GI_CONST_STR(SQL_EXPRESSIONS_IN_ORDERBY),
    GI_CONST_STR(SQL_IDENTIFIER_QUOTE_CHAR),
    GI_CONST_STR(SQL_INTEGRITY),
    GI_CONST_STR(SQL_KEYWORDS),
    GI_CONST_STR(SQL_LIKE_ESCAPE_CLAUSE),
    GI_CONST_STR(SQL_MAX_ROW_SIZE_INCLUDES_LONG),
    GI_CONST_STR(SQL_MULT_RESULT_SETS),
    GI_CONST_STR(SQL_MULTIPLE_ACTIVE_TXN),
    GI_CONST_STR(SQL_NEED_LONG_DATA_LEN),
    GI_CONST_STR(SQL_ODBC_SQL_OPT_IEF),
    GI_CONST_STR(SQL_ODBC_VER),
    GI_CONST_STR(SQL_ORDER_BY_COLUMNS_IN_SELECT),
    GI_CONST_STR(SQL_OUTER_JOINS),
    GI_CONST_STR(SQL_OWNER_TERM),
    GI_CONST_STR(SQL_PROCEDURE_TERM),
    GI_CONST_STR(SQL_PROCEDURES),
    GI_CONST_STR(SQL_QUALIFIER_NAME_SEPARATOR),
    GI_CONST_STR(SQL_QUALIFIER_TERM),
    GI_CONST_STR(SQL_ROW_UPDATES),
    GI_CONST_STR(SQL_SCHEMA_TERM),
    GI_CONST_STR(SQL_SEARCH_PATTERN_ESCAPE),
    GI_CONST_STR(SQL_SERVER_NAME),
    GI_CONST_STR(SQL_SPECIAL_CHARACTERS),
    GI_CONST_STR(SQL_TABLE_TERM),
    GI_CONST_STR(SQL_USER_NAME),
    GI_CONST_STR(SQL_XOPEN_CLI_YEAR),

    /* end of table */
    GI_CONST_END
};

#define GI_CONST_BITMAP(x)  { #x, x }
#define GI_CONST_BITMAP_END { NULL, 0 }
static struct {
    const char *name;
    int bits;
} get_info_bitmap[] = {
    GI_CONST_BITMAP(SQL_AD_ADD_CONSTRAINT_DEFERRABLE),
    GI_CONST_BITMAP(SQL_AD_ADD_CONSTRAINT_INITIALLY_DEFERRED),
    GI_CONST_BITMAP(SQL_AD_ADD_CONSTRAINT_INITIALLY_IMMEDIATE),
    GI_CONST_BITMAP(SQL_AD_ADD_CONSTRAINT_NON_DEFERRABLE),
    GI_CONST_BITMAP(SQL_AD_ADD_DOMAIN_CONSTRAINT),
    GI_CONST_BITMAP(SQL_AD_ADD_DOMAIN_DEFAULT),
    GI_CONST_BITMAP(SQL_AD_CONSTRAINT_NAME_DEFINITION),
    GI_CONST_BITMAP(SQL_AD_DROP_DOMAIN_CONSTRAINT),
    GI_CONST_BITMAP(SQL_AD_DROP_DOMAIN_DEFAULT),
    GI_CONST_BITMAP(SQL_AF_ALL),
    GI_CONST_BITMAP(SQL_AF_AVG),
    GI_CONST_BITMAP(SQL_AF_COUNT),
    GI_CONST_BITMAP(SQL_AF_DISTINCT),
    GI_CONST_BITMAP(SQL_AF_MAX),
    GI_CONST_BITMAP(SQL_AF_MIN),
    GI_CONST_BITMAP(SQL_AF_SUM),
    GI_CONST_BITMAP(SQL_AM_CONNECTION),
    GI_CONST_BITMAP(SQL_AM_NONE),
    GI_CONST_BITMAP(SQL_AM_STATEMENT),
    GI_CONST_BITMAP(SQL_AT_ADD_COLUMN),
    GI_CONST_BITMAP(SQL_AT_ADD_COLUMN_COLLATION),
    GI_CONST_BITMAP(SQL_AT_ADD_COLUMN_DEFAULT),
    GI_CONST_BITMAP(SQL_AT_ADD_COLUMN_SINGLE),
    GI_CONST_BITMAP(SQL_AT_ADD_CONSTRAINT),
    GI_CONST_BITMAP(SQL_AT_ADD_TABLE_CONSTRAINT),
#ifdef SQL_AT_COLUMN_SINGLE
    GI_CONST_BITMAP(SQL_AT_COLUMN_SINGLE),
#endif
    GI_CONST_BITMAP(SQL_AT_CONSTRAINT_DEFERRABLE),
    GI_CONST_BITMAP(SQL_AT_CONSTRAINT_INITIALLY_DEFERRED),
    GI_CONST_BITMAP(SQL_AT_CONSTRAINT_INITIALLY_IMMEDIATE),
    GI_CONST_BITMAP(SQL_AT_CONSTRAINT_NAME_DEFINITION),
    GI_CONST_BITMAP(SQL_AT_CONSTRAINT_NON_DEFERRABLE),
    GI_CONST_BITMAP(SQL_AT_DROP_COLUMN),
    GI_CONST_BITMAP(SQL_AT_DROP_COLUMN_CASCADE),
    GI_CONST_BITMAP(SQL_AT_DROP_COLUMN_DEFAULT),
    GI_CONST_BITMAP(SQL_AT_DROP_COLUMN_RESTRICT),
    GI_CONST_BITMAP(SQL_AT_DROP_TABLE_CONSTRAINT_CASCADE),
    GI_CONST_BITMAP(SQL_AT_DROP_TABLE_CONSTRAINT_RESTRICT),
    GI_CONST_BITMAP(SQL_AT_SET_COLUMN_DEFAULT),
    GI_CONST_BITMAP(SQL_BP_CLOSE),
    GI_CONST_BITMAP(SQL_BP_DELETE),
    GI_CONST_BITMAP(SQL_BP_DROP),
    GI_CONST_BITMAP(SQL_BP_OTHER_HSTMT),
    GI_CONST_BITMAP(SQL_BP_SCROLL),
    GI_CONST_BITMAP(SQL_BP_TRANSACTION),
    GI_CONST_BITMAP(SQL_BP_UPDATE),
    GI_CONST_BITMAP(SQL_BRC_EXPLICIT),
    GI_CONST_BITMAP(SQL_BRC_PROCEDURES),
    GI_CONST_BITMAP(SQL_BRC_ROLLED_UP),
    GI_CONST_BITMAP(SQL_BS_ROW_COUNT_EXPLICIT),
    GI_CONST_BITMAP(SQL_BS_ROW_COUNT_PROC),
    GI_CONST_BITMAP(SQL_BS_SELECT_EXPLICIT),
    GI_CONST_BITMAP(SQL_BS_SELECT_PROC),
    GI_CONST_BITMAP(SQL_CA1_ABSOLUTE),
    GI_CONST_BITMAP(SQL_CA1_BOOKMARK),
    GI_CONST_BITMAP(SQL_CA1_BULK_ADD),
    GI_CONST_BITMAP(SQL_CA1_BULK_DELETE_BY_BOOKMARK),
    GI_CONST_BITMAP(SQL_CA1_BULK_FETCH_BY_BOOKMARK),
    GI_CONST_BITMAP(SQL_CA1_BULK_UPDATE_BY_BOOKMARK),
    GI_CONST_BITMAP(SQL_CA1_LOCK_EXCLUSIVE),
    GI_CONST_BITMAP(SQL_CA1_LOCK_NO_CHANGE),
    GI_CONST_BITMAP(SQL_CA1_LOCK_UNLOCK),
    GI_CONST_BITMAP(SQL_CA1_NEXT),
    GI_CONST_BITMAP(SQL_CA1_POS_DELETE),
    GI_CONST_BITMAP(SQL_CA1_POSITIONED_DELETE),
    GI_CONST_BITMAP(SQL_CA1_POSITIONED_UPDATE),
    GI_CONST_BITMAP(SQL_CA1_POS_POSITION),
    GI_CONST_BITMAP(SQL_CA1_POS_REFRESH),
    GI_CONST_BITMAP(SQL_CA1_POS_UPDATE),
    GI_CONST_BITMAP(SQL_CA1_RELATIVE),
    GI_CONST_BITMAP(SQL_CA1_SELECT_FOR_UPDATE),
    GI_CONST_BITMAP(SQL_CA2_CRC_APPROXIMATE),
    GI_CONST_BITMAP(SQL_CA2_CRC_EXACT),
    GI_CONST_BITMAP(SQL_CA2_LOCK_CONCURRENCY),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_AFFECTS_ALL),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_CATALOG),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_DELETE),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_INSERT),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_SELECT),
    GI_CONST_BITMAP(SQL_CA2_MAX_ROWS_UPDATE),
    GI_CONST_BITMAP(SQL_CA2_OPT_ROWVER_CONCURRENCY),
    GI_CONST_BITMAP(SQL_CA2_OPT_VALUES_CONCURRENCY),
    GI_CONST_BITMAP(SQL_CA2_READ_ONLY_CONCURRENCY),
    GI_CONST_BITMAP(SQL_CA2_SENSITIVITY_ADDITIONS),
    GI_CONST_BITMAP(SQL_CA2_SENSITIVITY_DELETIONS),
    GI_CONST_BITMAP(SQL_CA2_SENSITIVITY_UPDATES),
    GI_CONST_BITMAP(SQL_CA2_SIMULATE_NON_UNIQUE),
    GI_CONST_BITMAP(SQL_CA2_SIMULATE_TRY_UNIQUE),
    GI_CONST_BITMAP(SQL_CA2_SIMULATE_UNIQUE),
    GI_CONST_BITMAP(SQL_CA_CONSTRAINT_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CA_CONSTRAINT_INITIALLY_DEFERRED),
    GI_CONST_BITMAP(SQL_CA_CONSTRAINT_INITIALLY_IMMEDIATE),
    GI_CONST_BITMAP(SQL_CA_CONSTRAINT_NON_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CA_CREATE_ASSERTION),
    GI_CONST_BITMAP(SQL_CB_CLOSE),
    GI_CONST_BITMAP(SQL_CB_DELETE),
    GI_CONST_BITMAP(SQL_CB_NON_NULL),
    GI_CONST_BITMAP(SQL_CB_NULL),
    GI_CONST_BITMAP(SQL_CB_PRESERVE),
    GI_CONST_BITMAP(SQL_CC_CLOSE),
    GI_CONST_BITMAP(SQL_CC_DELETE),
    GI_CONST_BITMAP(SQL_CCOL_CREATE_COLLATION),
    GI_CONST_BITMAP(SQL_CC_PRESERVE),
    GI_CONST_BITMAP(SQL_CCS_COLLATE_CLAUSE),
    GI_CONST_BITMAP(SQL_CCS_CREATE_CHARACTER_SET),
    GI_CONST_BITMAP(SQL_CCS_LIMITED_COLLATION),
    GI_CONST_BITMAP(SQL_CDO_COLLATION),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT_INITIALLY_DEFERRED),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT_INITIALLY_IMMEDIATE),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT_NAME_DEFINITION),
    GI_CONST_BITMAP(SQL_CDO_CONSTRAINT_NON_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CDO_CREATE_DOMAIN),
    GI_CONST_BITMAP(SQL_CDO_DEFAULT),
    GI_CONST_BITMAP(SQL_CL_END),
    GI_CONST_BITMAP(SQL_CL_START),
    GI_CONST_BITMAP(SQL_CN_ANY),
    GI_CONST_BITMAP(SQL_CN_DIFFERENT),
    GI_CONST_BITMAP(SQL_CN_NONE),
    GI_CONST_BITMAP(SQL_CONCUR_TIMESTAMP),
    GI_CONST_BITMAP(SQL_CR_CLOSE),
    GI_CONST_BITMAP(SQL_CR_DELETE),
    GI_CONST_BITMAP(SQL_CR_PRESERVE),
    GI_CONST_BITMAP(SQL_CS_AUTHORIZATION),
    GI_CONST_BITMAP(SQL_CS_CREATE_SCHEMA),
    GI_CONST_BITMAP(SQL_CS_DEFAULT_CHARACTER_SET),
    GI_CONST_BITMAP(SQL_CT_COLUMN_COLLATION),
    GI_CONST_BITMAP(SQL_CT_COLUMN_CONSTRAINT),
    GI_CONST_BITMAP(SQL_CT_COLUMN_DEFAULT),
    GI_CONST_BITMAP(SQL_CT_COMMIT_DELETE),
    GI_CONST_BITMAP(SQL_CT_COMMIT_PRESERVE),
    GI_CONST_BITMAP(SQL_CT_CONSTRAINT_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CT_CONSTRAINT_INITIALLY_DEFERRED),
    GI_CONST_BITMAP(SQL_CT_CONSTRAINT_INITIALLY_IMMEDIATE),
    GI_CONST_BITMAP(SQL_CT_CONSTRAINT_NAME_DEFINITION),
    GI_CONST_BITMAP(SQL_CT_CONSTRAINT_NON_DEFERRABLE),
    GI_CONST_BITMAP(SQL_CT_CREATE_TABLE),
    GI_CONST_BITMAP(SQL_CT_GLOBAL_TEMPORARY),
    GI_CONST_BITMAP(SQL_CT_LOCAL_TEMPORARY),
    GI_CONST_BITMAP(SQL_CTR_CREATE_TRANSLATION),
    GI_CONST_BITMAP(SQL_CT_TABLE_CONSTRAINT),
    GI_CONST_BITMAP(SQL_CU_DML_STATEMENTS),
    GI_CONST_BITMAP(SQL_CU_INDEX_DEFINITION),
    GI_CONST_BITMAP(SQL_CU_PRIVILEGE_DEFINITION),
    GI_CONST_BITMAP(SQL_CU_PROCEDURE_INVOCATION),
    GI_CONST_BITMAP(SQL_CU_TABLE_DEFINITION),
    GI_CONST_BITMAP(SQL_CV_CASCADED),
    GI_CONST_BITMAP(SQL_CV_CHECK_OPTION),
    GI_CONST_BITMAP(SQL_CV_CREATE_VIEW),
    GI_CONST_BITMAP(SQL_CV_LOCAL),
    GI_CONST_BITMAP(SQL_CVT_BIGINT),
    GI_CONST_BITMAP(SQL_CVT_BINARY),
    GI_CONST_BITMAP(SQL_CVT_BIT),
    GI_CONST_BITMAP(SQL_CVT_CHAR),
    GI_CONST_BITMAP(SQL_CVT_DATE),
    GI_CONST_BITMAP(SQL_CVT_DECIMAL),
    GI_CONST_BITMAP(SQL_CVT_DOUBLE),
    GI_CONST_BITMAP(SQL_CVT_FLOAT),
    GI_CONST_BITMAP(SQL_CVT_INTEGER),
    GI_CONST_BITMAP(SQL_CVT_INTERVAL_DAY_TIME),
    GI_CONST_BITMAP(SQL_CVT_INTERVAL_YEAR_MONTH),
    GI_CONST_BITMAP(SQL_CVT_LONGVARBINARY),
    GI_CONST_BITMAP(SQL_CVT_LONGVARCHAR),
    GI_CONST_BITMAP(SQL_CVT_NUMERIC),
    GI_CONST_BITMAP(SQL_CVT_REAL),
    GI_CONST_BITMAP(SQL_CVT_SMALLINT),
    GI_CONST_BITMAP(SQL_CVT_TIME),
    GI_CONST_BITMAP(SQL_CVT_TIMESTAMP),
    GI_CONST_BITMAP(SQL_CVT_TINYINT),
    GI_CONST_BITMAP(SQL_CVT_VARBINARY),
    GI_CONST_BITMAP(SQL_CVT_VARCHAR),
    GI_CONST_BITMAP(SQL_CVT_WCHAR),
    GI_CONST_BITMAP(SQL_CVT_WLONGVARCHAR),
    GI_CONST_BITMAP(SQL_CVT_WVARCHAR),
    GI_CONST_BITMAP(SQL_DA_DROP_ASSERTION),
    GI_CONST_BITMAP(SQL_DC_DROP_COLLATION),
    GI_CONST_BITMAP(SQL_DCS_DROP_CHARACTER_SET),
    GI_CONST_BITMAP(SQL_DD_CASCADE),
    GI_CONST_BITMAP(SQL_DD_DROP_DOMAIN),
    GI_CONST_BITMAP(SQL_DD_RESTRICT),
    GI_CONST_BITMAP(SQL_DI_CREATE_INDEX),
    GI_CONST_BITMAP(SQL_DI_DROP_INDEX),
    GI_CONST_BITMAP(SQL_DL_SQL92_DATE),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_DAY),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_DAY_TO_HOUR),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_DAY_TO_MINUTE),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_DAY_TO_SECOND),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_HOUR),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_HOUR_TO_MINUTE),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_HOUR_TO_SECOND),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_MINUTE),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_MINUTE_TO_SECOND),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_MONTH),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_SECOND),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_YEAR),
    GI_CONST_BITMAP(SQL_DL_SQL92_INTERVAL_YEAR_TO_MONTH),
    GI_CONST_BITMAP(SQL_DL_SQL92_TIME),
    GI_CONST_BITMAP(SQL_DL_SQL92_TIMESTAMP),
    GI_CONST_BITMAP(SQL_DS_CASCADE),
    GI_CONST_BITMAP(SQL_DS_DROP_SCHEMA),
    GI_CONST_BITMAP(SQL_DS_RESTRICT),
    GI_CONST_BITMAP(SQL_DT_CASCADE),
    GI_CONST_BITMAP(SQL_DTC_ENLIST_EXPENSIVE),
    GI_CONST_BITMAP(SQL_DTC_UNENLIST_EXPENSIVE),
    GI_CONST_BITMAP(SQL_DT_DROP_TABLE),
    GI_CONST_BITMAP(SQL_DTR_DROP_TRANSLATION),
    GI_CONST_BITMAP(SQL_DT_RESTRICT),
    GI_CONST_BITMAP(SQL_DV_CASCADE),
    GI_CONST_BITMAP(SQL_DV_DROP_VIEW),
    GI_CONST_BITMAP(SQL_DV_RESTRICT),
    GI_CONST_BITMAP(SQL_FD_FETCH_ABSOLUTE),
    GI_CONST_BITMAP(SQL_FD_FETCH_BOOKMARK),
    GI_CONST_BITMAP(SQL_FD_FETCH_FIRST),
    GI_CONST_BITMAP(SQL_FD_FETCH_LAST),
    GI_CONST_BITMAP(SQL_FD_FETCH_NEXT),
    GI_CONST_BITMAP(SQL_FD_FETCH_PRIOR),
    GI_CONST_BITMAP(SQL_FD_FETCH_RELATIVE),
#ifdef SQL_FD_FETCH_RESUME
    GI_CONST_BITMAP(SQL_FD_FETCH_RESUME),
#endif
    GI_CONST_BITMAP(SQL_FILE_CATALOG),
    GI_CONST_BITMAP(SQL_FILE_NOT_SUPPORTED),
    GI_CONST_BITMAP(SQL_FILE_QUALIFIER),
    GI_CONST_BITMAP(SQL_FILE_TABLE),
    GI_CONST_BITMAP(SQL_FN_CVT_CAST),
    GI_CONST_BITMAP(SQL_FN_CVT_CONVERT),
    GI_CONST_BITMAP(SQL_FN_NUM_ABS),
    GI_CONST_BITMAP(SQL_FN_NUM_ACOS),
    GI_CONST_BITMAP(SQL_FN_NUM_ASIN),
    GI_CONST_BITMAP(SQL_FN_NUM_ATAN),
    GI_CONST_BITMAP(SQL_FN_NUM_ATAN2),
    GI_CONST_BITMAP(SQL_FN_NUM_CEILING),
    GI_CONST_BITMAP(SQL_FN_NUM_COS),
    GI_CONST_BITMAP(SQL_FN_NUM_COT),
    GI_CONST_BITMAP(SQL_FN_NUM_DEGREES),
    GI_CONST_BITMAP(SQL_FN_NUM_EXP),
    GI_CONST_BITMAP(SQL_FN_NUM_FLOOR),
    GI_CONST_BITMAP(SQL_FN_NUM_LOG),
    GI_CONST_BITMAP(SQL_FN_NUM_LOG10),
    GI_CONST_BITMAP(SQL_FN_NUM_MOD),
    GI_CONST_BITMAP(SQL_FN_NUM_PI),
    GI_CONST_BITMAP(SQL_FN_NUM_POWER),
    GI_CONST_BITMAP(SQL_FN_NUM_RADIANS),
    GI_CONST_BITMAP(SQL_FN_NUM_RAND),
    GI_CONST_BITMAP(SQL_FN_NUM_ROUND),
    GI_CONST_BITMAP(SQL_FN_NUM_SIGN),
    GI_CONST_BITMAP(SQL_FN_NUM_SIN),
    GI_CONST_BITMAP(SQL_FN_NUM_SQRT),
    GI_CONST_BITMAP(SQL_FN_NUM_TAN),
    GI_CONST_BITMAP(SQL_FN_NUM_TRUNCATE),
    GI_CONST_BITMAP(SQL_FN_STR_ASCII),
    GI_CONST_BITMAP(SQL_FN_STR_BIT_LENGTH),
    GI_CONST_BITMAP(SQL_FN_STR_CHAR),
    GI_CONST_BITMAP(SQL_FN_STR_CHARACTER_LENGTH),
    GI_CONST_BITMAP(SQL_FN_STR_CHAR_LENGTH),
    GI_CONST_BITMAP(SQL_FN_STR_CONCAT),
    GI_CONST_BITMAP(SQL_FN_STR_DIFFERENCE),
    GI_CONST_BITMAP(SQL_FN_STR_INSERT),
    GI_CONST_BITMAP(SQL_FN_STR_LCASE),
    GI_CONST_BITMAP(SQL_FN_STR_LEFT),
    GI_CONST_BITMAP(SQL_FN_STR_LENGTH),
    GI_CONST_BITMAP(SQL_FN_STR_LOCATE),
    GI_CONST_BITMAP(SQL_FN_STR_LOCATE_2),
    GI_CONST_BITMAP(SQL_FN_STR_LTRIM),
    GI_CONST_BITMAP(SQL_FN_STR_OCTET_LENGTH),
    GI_CONST_BITMAP(SQL_FN_STR_POSITION),
    GI_CONST_BITMAP(SQL_FN_STR_REPEAT),
    GI_CONST_BITMAP(SQL_FN_STR_REPLACE),
    GI_CONST_BITMAP(SQL_FN_STR_RIGHT),
    GI_CONST_BITMAP(SQL_FN_STR_RTRIM),
    GI_CONST_BITMAP(SQL_FN_STR_SOUNDEX),
    GI_CONST_BITMAP(SQL_FN_STR_SPACE),
    GI_CONST_BITMAP(SQL_FN_STR_SUBSTRING),
    GI_CONST_BITMAP(SQL_FN_STR_UCASE),
    GI_CONST_BITMAP(SQL_FN_SYS_DBNAME),
    GI_CONST_BITMAP(SQL_FN_SYS_IFNULL),
    GI_CONST_BITMAP(SQL_FN_SYS_USERNAME),
    GI_CONST_BITMAP(SQL_FN_TD_CURDATE),
    GI_CONST_BITMAP(SQL_FN_TD_CURRENT_DATE),
    GI_CONST_BITMAP(SQL_FN_TD_CURRENT_TIME),
    GI_CONST_BITMAP(SQL_FN_TD_CURRENT_TIMESTAMP),
    GI_CONST_BITMAP(SQL_FN_TD_CURTIME),
    GI_CONST_BITMAP(SQL_FN_TD_DAYNAME),
    GI_CONST_BITMAP(SQL_FN_TD_DAYOFMONTH),
    GI_CONST_BITMAP(SQL_FN_TD_DAYOFWEEK),
    GI_CONST_BITMAP(SQL_FN_TD_DAYOFYEAR),
    GI_CONST_BITMAP(SQL_FN_TD_EXTRACT),
    GI_CONST_BITMAP(SQL_FN_TD_HOUR),
    GI_CONST_BITMAP(SQL_FN_TD_MINUTE),
    GI_CONST_BITMAP(SQL_FN_TD_MONTH),
    GI_CONST_BITMAP(SQL_FN_TD_MONTHNAME),
    GI_CONST_BITMAP(SQL_FN_TD_NOW),
    GI_CONST_BITMAP(SQL_FN_TD_QUARTER),
    GI_CONST_BITMAP(SQL_FN_TD_SECOND),
    GI_CONST_BITMAP(SQL_FN_TD_TIMESTAMPADD),
    GI_CONST_BITMAP(SQL_FN_TD_TIMESTAMPDIFF),
    GI_CONST_BITMAP(SQL_FN_TD_WEEK),
    GI_CONST_BITMAP(SQL_FN_TD_YEAR),
    GI_CONST_BITMAP(SQL_FN_TSI_DAY),
    GI_CONST_BITMAP(SQL_FN_TSI_FRAC_SECOND),
    GI_CONST_BITMAP(SQL_FN_TSI_HOUR),
    GI_CONST_BITMAP(SQL_FN_TSI_MINUTE),
    GI_CONST_BITMAP(SQL_FN_TSI_MONTH),
    GI_CONST_BITMAP(SQL_FN_TSI_QUARTER),
    GI_CONST_BITMAP(SQL_FN_TSI_SECOND),
    GI_CONST_BITMAP(SQL_FN_TSI_WEEK),
    GI_CONST_BITMAP(SQL_FN_TSI_YEAR),
    GI_CONST_BITMAP(SQL_GB_COLLATE),
    GI_CONST_BITMAP(SQL_GB_GROUP_BY_CONTAINS_SELECT),
    GI_CONST_BITMAP(SQL_GB_GROUP_BY_EQUALS_SELECT),
    GI_CONST_BITMAP(SQL_GB_NO_RELATION),
    GI_CONST_BITMAP(SQL_GB_NOT_SUPPORTED),
    GI_CONST_BITMAP(SQL_GD_ANY_COLUMN),
    GI_CONST_BITMAP(SQL_GD_ANY_ORDER),
    GI_CONST_BITMAP(SQL_GD_BLOCK),
    GI_CONST_BITMAP(SQL_GD_BOUND),
    GI_CONST_BITMAP(SQL_IC_LOWER),
    GI_CONST_BITMAP(SQL_IC_MIXED),
    GI_CONST_BITMAP(SQL_IC_SENSITIVE),
    GI_CONST_BITMAP(SQL_IC_UPPER),
    GI_CONST_BITMAP(SQL_IK_ALL),
    GI_CONST_BITMAP(SQL_IK_ASC),
    GI_CONST_BITMAP(SQL_IK_DESC),
    GI_CONST_BITMAP(SQL_IK_NONE),
    GI_CONST_BITMAP(SQL_IS_INSERT_LITERALS),
    GI_CONST_BITMAP(SQL_IS_INSERT_SEARCHED),
    GI_CONST_BITMAP(SQL_IS_SELECT_INTO),
    GI_CONST_BITMAP(SQL_ISV_ASSERTIONS),
    GI_CONST_BITMAP(SQL_ISV_CHARACTER_SETS),
    GI_CONST_BITMAP(SQL_ISV_CHECK_CONSTRAINTS),
    GI_CONST_BITMAP(SQL_ISV_COLLATIONS),
    GI_CONST_BITMAP(SQL_ISV_COLUMN_DOMAIN_USAGE),
    GI_CONST_BITMAP(SQL_ISV_COLUMN_PRIVILEGES),
    GI_CONST_BITMAP(SQL_ISV_COLUMNS),
    GI_CONST_BITMAP(SQL_ISV_CONSTRAINT_COLUMN_USAGE),
    GI_CONST_BITMAP(SQL_ISV_CONSTRAINT_TABLE_USAGE),
    GI_CONST_BITMAP(SQL_ISV_DOMAIN_CONSTRAINTS),
    GI_CONST_BITMAP(SQL_ISV_DOMAINS),
    GI_CONST_BITMAP(SQL_ISV_KEY_COLUMN_USAGE),
    GI_CONST_BITMAP(SQL_ISV_REFERENTIAL_CONSTRAINTS),
    GI_CONST_BITMAP(SQL_ISV_SCHEMATA),
    GI_CONST_BITMAP(SQL_ISV_SQL_LANGUAGES),
    GI_CONST_BITMAP(SQL_ISV_TABLE_CONSTRAINTS),
    GI_CONST_BITMAP(SQL_ISV_TABLE_PRIVILEGES),
    GI_CONST_BITMAP(SQL_ISV_TABLES),
    GI_CONST_BITMAP(SQL_ISV_TRANSLATIONS),
    GI_CONST_BITMAP(SQL_ISV_USAGE_PRIVILEGES),
    GI_CONST_BITMAP(SQL_ISV_VIEW_COLUMN_USAGE),
    GI_CONST_BITMAP(SQL_ISV_VIEWS),
    GI_CONST_BITMAP(SQL_ISV_VIEW_TABLE_USAGE),
    GI_CONST_BITMAP(SQL_LCK_EXCLUSIVE),
    GI_CONST_BITMAP(SQL_LCK_NO_CHANGE),
    GI_CONST_BITMAP(SQL_LCK_UNLOCK),
    GI_CONST_BITMAP(SQL_NC_END),
    GI_CONST_BITMAP(SQL_NC_HIGH),
    GI_CONST_BITMAP(SQL_NC_LOW),
    GI_CONST_BITMAP(SQL_NC_START),
    GI_CONST_BITMAP(SQL_NNC_NON_NULL),
    GI_CONST_BITMAP(SQL_NNC_NULL),
    GI_CONST_BITMAP(SQL_OAC_LEVEL1),
    GI_CONST_BITMAP(SQL_OAC_LEVEL2),
    GI_CONST_BITMAP(SQL_OAC_NONE),
    GI_CONST_BITMAP(SQL_OIC_CORE),
    GI_CONST_BITMAP(SQL_OIC_LEVEL1),
    GI_CONST_BITMAP(SQL_OIC_LEVEL2),
    GI_CONST_BITMAP(SQL_OJ_ALL_COMPARISON_OPS),
    GI_CONST_BITMAP(SQL_OJ_FULL),
    GI_CONST_BITMAP(SQL_OJ_INNER),
    GI_CONST_BITMAP(SQL_OJ_LEFT),
    GI_CONST_BITMAP(SQL_OJ_NESTED),
    GI_CONST_BITMAP(SQL_OJ_NOT_ORDERED),
    GI_CONST_BITMAP(SQL_OJ_RIGHT),
    GI_CONST_BITMAP(SQL_OSCC_COMPLIANT),
    GI_CONST_BITMAP(SQL_OSCC_NOT_COMPLIANT),
    GI_CONST_BITMAP(SQL_OSC_CORE),
    GI_CONST_BITMAP(SQL_OSC_EXTENDED),
    GI_CONST_BITMAP(SQL_OSC_MINIMUM),
    GI_CONST_BITMAP(SQL_OU_DML_STATEMENTS),
    GI_CONST_BITMAP(SQL_OU_INDEX_DEFINITION),
    GI_CONST_BITMAP(SQL_OU_PRIVILEGE_DEFINITION),
    GI_CONST_BITMAP(SQL_OU_PROCEDURE_INVOCATION),
    GI_CONST_BITMAP(SQL_OU_TABLE_DEFINITION),
    GI_CONST_BITMAP(SQL_PARC_BATCH),
    GI_CONST_BITMAP(SQL_PARC_NO_BATCH),
    GI_CONST_BITMAP(SQL_PAS_BATCH),
    GI_CONST_BITMAP(SQL_PAS_NO_BATCH),
    GI_CONST_BITMAP(SQL_PAS_NO_SELECT),
    GI_CONST_BITMAP(SQL_POS_ADD),
    GI_CONST_BITMAP(SQL_POS_DELETE),
    GI_CONST_BITMAP(SQL_POS_POSITION),
    GI_CONST_BITMAP(SQL_POS_REFRESH),
    GI_CONST_BITMAP(SQL_POS_UPDATE),
    GI_CONST_BITMAP(SQL_PS_POSITIONED_DELETE),
    GI_CONST_BITMAP(SQL_PS_POSITIONED_UPDATE),
    GI_CONST_BITMAP(SQL_PS_SELECT_FOR_UPDATE),
    GI_CONST_BITMAP(SQL_QL_END),
    GI_CONST_BITMAP(SQL_QL_START),
    GI_CONST_BITMAP(SQL_QU_DML_STATEMENTS),
    GI_CONST_BITMAP(SQL_QU_INDEX_DEFINITION),
    GI_CONST_BITMAP(SQL_QU_PRIVILEGE_DEFINITION),
    GI_CONST_BITMAP(SQL_QU_PROCEDURE_INVOCATION),
    GI_CONST_BITMAP(SQL_QU_TABLE_DEFINITION),
    GI_CONST_BITMAP(SQL_SCC_ISO92_CLI),
    GI_CONST_BITMAP(SQL_SCCO_LOCK),
    GI_CONST_BITMAP(SQL_SCCO_OPT_ROWVER),
    GI_CONST_BITMAP(SQL_SCCO_OPT_TIMESTAMP),
    GI_CONST_BITMAP(SQL_SCCO_OPT_VALUES),
    GI_CONST_BITMAP(SQL_SCCO_READ_ONLY),
    GI_CONST_BITMAP(SQL_SCC_XOPEN_CLI_VERSION1),
    GI_CONST_BITMAP(SQL_SC_FIPS127_2_TRANSITIONAL),
    GI_CONST_BITMAP(SQL_SC_NON_UNIQUE),
    GI_CONST_BITMAP(SQL_SC_SQL92_ENTRY),
    GI_CONST_BITMAP(SQL_SC_SQL92_FULL),
    GI_CONST_BITMAP(SQL_SC_SQL92_INTERMEDIATE),
    GI_CONST_BITMAP(SQL_SC_TRY_UNIQUE),
    GI_CONST_BITMAP(SQL_SC_UNIQUE),
    GI_CONST_BITMAP(SQL_SDF_CURRENT_DATE),
    GI_CONST_BITMAP(SQL_SDF_CURRENT_TIME),
    GI_CONST_BITMAP(SQL_SDF_CURRENT_TIMESTAMP),
    GI_CONST_BITMAP(SQL_SFKD_CASCADE),
    GI_CONST_BITMAP(SQL_SFKD_NO_ACTION),
    GI_CONST_BITMAP(SQL_SFKD_SET_DEFAULT),
    GI_CONST_BITMAP(SQL_SFKD_SET_NULL),
    GI_CONST_BITMAP(SQL_SFKU_CASCADE),
    GI_CONST_BITMAP(SQL_SFKU_NO_ACTION),
    GI_CONST_BITMAP(SQL_SFKU_SET_DEFAULT),
    GI_CONST_BITMAP(SQL_SFKU_SET_NULL),
    GI_CONST_BITMAP(SQL_SG_DELETE_TABLE),
    GI_CONST_BITMAP(SQL_SG_INSERT_COLUMN),
    GI_CONST_BITMAP(SQL_SG_INSERT_TABLE),
    GI_CONST_BITMAP(SQL_SG_REFERENCES_COLUMN),
    GI_CONST_BITMAP(SQL_SG_REFERENCES_TABLE),
    GI_CONST_BITMAP(SQL_SG_SELECT_TABLE),
    GI_CONST_BITMAP(SQL_SG_UPDATE_COLUMN),
    GI_CONST_BITMAP(SQL_SG_UPDATE_TABLE),
    GI_CONST_BITMAP(SQL_SG_USAGE_ON_CHARACTER_SET),
    GI_CONST_BITMAP(SQL_SG_USAGE_ON_COLLATION),
    GI_CONST_BITMAP(SQL_SG_USAGE_ON_DOMAIN),
    GI_CONST_BITMAP(SQL_SG_USAGE_ON_TRANSLATION),
    GI_CONST_BITMAP(SQL_SG_WITH_GRANT_OPTION),
    GI_CONST_BITMAP(SQL_SNVF_BIT_LENGTH),
    GI_CONST_BITMAP(SQL_SNVF_CHARACTER_LENGTH),
    GI_CONST_BITMAP(SQL_SNVF_CHAR_LENGTH),
    GI_CONST_BITMAP(SQL_SNVF_EXTRACT),
    GI_CONST_BITMAP(SQL_SNVF_OCTET_LENGTH),
    GI_CONST_BITMAP(SQL_SNVF_POSITION),
    GI_CONST_BITMAP(SQL_SO_DYNAMIC),
    GI_CONST_BITMAP(SQL_SO_FORWARD_ONLY),
    GI_CONST_BITMAP(SQL_SO_KEYSET_DRIVEN),
    GI_CONST_BITMAP(SQL_SO_MIXED),
    GI_CONST_BITMAP(SQL_SO_STATIC),
    GI_CONST_BITMAP(SQL_SP_BETWEEN),
    GI_CONST_BITMAP(SQL_SP_COMPARISON),
    GI_CONST_BITMAP(SQL_SP_EXISTS),
    GI_CONST_BITMAP(SQL_SP_IN),
    GI_CONST_BITMAP(SQL_SP_ISNOTNULL),
    GI_CONST_BITMAP(SQL_SP_ISNULL),
    GI_CONST_BITMAP(SQL_SP_LIKE),
    GI_CONST_BITMAP(SQL_SP_MATCH_FULL),
    GI_CONST_BITMAP(SQL_SP_MATCH_PARTIAL),
    GI_CONST_BITMAP(SQL_SP_MATCH_UNIQUE_FULL),
    GI_CONST_BITMAP(SQL_SP_MATCH_UNIQUE_PARTIAL),
    GI_CONST_BITMAP(SQL_SP_OVERLAPS),
    GI_CONST_BITMAP(SQL_SP_QUANTIFIED_COMPARISON),
    GI_CONST_BITMAP(SQL_SP_UNIQUE),
    GI_CONST_BITMAP(SQL_SQ_COMPARISON),
    GI_CONST_BITMAP(SQL_SQ_CORRELATED_SUBQUERIES),
    GI_CONST_BITMAP(SQL_SQ_EXISTS),
    GI_CONST_BITMAP(SQL_SQ_IN),
    GI_CONST_BITMAP(SQL_SQ_QUANTIFIED),
    GI_CONST_BITMAP(SQL_SR_CASCADE),
    GI_CONST_BITMAP(SQL_SR_DELETE_TABLE),
    GI_CONST_BITMAP(SQL_SR_GRANT_OPTION_FOR),
    GI_CONST_BITMAP(SQL_SR_INSERT_COLUMN),
    GI_CONST_BITMAP(SQL_SR_INSERT_TABLE),
    GI_CONST_BITMAP(SQL_SRJO_CORRESPONDING_CLAUSE),
    GI_CONST_BITMAP(SQL_SRJO_CROSS_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_EXCEPT_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_FULL_OUTER_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_INNER_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_INTERSECT_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_LEFT_OUTER_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_NATURAL_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_RIGHT_OUTER_JOIN),
    GI_CONST_BITMAP(SQL_SRJO_UNION_JOIN),
    GI_CONST_BITMAP(SQL_SR_REFERENCES_COLUMN),
    GI_CONST_BITMAP(SQL_SR_REFERENCES_TABLE),
    GI_CONST_BITMAP(SQL_SR_RESTRICT),
    GI_CONST_BITMAP(SQL_SR_SELECT_TABLE),
    GI_CONST_BITMAP(SQL_SR_UPDATE_COLUMN),
    GI_CONST_BITMAP(SQL_SR_UPDATE_TABLE),
    GI_CONST_BITMAP(SQL_SR_USAGE_ON_CHARACTER_SET),
    GI_CONST_BITMAP(SQL_SR_USAGE_ON_COLLATION),
    GI_CONST_BITMAP(SQL_SR_USAGE_ON_DOMAIN),
    GI_CONST_BITMAP(SQL_SR_USAGE_ON_TRANSLATION),
    GI_CONST_BITMAP(SQL_SRVC_DEFAULT),
    GI_CONST_BITMAP(SQL_SRVC_NULL),
    GI_CONST_BITMAP(SQL_SRVC_ROW_SUBQUERY),
    GI_CONST_BITMAP(SQL_SRVC_VALUE_EXPRESSION),
    GI_CONST_BITMAP(SQL_SS_ADDITIONS),
    GI_CONST_BITMAP(SQL_SS_DELETIONS),
    GI_CONST_BITMAP(SQL_SSF_CONVERT),
    GI_CONST_BITMAP(SQL_SSF_LOWER),
    GI_CONST_BITMAP(SQL_SSF_SUBSTRING),
    GI_CONST_BITMAP(SQL_SSF_TRANSLATE),
    GI_CONST_BITMAP(SQL_SSF_TRIM_BOTH),
    GI_CONST_BITMAP(SQL_SSF_TRIM_LEADING),
    GI_CONST_BITMAP(SQL_SSF_TRIM_TRAILING),
    GI_CONST_BITMAP(SQL_SSF_UPPER),
    GI_CONST_BITMAP(SQL_SS_UPDATES),
    GI_CONST_BITMAP(SQL_SU_DML_STATEMENTS),
    GI_CONST_BITMAP(SQL_SU_INDEX_DEFINITION),
    GI_CONST_BITMAP(SQL_SU_PRIVILEGE_DEFINITION),
    GI_CONST_BITMAP(SQL_SU_PROCEDURE_INVOCATION),
    GI_CONST_BITMAP(SQL_SU_TABLE_DEFINITION),
    GI_CONST_BITMAP(SQL_SVE_CASE),
    GI_CONST_BITMAP(SQL_SVE_CAST),
    GI_CONST_BITMAP(SQL_SVE_COALESCE),
    GI_CONST_BITMAP(SQL_SVE_NULLIF),
    GI_CONST_BITMAP(SQL_TC_ALL),
    GI_CONST_BITMAP(SQL_TC_DDL_COMMIT),
    GI_CONST_BITMAP(SQL_TC_DDL_IGNORE),
    GI_CONST_BITMAP(SQL_TC_DML),
    GI_CONST_BITMAP(SQL_TC_NONE),
    GI_CONST_BITMAP(SQL_TRANSACTION_READ_COMMITTED),
    GI_CONST_BITMAP(SQL_TRANSACTION_READ_UNCOMMITTED),
    GI_CONST_BITMAP(SQL_TRANSACTION_REPEATABLE_READ),
    GI_CONST_BITMAP(SQL_TRANSACTION_SERIALIZABLE),
    GI_CONST_BITMAP(SQL_TXN_READ_COMMITTED),
    GI_CONST_BITMAP(SQL_TXN_READ_UNCOMMITTED),
    GI_CONST_BITMAP(SQL_TXN_REPEATABLE_READ),
    GI_CONST_BITMAP(SQL_TXN_SERIALIZABLE),
#ifdef SQL_TXN_VERSIONING
    GI_CONST_BITMAP(SQL_TXN_VERSIONING),
#endif
    GI_CONST_BITMAP(SQL_US_UNION),
    GI_CONST_BITMAP(SQL_US_UNION_ALL),
    GI_CONST_BITMAP(SQL_U_UNION),
    GI_CONST_BITMAP(SQL_U_UNION_ALL),

    /* end of table */
    GI_CONST_BITMAP_END
};

static VALUE
dbc_getinfo(int argc, VALUE *argv, VALUE self)
{
    DBC *p = get_dbc(self);
    VALUE which, vtype, vstr;
    SQLRETURN ret;
    int i, k, info = -1, maptype = -1, info_found = 0;
    SQLUSMALLINT sbuffer;
    SQLUINTEGER lbuffer;
    SQLSMALLINT len_in, len_out;
    char *string = NULL;
#ifdef UNICODE
    SQLWCHAR buffer[513];
#else
    char buffer[513];
#endif
    char ebuffer[513];

    rb_scan_args(argc, argv, "11", &which, &vtype);
    switch (TYPE(which)) {
    default:
	vstr = rb_any_to_s(which);
	string = STR2CSTR(vstr);
	goto doString;
    case T_STRING:
	string = STR2CSTR(which);
    doString:
	for (i = 0; get_info_map[i].name != NULL; i++) {
	    if (strcmp(string, get_info_map[i].name) == 0) {
		info = get_info_map[i].info;
		maptype = get_info_map[i].maptype;
		info_found = 2;
		break;
	    }
	}
	break;
    case T_FLOAT:
    case T_BIGNUM:
	k = (int) NUM2DBL(which);
	goto doInt;
    case T_FIXNUM:
	k = FIX2INT(which);
    doInt:
	info = k;
	info_found = 1;
	for (i = 0; get_info_map[i].name != NULL; i++) {
	    if (k == get_info_map[i].info) {
		info = get_info_map[i].info;
		maptype = get_info_map[i].maptype;
		info_found = 3;
		break;
	    }
	}
	break;
    }
    switch (info_found) {
    case 0:
	rb_raise(Cerror, "%s",
		 set_err("Invalid info type for ODBC::Connection.get_info",
			 0));
	return Qnil;
    case 1:
	sprintf(ebuffer, "Unknown info type %d for ODBC::Connection.get_info",
		info);
	set_err(ebuffer, 1);
	break;
    }
    if (vtype != Qnil) {
	switch (TYPE(vtype)) {
	case T_FIXNUM:
	    maptype = FIX2INT(vtype);
	    break;
	case T_BIGNUM:
	case T_FLOAT:
	    maptype = (int) NUM2DBL(vtype);
	    break;
	default:
	    rb_raise(rb_eTypeError, "need number for sql_type");
	    return Qnil;
	}
	switch (maptype) {
	case SQL_NUMERIC:
	case SQL_DECIMAL:
	case SQL_INTEGER:
	case SQL_FLOAT:
	case SQL_REAL:
	case SQL_DOUBLE:
	case SQL_C_ULONG:
#ifdef SQL_BIGINT
	case SQL_BIGINT:
#endif
#ifdef SQL_C_UBIGINT
	case SQL_C_UBIGINT:
#endif
	    maptype = SQL_C_LONG;
	    break;
	case SQL_SMALLINT:
	case SQL_TINYINT:
	case SQL_C_USHORT:
	case SQL_C_UTINYINT:
	    maptype = SQL_C_SHORT;
	    break;
	default:
	    maptype = SQL_C_CHAR;
	    break;
	}
    }
    switch (maptype) {
    case SQL_C_SHORT:
	len_in = sizeof (sbuffer);
	sbuffer = 0;
	ret = SQLGetInfo(p->hdbc, (SQLUSMALLINT) info,
			 (SQLPOINTER) &sbuffer, len_in, &len_out);
	break;
    case SQL_C_LONG:
	len_in = sizeof (lbuffer);
	lbuffer = 0;
	ret = SQLGetInfo(p->hdbc, (SQLUSMALLINT) info,
			 (SQLPOINTER) &lbuffer, len_in, &len_out);
	break;
    default:
    case SQL_C_CHAR:
#ifdef UNICODE
	len_in = sizeof (buffer) - sizeof (SQLWCHAR);
#else
	len_in = sizeof (buffer) - 1;
#endif
	memset(buffer, 0, sizeof (buffer));
	ret = SQLGetInfo(p->hdbc, (SQLUSMALLINT) info,
			 (SQLPOINTER) buffer, len_in, &len_out);
	break;
    }
    if (!SQL_SUCCEEDED(ret)) {
	rb_raise(Cerror, "%s",
		 get_err(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT));
    }
    switch (maptype) {
    case SQL_C_SHORT:
	return INT2NUM(sbuffer);
    case SQL_C_LONG:
	return INT2NUM(lbuffer);
    default:
    case SQL_C_CHAR:
#ifdef UNICODE
	return uc_str_new(buffer, len_out / sizeof (SQLWCHAR));
#else
	return rb_str_new(buffer, len_out);
#endif
    }
    return Qnil;
}

/*
 *----------------------------------------------------------------------
 *
 *	Fill column type array for statement.
 *
 *----------------------------------------------------------------------
 */

static COLTYPE *
make_coltypes(SQLHSTMT hstmt, int ncols, char **msgp)
{
    int i;
    COLTYPE *ret = NULL;

    for (i = 0; i < ncols; i++) {
	SQLUSMALLINT ic = i + 1;
	SQLLEN type = SQL_UNKNOWN_TYPE, size = 0;

	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLColAttributes(hstmt, ic,
					SQL_COLUMN_TYPE, NULL, 0, NULL,
					&type),
		       msgp, "SQLColAttributes(SQL_COLUMN_TYPE)")) {
	    return ret;
	}
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLColAttributes(hstmt, ic,
					SQL_COLUMN_DISPLAY_SIZE,
					NULL, 0, NULL, &size),
		       msgp, "SQLColAttributes(SQL_COLUMN_DISPLAY_SIZE)")) {
	    return ret;
	}
    }
    ret = ALLOC_N(COLTYPE, ncols);
    if (ret == NULL) {
	if (msgp != NULL) {
	    *msgp = set_err("Out of memory", 0);
	}
	return NULL;
    }
    for (i = 0; i < ncols; i++) {
	SQLUSMALLINT ic = i + 1;
	SQLLEN type = SQL_UNKNOWN_TYPE, size = 0;

	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		SQLColAttributes(hstmt, ic,
				 SQL_COLUMN_TYPE,
				 NULL, 0, NULL, &type),
		"SQLColAttributes(SQL_COLUMN_TYPE)");
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		SQLColAttributes(hstmt, ic,
				 SQL_COLUMN_DISPLAY_SIZE,
				 NULL, 0, NULL, &size),
		"SQLColAttributes(SQL_COLUMN_DISPLAY_SIZE)");
	switch (type) {
#ifdef SQL_BIT
	case SQL_BIT:
#endif
#ifdef SQL_TINYINT
	case SQL_TINYINT:
#endif
	case SQL_SMALLINT:
	case SQL_INTEGER:
	    type = SQL_C_LONG;
	    size = sizeof (SQLINTEGER);
	    break;
	case SQL_FLOAT:
	case SQL_DOUBLE:
	case SQL_REAL:
	    type = SQL_C_DOUBLE;
	    size = sizeof (double);
	    break;
	case SQL_DATE:
#ifdef SQL_TYPE_DATE
	case SQL_TYPE_DATE:
#endif
	    type = SQL_C_DATE;
	    size = sizeof (DATE_STRUCT);
	    break;
	case SQL_TIME:
#ifdef SQL_TYPE_TIME
	case SQL_TYPE_TIME:
#endif
	    type = SQL_C_TIME;
	    size = sizeof (TIME_STRUCT);
	    break;
	case SQL_TIMESTAMP:
#ifdef SQL_TYPE_TIMESTAMP
	case SQL_TYPE_TIMESTAMP:
#endif
	    type = SQL_C_TIMESTAMP;
	    size = sizeof (TIMESTAMP_STRUCT);
	    break;
	case SQL_LONGVARBINARY:
	    type = SQL_C_BINARY;
	    size = SQL_NO_TOTAL;
	    break;
	case SQL_LONGVARCHAR:
#ifdef UNICODE
	case SQL_WLONGVARCHAR:
	    type = SQL_C_WCHAR;
#else
	    type = SQL_C_CHAR;
#endif
	    size = SQL_NO_TOTAL;
	    break;
#ifdef SQL_BIGINT
	case SQL_BIGINT:
	    if (sizeof (SQLBIGINT) == sizeof (SQLINTEGER)) {
		type = SQL_C_LONG;
		size = sizeof (SQLINTEGER);
	    } else {
		type = SQL_C_SBIGINT;
		size = sizeof (SQLBIGINT);
	    }
	    break;
#endif
#ifdef SQL_UBIGINT
	case SQL_UBIGINT:
	    if (sizeof (SQLBIGINT) == sizeof (SQLINTEGER)) {
		type = SQL_C_LONG;
		size = sizeof (SQLINTEGER);
	    } else {
		type = SQL_C_UBIGINT;
		size = sizeof (SQLBIGINT);
	    }
	    break;
#endif
	case SQL_DECIMAL:
	case SQL_NUMERIC:
	    if ((size == 0) || (size > SEGSIZE)) {
		size = SQL_NO_TOTAL;
	    }
	    if (size != SQL_NO_TOTAL) {
		size += 2;	/* sign and decimal point */
		size += 1;	/* NUL terminator */
	    }
#ifdef UNICODE
	    type = SQL_C_WCHAR;
	    size *= sizeof (SQLWCHAR);
#else
	    type = SQL_C_CHAR;
#endif
	    break;
	default:
	    if ((size == 0) || (size > SEGSIZE)) {
		size = SQL_NO_TOTAL;
	    }
#ifdef UNICODE
	    type = SQL_C_WCHAR;
	    if (size != SQL_NO_TOTAL) {
		size *= sizeof (SQLWCHAR);
		size += sizeof (SQLWCHAR);
	    }
#else
	    type = SQL_C_CHAR;
	    if (size != SQL_NO_TOTAL) {
		size += 1;
	    }
#endif
	    break;
	}
	ret[i].type = type;
	ret[i].size = size;
    }
    return ret;
}

/*
 *----------------------------------------------------------------------
 *
 *	Fill parameter info array for statement.
 *
 *----------------------------------------------------------------------
 */

static PARAMINFO *
make_paraminfo(SQLHSTMT hstmt, int nump, char **msgp)
{
    int i;
    PARAMINFO *paraminfo = NULL;

    paraminfo = ALLOC_N(PARAMINFO, nump);
    if (paraminfo == NULL) {
	if (msgp != NULL) {
	    *msgp = set_err("Out of memory", 0);
	}
	return NULL;
    }
    for (i = 0; i < nump; i++) {
	paraminfo[i].iotype = SQL_PARAM_INPUT;
	paraminfo[i].outsize = 0;
	paraminfo[i].outbuf = NULL;
	paraminfo[i].rlen = SQL_NULL_DATA;
	paraminfo[i].ctype = SQL_C_CHAR;
#ifdef UNICODE
	paraminfo[i].outtype = SQL_WCHAR;
#else
	paraminfo[i].outtype = SQL_CHAR;
#endif
	paraminfo[i].coldef_max = 0;
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLDescribeParam(hstmt, (SQLUSMALLINT) (i + 1),
					&paraminfo[i].type,
					&paraminfo[i].coldef,
					&paraminfo[i].scale,
					&paraminfo[i].nullable),
		       NULL, "SQLDescribeParam")) {
#ifdef UNICODE
	    paraminfo[i].type = SQL_WVARCHAR;
#else
	    paraminfo[i].type = SQL_VARCHAR;
#endif
	    paraminfo[i].coldef = 0;
	    paraminfo[i].scale = 0;
	    paraminfo[i].nullable = SQL_NULLABLE_UNKNOWN;
	    paraminfo[i].override = 0;
	}
    }
    return paraminfo;
}

static void
retain_paraminfo_override(STMT *q, int nump, PARAMINFO *paraminfo)
{
    if ((q->paraminfo != NULL) && (q->nump == nump)) {
	int i;

	for (i = 0; i < nump; i++) {
	    paraminfo[i].iotype = q->paraminfo[i].iotype;
	    paraminfo[i].rlen = q->paraminfo[i].rlen;
	    paraminfo[i].ctype = q->paraminfo[i].ctype;
	    paraminfo[i].outtype = q->paraminfo[i].outtype;
	    paraminfo[i].outsize = q->paraminfo[i].outsize;
	    if (q->paraminfo[i].outbuf != NULL) {
		paraminfo[i].outbuf = q->paraminfo[i].outbuf;
		q->paraminfo[i].outbuf = NULL;
	    }
	    if (q->paraminfo[i].override) {
		paraminfo[i].override = q->paraminfo[i].override;
		paraminfo[i].type = q->paraminfo[i].type;
		paraminfo[i].coldef = q->paraminfo[i].coldef;
		paraminfo[i].scale = q->paraminfo[i].scale;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 *	Wrap SQLHSTMT into struct/VALUE.
 *
 *----------------------------------------------------------------------
 */

static VALUE
wrap_stmt(VALUE dbc, DBC *p, SQLHSTMT hstmt, STMT **qp)
{
    VALUE stmt = Qnil;
    STMT *q;
    int i;

    stmt = Data_Make_Struct(Cstmt, STMT, mark_stmt, free_stmt, q);
    tracemsg(2, fprintf(stderr, "ObjAlloc: STMT %p\n", q););
    list_init(&q->link, offsetof(STMT, link));
    q->self = stmt;
    q->hstmt = hstmt;
    q->dbc = dbc;
    q->dbcp = NULL;
    q->paraminfo = NULL;
    q->coltypes = NULL;
    q->colnames = q->dbufs = NULL;
    q->colvals = NULL;
    q->fetchc = 0;
    q->upc = p->upc;
    q->usef = 0;
    rb_iv_set(q->self, "@_a", rb_ary_new());
    rb_iv_set(q->self, "@_h", rb_hash_new());
    for (i = 0; i < 4; i++) {
	rb_iv_set(q->self, colnamebuf[i], rb_hash_new());
    }
    if (hstmt != SQL_NULL_HSTMT) {
	link_stmt(q, p);
    } else {
	q->dbc = Qnil;
    }
    if (qp != NULL) {
	*qp = q;
    }
    return stmt;
}

/*
 *----------------------------------------------------------------------
 *
 *	Create statement with result.
 *
 *----------------------------------------------------------------------
 */

static VALUE
make_result(VALUE dbc, SQLHSTMT hstmt, VALUE result, int mode)
{
    DBC *p;
    STMT *q;
    SQLSMALLINT cols = 0, nump;
    COLTYPE *coltypes = NULL;
    PARAMINFO *paraminfo = NULL;
    char *msg = NULL;

    Data_Get_Struct(dbc, DBC, p);
    if ((hstmt == SQL_NULL_HSTMT) ||
	!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		   SQLNumParams(hstmt, &nump), NULL, "SQLNumParams")) {
	nump = 0;
    }
    if (nump > 0) {
	paraminfo = make_paraminfo(hstmt, nump, &msg);
	if (paraminfo == NULL) {
	    goto error;
	}
    }
    if ((mode & MAKERES_PREPARE) ||
	(hstmt == SQL_NULL_HSTMT) ||
	(!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		   SQLNumResultCols(hstmt, &cols), NULL,
		   "SQLNumResultCols"))) {
	cols = 0;
    }
    if (cols > 0) {
	coltypes = make_coltypes(hstmt, cols, &msg);
	if (coltypes == NULL) {
	    goto error;
	}
    }
    if (result == Qnil) {
	result = wrap_stmt(dbc, p, hstmt, &q);
    } else {
	Data_Get_Struct(result, STMT, q);
	retain_paraminfo_override(q, nump, paraminfo);
	free_stmt_sub(q, 1);
	if (q->dbc != dbc) {
	    unlink_stmt(q);
	    q->dbc = dbc;
	    if (hstmt != SQL_NULL_HSTMT) {
		link_stmt(q, p);
	    }
	}
	q->hstmt = hstmt;
    }
    q->nump = nump;
    q->paraminfo = paraminfo;
    q->ncols = cols;
    q->coltypes = coltypes;
    if ((mode & MAKERES_BLOCK) && rb_block_given_p()) {
	if (mode & MAKERES_NOCLOSE) {
	    return rb_yield(result);
	}
	return rb_ensure(rb_yield, result, stmt_close, result);
    }
    return result;
error:
    callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
	    SQLFreeStmt(hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
    if (result != Qnil) {
	Data_Get_Struct(result, STMT, q);
	if (q->hstmt == hstmt) {
	    q->hstmt = SQL_NULL_HSTMT;
	    unlink_stmt(q);
	}
    }
    if (paraminfo != NULL) {
	xfree(paraminfo);
    }
    if (coltypes != NULL) {
	xfree(coltypes);
    }
    rb_raise(Cerror, "%s", msg);
    return Qnil;
}

static char *
upcase_if(char *string, int upc)
{
    if (upc && (string != NULL)) {
	unsigned char *p = (unsigned char *) string;

	while (*p != '\0') {
#ifdef UNICODE
	    if ((*p < 0x80) && ISLOWER(*p))
#else
	    if (ISLOWER(*p))
#endif
	    {
		*p = toupper(*p);
	    }
	    ++p;
	}
    }
    return string;
}

/*
 *----------------------------------------------------------------------
 *
 *	Constructor: make column from statement.
 *
 *----------------------------------------------------------------------
 */

static VALUE
make_column(SQLHSTMT hstmt, int i, int upc, int use_scn)
{
    VALUE obj, v;
    SQLUSMALLINT ic = i + 1;
    SQLLEN iv;
#ifdef UNICODE
    SQLWCHAR name[SQL_MAX_MESSAGE_LENGTH];
#else
    char name[SQL_MAX_MESSAGE_LENGTH];
#endif
    char *msg;
    SQLSMALLINT name_len;

    name[0] = 0;
    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		   SQLColAttributes(hstmt, ic, use_scn ? SQL_COLUMN_NAME :
				    SQL_COLUMN_LABEL, name,
				    (SQLSMALLINT) sizeof (name),
				    &name_len, NULL),
		   &msg, use_scn ? "SQLColAttributes(SQL_COLUMN_NAME)" :
		   "SQLColAttributes(SQL_COLUMN_LABEL)")) {
	rb_raise(Cerror, "%s", msg);
    }
    obj = rb_obj_alloc(Ccolumn);
    if (name_len >= (SQLSMALLINT) sizeof (name)) {
	name_len = sizeof (name) - 1;
    }
    if (name_len > 0) {
	name[name_len / sizeof (name[0])] = 0;
    }
#ifdef UNICODE
    if (upc) {
	int len = uc_strlen(name);
	char tmpbuf[1];
	char *tmp = xmalloc(len);
	VALUE v;

	if (tmp == NULL) {
	    tmp = tmpbuf;
	    len = 0;
	}
	mkutf(tmp, name, len);
	v = RB_TAINTED_STR_NEW2(upcase_if(tmp, 1));
#ifdef USE_RB_ENC
	rb_enc_associate(v, rb_enc);
#endif
	rb_iv_set(obj, "@name", v);
	if ((tmp != NULL) && (tmp != tmpbuf)) {
	    xfree(tmp);
	}
    } else {
	rb_iv_set(obj, "@name", uc_tainted_str_new2(name));
    }
#else
    rb_iv_set(obj, "@name", RB_TAINTED_STR_NEW2(upcase_if(name, upc)));
#endif
    v = Qnil;
    name[0] = 0;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_TABLE_NAME, name,
				   (SQLSMALLINT) sizeof (name),
				   &name_len, NULL),
		  NULL, "SQLColAttributes(SQL_COLUMN_TABLE_NAME)")) {
	if (name_len > (SQLSMALLINT) sizeof (name)) {
	    name_len = sizeof (name) - 1;
	}
	if (name_len > 0) {
	    name[name_len / sizeof (name[0])] = 0;
	}
#ifdef UNICODE
	v = uc_tainted_str_new2(name);
#else
	v = RB_TAINTED_STR_NEW2(name);
#endif
    }
    rb_iv_set(obj, "@table", v);
    iv = SQL_UNKNOWN_TYPE;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_TYPE, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_TYPE)")) {
	v = INT2NUM(iv);
    } else {
	v = INT2NUM(SQL_UNKNOWN_TYPE);
    }
    rb_iv_set(obj, "@type", v);
    v = Qnil;
    iv = 0;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic,
#if (ODBCVER >= 0x0300)
				   SQL_DESC_LENGTH,
#else
				   SQL_COLUMN_LENGTH,
#endif
				   NULL, 0, NULL, &iv),
		  NULL,
#if (ODBCVER >= 0x0300)
		  "SQLColAttributes(SQL_DESC_LENGTH)"
#else
		  "SQLColAttributes(SQL_COLUMN_LENGTH)"
#endif
	)) {
	v = INT2NUM(iv);
    } else if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
			 SQLColAttributes(hstmt, ic,
					  SQL_COLUMN_DISPLAY_SIZE, NULL,
					  0, NULL, &iv),
			 NULL, "SQLColAttributes(SQL_COLUMN_DISPLAY_SIZE)")) {
	v = INT2NUM(iv);
    }
    rb_iv_set(obj, "@length", v);
    v = Qnil;
    iv = SQL_NO_NULLS;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_NULLABLE, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_NULLABLE)")) {
	v = (iv == SQL_NO_NULLS) ? Qfalse : Qtrue;
    }
    rb_iv_set(obj, "@nullable", v);
    v = Qnil;
    iv = 0;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_SCALE, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_SCALE)")) {
	v = INT2NUM(iv);
    }
    rb_iv_set(obj, "@scale", v);
    v = Qnil;
    iv = 0;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_PRECISION, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_PRECISION)")) {
	v = INT2NUM(iv);
    }
    rb_iv_set(obj, "@precision", v);
    v = Qnil;
    iv = SQL_UNSEARCHABLE;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_SEARCHABLE, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_SEARCHABLE)")) {
	v = (iv == SQL_UNSEARCHABLE) ? Qfalse : Qtrue;
    }
    rb_iv_set(obj, "@searchable", v);
    v = Qnil;
    iv = SQL_FALSE;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_UNSIGNED, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_UNSIGNED)")) {
	v = (iv == SQL_FALSE) ? Qfalse : Qtrue;
    }
    rb_iv_set(obj, "@unsigned", v);
    v = Qnil;
#ifdef SQL_COLUMN_AUTO_INCREMENT
    iv = SQL_FALSE;
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		  SQLColAttributes(hstmt, ic, SQL_COLUMN_AUTO_INCREMENT, NULL,
				   0, NULL, &iv),
		  NULL, "SQLColAttributes(SQL_COLUMN_AUTO_INCREMENT)")) {
	v = (iv == SQL_FALSE) ? Qfalse : Qtrue;
    }
#endif
    rb_iv_set(obj, "@autoincrement", v);
    return obj;
}

/*
 *----------------------------------------------------------------------
 *
 *	Constructor: make parameter from statement.
 *
 *----------------------------------------------------------------------
 */

static VALUE
make_param(STMT *q, int i)
{
    VALUE obj;
    int v;

    obj = rb_obj_alloc(Cparam);
#ifdef UNICODE
    v = q->paraminfo ? q->paraminfo[i].type : SQL_WVARCHAR;
#else
    v = q->paraminfo ? q->paraminfo[i].type : SQL_VARCHAR;
#endif
    rb_iv_set(obj, "@type", INT2NUM(v));
    v = q->paraminfo ? q->paraminfo[i].coldef : 0;
    rb_iv_set(obj, "@precision", INT2NUM(v));
    v = q->paraminfo ? q->paraminfo[i].scale : 0;
    rb_iv_set(obj, "@scale", INT2NUM(v));
    v = q->paraminfo ? q->paraminfo[i].nullable : SQL_NULLABLE_UNKNOWN;
    rb_iv_set(obj, "@nullable", INT2NUM(v));
    v = q->paraminfo ? q->paraminfo[i].iotype : SQL_PARAM_INPUT;
    rb_iv_set(obj, "@iotype", INT2NUM(v));
    v = q->paraminfo ? q->paraminfo[i].outsize : 0;
    rb_iv_set(obj, "@output_size", INT2NUM(v));
#ifdef UNICODE
    v = q->paraminfo ? q->paraminfo[i].outtype : SQL_WCHAR;
#else
    v = q->paraminfo ? q->paraminfo[i].outtype : SQL_CHAR;
#endif
    rb_iv_set(obj, "@output_type", INT2NUM(v));
    return obj;
}

/*
 *----------------------------------------------------------------------
 *
 *	Query tables/columns/keys/indexes/types of data source.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_info(int argc, VALUE *argv, VALUE self, int mode)
{
    DBC *p = get_dbc(self);
    VALUE which = Qnil, which2 = Qnil, which3 = Qnil;
#ifdef UNICODE
    SQLWCHAR *swhich = NULL, *swhich2 = NULL;
#else
    SQLCHAR *swhich = NULL, *swhich2 = NULL;
#endif
    char *msg;
    const char *argspec = NULL;
    SQLHSTMT hstmt;
    int needstr = 1, itype = SQL_ALL_TYPES;
    int iid = SQL_BEST_ROWID, iscope = SQL_SCOPE_CURROW;

    if (p->hdbc == SQL_NULL_HDBC) {
	rb_raise(Cerror, "%s", set_err("No connection", 0));
    }
    switch (mode) {
    case INFO_TYPES:
	needstr = 0;
	/* FALL THRU */
    case INFO_TABLES:
    case INFO_PRIMKEYS:
    case INFO_TPRIV:
    case INFO_PROCS:
	argspec = "01";
	break;
    case INFO_COLUMNS:
	argspec = "02";
	break;
    case INFO_INDEXES:
    case INFO_FORKEYS:
    case INFO_PROCCOLS:
	argspec = "11";
	break;
    case INFO_SPECCOLS:
	argspec = "12";
	break;
    default:
	rb_raise(Cerror, "%s", set_err("Invalid info mode", 0));
	break;
    }
    rb_scan_args(argc, argv, argspec, &which, &which2, &which3);
    if (which != Qnil) {
	if (needstr) {
	    Check_Type(which, T_STRING);
#ifdef UNICODE
	    swhich = (SQLWCHAR *) which;
#else
	    swhich = (SQLCHAR *) STR2CSTR(which);
#endif
	} else {
	    itype = NUM2INT(which);
	}
    }
    if (which2 != Qnil) {
	if (mode == INFO_SPECCOLS) {
	    iid = NUM2INT(which2);
	} else if (mode != INFO_INDEXES) {
	    Check_Type(which2, T_STRING);
#ifdef UNICODE
	    swhich2 = (SQLWCHAR *) which2;
#else
	    swhich2 = (SQLCHAR *) STR2CSTR(which2);
#endif
	}
    }
#ifdef UNICODE
    if (swhich != NULL) {
	VALUE val = (VALUE) swhich;

#ifdef USE_RB_ENC
	val = rb_funcall(val, IDencode, 1, rb_encv);
#endif
	swhich = uc_from_utf((unsigned char *) STR2CSTR(val), -1);
	if (swhich == NULL) {
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
    }
    if (swhich2 != NULL) {
	VALUE val = (VALUE) swhich2;

#ifdef USE_RB_ENC
	val = rb_funcall(val, IDencode, 1, rb_encv);
#endif
	swhich2 = uc_from_utf((unsigned char *) STR2CSTR(val), -1);
	if (swhich2 == NULL) {
	    uc_free(swhich);
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
    }
#endif
    if (which3 != Qnil) {
	iscope = NUM2INT(which3);
    }
    if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		   SQLAllocStmt(p->hdbc, &hstmt), &msg, "SQLAllocStmt")) {
#ifdef UNICODE
	uc_free(swhich);
	uc_free(swhich2);
#endif
	rb_raise(Cerror, "%s", msg);
    }
    switch (mode) {
    case INFO_TABLES:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLTABLES(hstmt, NULL, 0, NULL, 0,
				 swhich, (swhich == NULL) ? 0 : SQL_NTS,
				 NULL, 0),
		       &msg, "SQLTables")) {
	    goto error;
	}
	break;
    case INFO_COLUMNS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLCOLUMNS(hstmt, NULL, 0, NULL, 0,
				  swhich, (swhich == NULL) ? 0 : SQL_NTS,
				  swhich2, (swhich2 == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLColumns")) {
	    goto error;
	}
	break;
    case INFO_PRIMKEYS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLPRIMARYKEYS(hstmt, NULL, 0, NULL, 0,
				      swhich, (swhich == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLPrimaryKeys")) {
	    goto error;
	}
	break;
    case INFO_INDEXES:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLSTATISTICS(hstmt, NULL, 0, NULL, 0,
				     swhich, (swhich == NULL) ? 0 : SQL_NTS,
				     (SQLUSMALLINT) (RTEST(which2) ?
				      SQL_INDEX_UNIQUE : SQL_INDEX_ALL),
				     SQL_ENSURE),
		       &msg, "SQLStatistics")) {
	    goto error;
	}
	break;
    case INFO_TYPES:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLGETTYPEINFO(hstmt, (SQLSMALLINT) itype),
		       &msg, "SQLGetTypeInfo")) {
	    goto error;
	}
	break;
    case INFO_FORKEYS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLFOREIGNKEYS(hstmt, NULL, 0, NULL, 0,
				      swhich, (swhich == NULL) ? 0 : SQL_NTS,
				      NULL, 0, NULL, 0,
				      swhich2, (swhich2 == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLForeignKeys")) {
	    goto error;
	}
	break;
    case INFO_TPRIV:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLTABLEPRIVILEGES(hstmt, NULL, 0, NULL, 0, swhich,
					  (swhich == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLTablePrivileges")) {
	    goto error;
	}
	break;
    case INFO_PROCS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLPROCEDURES(hstmt, NULL, 0, NULL, 0,
				     swhich, (swhich == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLProcedures")) {
	    goto error;
	}
	break;
    case INFO_PROCCOLS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLPROCEDURECOLUMNS(hstmt, NULL, 0, NULL, 0,
					   swhich,
					   (swhich == NULL) ? 0 : SQL_NTS,
					   swhich2,
					   (swhich2 == NULL) ? 0 : SQL_NTS),
		       &msg, "SQLProcedureColumns")) {
	    goto error;
	}
	break;
    case INFO_SPECCOLS:
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		       SQLSPECIALCOLUMNS(hstmt, (SQLUSMALLINT) iid,
					 NULL, 0, NULL, 0,
					 swhich,
					 (swhich == NULL) ? 0 : SQL_NTS,
					 (SQLUSMALLINT) iscope,
					 SQL_NULLABLE),
		       &msg, "SQLSpecialColumns")) {
	    goto error;
	}
	break;
    }
#ifdef UNICODE
    uc_free(swhich);
    uc_free(swhich2);
#endif
    return make_result(self, hstmt, Qnil, MAKERES_BLOCK);
error:
#ifdef UNICODE
    uc_free(swhich);
    uc_free(swhich2);
#endif
    callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
	    SQLFreeStmt(hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
    rb_raise(Cerror, "%s", msg);
    return Qnil;
}

static VALUE
dbc_tables(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_TABLES);
}

static VALUE
dbc_columns(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_COLUMNS);
}

static VALUE
dbc_primkeys(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_PRIMKEYS);
}

static VALUE
dbc_indexes(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_INDEXES);
}

static VALUE
dbc_types(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_TYPES);
}

static VALUE
dbc_forkeys(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_FORKEYS);
}

static VALUE
dbc_tpriv(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_TPRIV);
}

static VALUE
dbc_procs(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_PROCS);
}

static VALUE
dbc_proccols(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_PROCCOLS);
}

static VALUE
dbc_speccols(int argc, VALUE *argv, VALUE self)
{
    return dbc_info(argc, argv, self, INFO_SPECCOLS);
}

/*
 *----------------------------------------------------------------------
 *
 *	Transaction stuff.
 *
 *----------------------------------------------------------------------
 */

static VALUE
dbc_trans(VALUE self, int what)
{
    ENV *e;
    SQLHDBC dbc = SQL_NULL_HDBC;
    SQLRETURN ret;
    char *msg;

    e = get_env(self);
    if (rb_obj_is_kind_of(self, Cdbc) == Qtrue) {
	DBC *d;

	d = get_dbc(self);
	dbc = d->hdbc;
    }
#if (ODBCVER >= 0x0300)
    ret = SQLENDTRAN((SQLSMALLINT)
		     ((dbc == SQL_NULL_HDBC) ? SQL_HANDLE_ENV : SQL_HANDLE_DBC),
		     (dbc == SQL_NULL_HDBC) ? e->henv : dbc,
		     (SQLSMALLINT) what);
#else
    ret = SQLTRANSACT(e->henv, dbc, (SQLUSMALLINT) what);
#endif
    if (!succeeded(e->henv, dbc, SQL_NULL_HSTMT, ret,
#if (ODBCVER >= 0x0300)
		   &msg, "SQLEndTran"
#else
		   &msg, "SQLTransact"
#endif
       )) {
	rb_raise(Cerror, "%s", msg);
    }
    return Qnil;
}

static VALUE
dbc_commit(VALUE self)
{
    return dbc_trans(self, SQL_COMMIT);
}

static VALUE
dbc_rollback(VALUE self)
{
    return dbc_trans(self, SQL_ROLLBACK);
}

static VALUE
dbc_nop(VALUE self)
{
    return Qnil;
}

static VALUE
dbc_transbody(VALUE self)
{
    return rb_yield(rb_ary_entry(self, 0));
}

static VALUE
dbc_transfail(VALUE self, VALUE err)
{
    rb_ary_store(self, 1, err);
    dbc_rollback(rb_ary_entry(self, 0));
    return Qundef;
}

static VALUE
dbc_transaction(VALUE self)
{
    VALUE a, ret;

    if (!rb_block_given_p()) {
	rb_raise(rb_eArgError, "block required");
    }
    rb_ensure(dbc_commit, self, dbc_nop, self);
    a = rb_ary_new2(2);
    rb_ary_store(a, 0, self);
    rb_ary_store(a, 1, Qnil);
    if ((ret = rb_rescue2(dbc_transbody, a, dbc_transfail, a,
			  rb_eException, (VALUE) 0)) != Qundef) {
	dbc_commit(self);
	return ret;
    }
    ret = rb_ary_entry(a, 1);
    rb_exc_raise(rb_exc_new3(rb_obj_class(ret),
			     FUNCALL_NOARGS(ret, IDto_s)));
    return Qnil;
}

/*
 *----------------------------------------------------------------------
 *
 *	Environment attribute handling.
 *
 *----------------------------------------------------------------------
 */

#if (ODBCVER >= 0x0300)
static VALUE
do_attr(int argc, VALUE *argv, VALUE self, int op)
{
    SQLHENV henv = SQL_NULL_HENV;
    VALUE val;
    SQLLEN v = 0;
    SQLPOINTER vp;
    SQLINTEGER l;
    char *msg;

    if (self != Modbc) {
	henv = get_env(self)->henv;
    }
    rb_scan_args(argc, argv, "01", &val);
    if (val == Qnil) {
	if (!succeeded(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		       SQLGetEnvAttr(henv, (SQLINTEGER) op,
				     (SQLPOINTER) &v, sizeof (v), &l),
		       &msg, "SQLGetEnvAttr(%d)", op)) {
	    rb_raise(Cerror, "%s", msg);
	}
	return rb_int2inum(v);
    }
    v = NUM2INT(val);
    vp = (SQLPOINTER) v;
    if (!succeeded(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT,
		   SQLSetEnvAttr(henv, (SQLINTEGER) op, vp, SQL_IS_INTEGER),
		   &msg, "SQLSetEnvAttr(%d)", op)) {
	rb_raise(Cerror, "%s", msg);
    }
    return Qnil;
}
#endif

static VALUE
env_cpooling(int argc, VALUE *argv, VALUE self)
{
#if (ODBCVER >= 0x0300)
    return do_attr(argc, argv, self, SQL_ATTR_CONNECTION_POOLING);
#else
    rb_raise(Cerror, "%s", set_err("Unsupported in ODBC < 3.0", 0));
    return Qnil;
#endif
}

static VALUE
env_cpmatch(int argc, VALUE *argv, VALUE self)
{
#if (ODBCVER >= 0x0300)
    return do_attr(argc, argv, self, SQL_ATTR_CP_MATCH);
#else
    rb_raise(Cerror, "%s", set_err("Unsupported in ODBC < 3.0", 0));
    return Qnil;
#endif
}

static VALUE
env_odbcver(int argc, VALUE *argv, VALUE self)
{
#if (ODBCVER >= 0x0300)
    return do_attr(argc, argv, self, SQL_ATTR_ODBC_VERSION);
#else
    VALUE val;

    rb_scan_args(argc, argv, "01", &val);
    if (val == Qnil) {
	return rb_int2inum(ODBCVER >> 8);
    }
    rb_raise(Cerror, "%s", set_err("Unsupported in ODBC < 3.0", 0));
#endif
}

/*
 *----------------------------------------------------------------------
 *
 *	Connection/statement option handling.
 *
 *	Note:
 *	ODBC 2 allows statement options to be set using SQLSetConnectOption,
 *	establishing the statement option as a default for any hstmts
 *	later allocated for that hdbc. This feature was deprecated in
 *	ODBC 3.x and may not work with ODBC 3.x drivers.
 *
 *	Although the Database class includes attribute accessors for
 *	statement-level options, a safer alternative, if using an ODBC 3
 *	driver, is to set the option directly on the Statement instance.
 *
 *----------------------------------------------------------------------
 */

#define OPT_LEVEL_STMT		1
#define OPT_LEVEL_DBC		2
#define OPT_LEVEL_BOTH		(OPT_LEVEL_STMT | OPT_LEVEL_DBC)

#define OPT_CONST_INT(x, level) { #x, x, level }
#define OPT_CONST_END    { NULL, -1 }
static struct {
    const char *name;
    int option;
    int level;
} option_map[] = {

    /* yielding ints */
    OPT_CONST_INT(SQL_AUTOCOMMIT, OPT_LEVEL_DBC),
    OPT_CONST_INT(SQL_LOGIN_TIMEOUT, OPT_LEVEL_DBC),
    OPT_CONST_INT(SQL_NOSCAN, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_CONCURRENCY, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_QUERY_TIMEOUT, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_MAX_ROWS, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_MAX_LENGTH, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_ROWSET_SIZE, OPT_LEVEL_BOTH),
    OPT_CONST_INT(SQL_CURSOR_TYPE, OPT_LEVEL_BOTH),

    /* end of table */
    OPT_CONST_END
};

static VALUE
do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op)
{
    DBC *p = NULL;
    STMT *q = NULL;
    VALUE val, val2 = Qnil;
    SQLINTEGER v;
    char *msg;
    int level = isstmt ? OPT_LEVEL_STMT : OPT_LEVEL_DBC;

    if (op == -1) {
	rb_scan_args(argc, argv, "11", &val, &val2);
    } else {
	rb_scan_args(argc, argv, "01", &val);
    }
    if (isstmt) {
	Data_Get_Struct(self, STMT, q);
	if (q->dbc == Qnil) {
	    rb_raise(Cerror, "%s", set_err("Stale ODBC::Statement", 0));
	}
	if (q->hstmt == SQL_NULL_HSTMT) {
	    rb_raise(Cerror, "%s", set_err("No statement", 0));
	}
    } else {
	p = get_dbc(self);
	if (p->hdbc == SQL_NULL_HDBC) {
	    rb_raise(Cerror, "%s", set_err("No connection", 0));
	}
    }
    if (op == -1) {
	VALUE vstr;
	char *string;
	int i, op_found = 0;

	switch (TYPE(val)) {
	default:
	    vstr = rb_any_to_s(val);
	    string = STR2CSTR(vstr);
	    goto doString;
	case T_STRING:
	    string = STR2CSTR(val);
	doString:
	    for (i = 0; option_map[i].name != NULL; i++) {
		if (strcmp(string, option_map[i].name) == 0) {
		    op = option_map[i].option;
		    level = option_map[i].level;
		    op_found = 3;
		    break;
		}
	    }
	    break;
	case T_FLOAT:
	case T_BIGNUM:
	    op = (int) NUM2DBL(val);
	    goto doInt;
	case T_FIXNUM:
	    op = FIX2INT(val);
	doInt:
	    op_found = 1;
	    for (i = 0; option_map[i].name != NULL; i++) {
		if (op == option_map[i].option) {
		    level = option_map[i].level;
		    op_found = 2;
		    break;
		}
	    }
	    break;
	}
	if (!op_found) {
	    rb_raise(Cerror, "%s", set_err("Unknown option", 0));
	    return Qnil;
	}
	val = val2;
    }
    if ((isstmt && (!(level & OPT_LEVEL_STMT))) ||
	(!isstmt && (!(level & OPT_LEVEL_DBC)))) {
	rb_raise(Cerror, "%s",
		 set_err("Invalid option type for this level", 0));
	return Qnil;
    }
    if (val == Qnil) {
	if (p != NULL) {
	    if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
			   SQLGetConnectOption(p->hdbc, (SQLUSMALLINT) op,
					       (SQLPOINTER) &v),
			   &msg, "SQLGetConnectOption(%d)", op)) {
		rb_raise(Cerror, "%s", msg);
	    }
	} else {
	    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HSTMT, q->hstmt,
			   SQLGetStmtOption(q->hstmt, (SQLUSMALLINT) op,
					    (SQLPOINTER) &v),
			   &msg, "SQLGetStmtOption(%d)", op)) {
		rb_raise(Cerror, "%s", msg);
	    }
	}
    }
    switch (op) {
    case SQL_AUTOCOMMIT:
	if (val == Qnil) {
	    return v ? Qtrue : Qfalse;
	}
	v = (TYPE(val) == T_FIXNUM) ?
	    (FIX2INT(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF) :
	    (RTEST(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
	break;

    case SQL_NOSCAN:
	if (val == Qnil) {
	    return v ? Qtrue : Qfalse;
	}
	v = (TYPE(val) == T_FIXNUM) ?
	    (FIX2INT(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF) :
	    (RTEST(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF);
	break;

    case SQL_LOGIN_TIMEOUT:
    case SQL_CONCURRENCY:
    case SQL_QUERY_TIMEOUT:
    case SQL_MAX_ROWS:
    case SQL_MAX_LENGTH:
    case SQL_ROWSET_SIZE:
    case SQL_CURSOR_TYPE:
    default:
	if (val == Qnil) {
	    return rb_int2inum(v);
	}
	Check_Type(val, T_FIXNUM);
	v = FIX2INT(val);
	if (op == SQL_ROWSET_SIZE) {
	    rb_raise(Cerror, "%s", set_err("Read only attribute", 0));
	}
	break;
    }
    if (p != NULL) {
	if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		       SQLSetConnectOption(p->hdbc, (SQLUSMALLINT) op,
					   (SQLUINTEGER) v),
		       &msg, "SQLSetConnectOption(%d)", op)) {
	    rb_raise(Cerror, "%s", msg);
	}
    } else {
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		       SQLSetStmtOption(q->hstmt, (SQLUSMALLINT) op,
					(SQLUINTEGER) v),
		       &msg, "SQLSetStmtOption(%d)", op)) {
	    rb_raise(Cerror, "%s", msg);
	}
    }
    return Qnil;
}

static VALUE
dbc_autocommit(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_AUTOCOMMIT);
}

static VALUE
dbc_concurrency(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_CONCURRENCY);
}

static VALUE
dbc_maxrows(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_MAX_ROWS);
}

static VALUE
dbc_timeout(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_QUERY_TIMEOUT);
}

static VALUE
dbc_login_timeout(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_LOGIN_TIMEOUT);
}

static VALUE
dbc_maxlength(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_MAX_LENGTH);
}

static VALUE
dbc_rowsetsize(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_ROWSET_SIZE);
}

static VALUE
dbc_cursortype(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_CURSOR_TYPE);
}

static VALUE
dbc_noscan(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, SQL_NOSCAN);
}

static VALUE
dbc_getsetoption(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 0, -1);
}

static VALUE
stmt_concurrency(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_CONCURRENCY);
}

static VALUE
stmt_maxrows(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_MAX_ROWS);
}

static VALUE
stmt_timeout(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_QUERY_TIMEOUT);
}

static VALUE
stmt_maxlength(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_MAX_LENGTH);
}

static VALUE
stmt_rowsetsize(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_ROWSET_SIZE);
}

static VALUE
stmt_cursortype(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_CURSOR_TYPE);
}

static VALUE
stmt_noscan(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, SQL_NOSCAN);
}

static VALUE
stmt_getsetoption(int argc, VALUE *argv, VALUE self)
{
    return do_option(argc, argv, self, 1, -1);
}

/*
 *----------------------------------------------------------------------
 *
 *	Scan literal date/time/timestamp to TIMESTAMP_STRUCT.
 *
 *----------------------------------------------------------------------
 */

static int
scan_dtts(VALUE str, int do_d, int do_t, TIMESTAMP_STRUCT *ts)
{
    int yy = 0, mm = 0, dd = 0, hh = 0, mmm = 0, ss = 0, ff = 0, i;
    char c, *cstr = STR2CSTR(str);

    memset(ts, 0, sizeof (TIMESTAMP_STRUCT));
    if (((sscanf(cstr, "{ts '%d-%d-%d %d:%d:%d.%d' %c",
		 &yy, &mm, &dd, &hh, &mmm, &ss, &ff, &c) == 8) ||
	 (sscanf(cstr, "{ts '%d-%d-%d %d:%d:%d' %c",
		 &yy, &mm, &dd, &hh, &mmm, &ss, &c) == 7)) &&
	(c == '}')) {
	ts->year = yy;
	ts->month = mm;
	ts->day = dd;
	ts->hour = hh;
	ts->minute = mmm;
	ts->second = ss;
	ts->fraction = ff;
	return 1;
    }
    if (do_d &&
	(sscanf(cstr, "{d '%d-%d-%d' %c", &yy, &mm, &dd, &c) == 4) &&
	(c == '}')) {
	ts->year = yy;
	ts->month = mm;
	ts->day = dd;
	return 1;
    }
    if (do_t &&
	(sscanf(cstr, "{t '%d:%d:%d' %c", &hh, &mmm, &ss, &c) == 4) &&
	(c == '}')) {
	ts->hour = yy;
	ts->minute = mmm;
	ts->second = ss;
	return 1;
    }
    ff = ss = 0;
    i = sscanf(cstr, "%d-%d-%d %d:%d:%d%c%d",
	       &yy, &mm, &dd, &hh, &mmm, &ss, &c, &ff);
    if (i >= 5) {
	if ((i > 6) && (c != 0) && (strchr(". \t", c) == NULL)) {
	    goto next;
	}
	ts->year = yy;
	ts->month = mm;
	ts->day = dd;
	ts->hour = hh;
	ts->minute = mmm;
	ts->second = ss;
	ts->fraction = ff;
	return 1;
    }
next:
    ff = ss = 0;
    if (do_d && (sscanf(cstr, "%d-%d-%d", &yy, &mm, &dd) == 3)) {
	ts->year = yy;
	ts->month = mm;
	ts->day = dd;
	return 1;
    }
    if (do_t && (sscanf(cstr, "%d:%d:%d", &hh, &mmm, &ss) == 3)) {
	ts->hour = hh;
	ts->minute = mmm;
	ts->second = ss;
	return 1;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 *	Date methods.
 *
 *----------------------------------------------------------------------
 */

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
date_alloc(VALUE self)
{
    DATE_STRUCT *date;
    VALUE obj = Data_Make_Struct(self, DATE_STRUCT, 0, xfree, date);

    memset(date, 0, sizeof (*date));
    return obj;
}
#else
static VALUE
date_new(int argc, VALUE *argv, VALUE self)
{
    DATE_STRUCT *date;
    VALUE obj = Data_Make_Struct(self, DATE_STRUCT, 0, xfree, date);

    rb_obj_call_init(obj, argc, argv);
    return obj;
}
#endif

static VALUE
date_load1(VALUE self, VALUE str, int load)
{
    TIMESTAMP_STRUCT tss;

    if (scan_dtts(str, 1, 0, &tss)) {
	DATE_STRUCT *date;
	VALUE obj;

	if (load) {
	    obj = Data_Make_Struct(self, DATE_STRUCT, 0, xfree, date);
	} else {
	    obj = self;
	    Data_Get_Struct(self, DATE_STRUCT, date);
	}
	date->year = tss.year;
	date->month = tss.month;
	date->day = tss.day;
	return obj;
    }
    if (load > 0) {
	rb_raise(rb_eTypeError, "marshaled ODBC::Date format error");
    }
    return Qnil;
}

static VALUE
date_load(VALUE self, VALUE str)
{
    return date_load1(self, str, 1);
}

static VALUE
date_init(int argc, VALUE *argv, VALUE self)
{
    DATE_STRUCT *date;
    VALUE d, m, y;

    rb_scan_args(argc, argv, "03", &y, &m, &d);
    if (rb_obj_is_kind_of(y, Cdate) == Qtrue) {
	DATE_STRUCT *date2;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	Data_Get_Struct(self, DATE_STRUCT, date);
	Data_Get_Struct(y, DATE_STRUCT, date2);
	*date = *date2;
	return self;
    }
    if (rb_obj_is_kind_of(y, Ctimestamp) == Qtrue) {
	TIMESTAMP_STRUCT *ts;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	Data_Get_Struct(self, DATE_STRUCT, date);
	Data_Get_Struct(y, TIMESTAMP_STRUCT, ts);
	date->year  = ts->year;
	date->month = ts->month;
	date->day   = ts->day;
	return self;
    }
    if (rb_obj_is_kind_of(y, rb_cTime) == Qtrue) {
	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	d = FUNCALL_NOARGS(y, IDday);
	m = FUNCALL_NOARGS(y, IDmonth);
	y = FUNCALL_NOARGS(y, IDyear);
    } else if (rb_obj_is_kind_of(y, rb_cDate) == Qtrue) {
	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	d = FUNCALL_NOARGS(y, IDmday);
	m = FUNCALL_NOARGS(y, IDmonth);
	y = FUNCALL_NOARGS(y, IDyear);
    } else if ((argc == 1) && (rb_obj_is_kind_of(y, rb_cString) == Qtrue)) {
	if (date_load1(self, y, 0) != Qnil) {
	    return self;
	}
    }
    Data_Get_Struct(self, DATE_STRUCT, date);
    date->year  = (y == Qnil) ? 0 : NUM2INT(y);
    date->month = (m == Qnil) ? 0 : NUM2INT(m);
    date->day   = (d == Qnil) ? 0 : NUM2INT(d);
    return self;
}

static VALUE
date_clone(VALUE self)
{
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    VALUE obj = rb_obj_alloc(CLASS_OF(self));
    DATE_STRUCT *date1, *date2;

    Data_Get_Struct(self, DATE_STRUCT, date1);
    Data_Get_Struct(obj, DATE_STRUCT, date2);
    *date2 = *date1;
    return obj;
#else
    return date_new(1, &self, CLASS_OF(self));
#endif
}

static VALUE
date_to_s(VALUE self)
{
    DATE_STRUCT *date;
    char buf[128];

    Data_Get_Struct(self, DATE_STRUCT, date);
    sprintf(buf, "%04d-%02d-%02d", date->year, date->month, date->day);
    return rb_str_new2(buf);
}

static VALUE
date_dump(VALUE self, VALUE depth)
{
    return date_to_s(self);
}

static VALUE
date_inspect(VALUE self)
{
    VALUE s = rb_str_new2("#<ODBC::Date: ");

    s = rb_str_append(s, date_to_s(self));
    return rb_str_append(s, rb_str_new2(">"));
}

static VALUE
date_year(int argc, VALUE *argv, VALUE self)
{
    DATE_STRUCT *date;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, DATE_STRUCT, date);
    if (v == Qnil) {
	return INT2NUM(date->year);
    }
    date->year = NUM2INT(v);
    return self;
}

static VALUE
date_month(int argc, VALUE *argv, VALUE self)
{
    DATE_STRUCT *date;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, DATE_STRUCT, date);
    if (v == Qnil) {
	return INT2NUM(date->month);
    }
    date->month = NUM2INT(v);
    return self;
}

static VALUE
date_day(int argc, VALUE *argv, VALUE self)
{
    DATE_STRUCT *date;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, DATE_STRUCT, date);
    if (v == Qnil) {
	return INT2NUM(date->day);
    }
    date->day = NUM2INT(v);
    return self;
}

static VALUE
date_cmp(VALUE self, VALUE date)
{
    DATE_STRUCT *date1, *date2;

    if (rb_obj_is_kind_of(date, Cdate) != Qtrue) {
	rb_raise(rb_eTypeError, "need ODBC::Date as argument");
    }
    Data_Get_Struct(self, DATE_STRUCT, date1);
    Data_Get_Struct(date, DATE_STRUCT, date2);
    if (date1->year < date2->year) {
	return INT2FIX(-1);
    }
    if (date1->year == date2->year) {
	if (date1->month < date2->month) {
	    return INT2FIX(-1);
	}
	if (date1->month == date2->month) {
	    if (date1->day < date2->day) {
		return INT2FIX(-1);
	    }
	    if (date1->day == date2->day) {
		return INT2FIX(0);
	    }
	}
    }
    return INT2FIX(1);
}

/*
 *----------------------------------------------------------------------
 *
 *	Time methods.
 *
 *----------------------------------------------------------------------
 */

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
time_alloc(VALUE self)
{
    TIME_STRUCT *time;
    VALUE obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time);

    memset(time, 0, sizeof (*time));
    return obj;
}
#else
static VALUE
time_new(int argc, VALUE *argv, VALUE self)
{
    TIME_STRUCT *time;
    VALUE obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time);

    rb_obj_call_init(obj, argc, argv);
    return obj;
}
#endif

static VALUE
time_load1(VALUE self, VALUE str, int load)
{
    TIMESTAMP_STRUCT tss;

    if (scan_dtts(str, 0, 1, &tss)) {
	TIME_STRUCT *time;
	VALUE obj;

	if (load) {
	    obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time);
	} else {
	    obj = self;
	    Data_Get_Struct(self, TIME_STRUCT, time);
	}
	time->hour = tss.hour;
	time->minute = tss.minute;
	time->second = tss.second;
	return obj;
    }
    if (load > 0) {
	rb_raise(rb_eTypeError, "marshaled ODBC::Time format error");
    }
    return Qnil;
}

static VALUE
time_load(VALUE self, VALUE str)
{
    return time_load1(self, str, 1);
}

static VALUE
time_init(int argc, VALUE *argv, VALUE self)
{
    TIME_STRUCT *time;
    VALUE h, m, s;

    rb_scan_args(argc, argv, "03", &h, &m, &s);
    if (rb_obj_is_kind_of(h, Ctime) == Qtrue) {
	TIME_STRUCT *time2;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	Data_Get_Struct(self, TIME_STRUCT, time);
	Data_Get_Struct(h, TIME_STRUCT, time2);
	*time = *time2;
	return self;
    }
    if (rb_obj_is_kind_of(h, Ctimestamp) == Qtrue) {
	TIMESTAMP_STRUCT *ts;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	Data_Get_Struct(self, TIME_STRUCT, time);
	Data_Get_Struct(h, TIMESTAMP_STRUCT, ts);
	time->hour   = ts->hour;
	time->minute = ts->minute;
	time->second = ts->second;
	return self;
    }
    if (rb_obj_is_kind_of(h, rb_cTime) == Qtrue) {
	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	s = FUNCALL_NOARGS(h, IDsec);
	m = FUNCALL_NOARGS(h, IDmin);
	h = FUNCALL_NOARGS(h, IDhour);
    } else if ((argc == 1) && (rb_obj_is_kind_of(h, rb_cString) == Qtrue)) {
	if (time_load1(self, h, 0) != Qnil) {
	    return self;
	}
    }
    Data_Get_Struct(self, TIME_STRUCT, time);
    time->hour   = (h == Qnil) ? 0 : NUM2INT(h);
    time->minute = (m == Qnil) ? 0 : NUM2INT(m);
    time->second = (s == Qnil) ? 0 : NUM2INT(s);
    return self;
}

static VALUE
time_clone(VALUE self)
{
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    VALUE obj = rb_obj_alloc(CLASS_OF(self));
    TIME_STRUCT *time1, *time2;

    Data_Get_Struct(self, TIME_STRUCT, time1);
    Data_Get_Struct(obj, TIME_STRUCT, time2);
    *time2 = *time1;
    return obj;
#else
    return time_new(1, &self, CLASS_OF(self));
#endif
}

static VALUE
time_to_s(VALUE self)
{
    TIME_STRUCT *time;
    char buf[128];

    Data_Get_Struct(self, TIME_STRUCT, time);
    sprintf(buf, "%02d:%02d:%02d", time->hour, time->minute, time->second);
    return rb_str_new2(buf);
}

static VALUE
time_dump(VALUE self, VALUE depth)
{
    return time_to_s(self);
}

static VALUE
time_inspect(VALUE self)
{
    VALUE s = rb_str_new2("#<ODBC::Time: ");

    s = rb_str_append(s, time_to_s(self));
    return rb_str_append(s, rb_str_new2(">"));
}

static VALUE
time_hour(int argc, VALUE *argv, VALUE self)
{
    TIME_STRUCT *time;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIME_STRUCT, time);
    if (v == Qnil) {
	return INT2NUM(time->hour);
    }
    time->hour = NUM2INT(v);
    return self;
}

static VALUE
time_min(int argc, VALUE *argv, VALUE self)
{
    TIME_STRUCT *time;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIME_STRUCT, time);
    if (v == Qnil) {
	return INT2NUM(time->minute);
    }
    time->minute = NUM2INT(v);
    return self;
}

static VALUE
time_sec(int argc, VALUE *argv, VALUE self)
{
    TIME_STRUCT *time;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIME_STRUCT, time);
    if (v == Qnil) {
	return INT2NUM(time->second);
    }
    time->second = NUM2INT(v);
    return self;
}

static VALUE
time_cmp(VALUE self, VALUE time)
{
    TIME_STRUCT *time1, *time2;

    if (rb_obj_is_kind_of(time, Ctime) != Qtrue) {
	rb_raise(rb_eTypeError, "need ODBC::Time as argument");
    }
    Data_Get_Struct(self, TIME_STRUCT, time1);
    Data_Get_Struct(time, TIME_STRUCT, time2);
    if (time1->hour < time2->hour) {
	return INT2FIX(-1);
    }
    if (time1->hour == time2->hour) {
	if (time1->minute < time2->minute) {
	    return INT2FIX(-1);
	}
	if (time1->minute == time2->minute) {
	    if (time1->second < time2->second) {
		return INT2FIX(-1);
	    }
	    if (time1->second == time2->second) {
		return INT2FIX(0);
	    }
	}
    }
    return INT2FIX(1);
}

/*
 *----------------------------------------------------------------------
 *
 *	TimeStamp methods.
 *
 *----------------------------------------------------------------------
 */

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
static VALUE
timestamp_alloc(VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE obj = Data_Make_Struct(self, TIMESTAMP_STRUCT, 0, xfree, ts);

    memset(ts, 0, sizeof (*ts));
    return obj;
}
#else
static VALUE
timestamp_new(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE obj = Data_Make_Struct(self, TIMESTAMP_STRUCT, 0, xfree, ts);

    rb_obj_call_init(obj, argc, argv);
    return obj;
}
#endif

static VALUE
timestamp_load1(VALUE self, VALUE str, int load)
{
    TIMESTAMP_STRUCT tss;

    if (scan_dtts(str, !load, !load, &tss)) {
	TIMESTAMP_STRUCT *ts;
	VALUE obj;

	if (load) {
	    obj = Data_Make_Struct(self, TIMESTAMP_STRUCT, 0, xfree, ts);
	} else {
	    obj = self;
	    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
	}
	*ts = tss;
	return obj;
    }
    if (load > 0) {
	rb_raise(rb_eTypeError, "marshaled ODBC::TimeStamp format error");
    }
    return Qnil;
}

static VALUE
timestamp_load(VALUE self, VALUE str)
{
    return timestamp_load1(self, str, 1);
}

static VALUE
timestamp_init(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE d, m, y, hh, mm, ss, f;

    rb_scan_args(argc, argv, "07", &y, &m, &d, &hh, &mm, &ss, &f);
    if (rb_obj_is_kind_of(y, Ctimestamp) == Qtrue) {
	TIMESTAMP_STRUCT *ts2;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
	Data_Get_Struct(y, TIMESTAMP_STRUCT, ts2);
	*ts = *ts2;
	return self;
    }
    if (rb_obj_is_kind_of(y, Cdate) == Qtrue) {
	DATE_STRUCT *date;

	if (argc > 1) {
	    if (argc > 2) {
		rb_raise(rb_eArgError, "wrong # arguments");
	    }
	    if (rb_obj_is_kind_of(m, Ctime) == Qtrue) {
		TIME_STRUCT *time;

		Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
		Data_Get_Struct(m, TIME_STRUCT, time);
		ts->hour   = time->hour;
		ts->minute = time->minute;
		ts->second = time->second;
	    } else {
		rb_raise(rb_eArgError, "need ODBC::Time argument");
	    }
	}
	Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
	Data_Get_Struct(y, DATE_STRUCT, date);
	ts->year = date->year;
	ts->year = date->year;
	ts->year = date->year;
	ts->fraction = 0;
	return self;
    }
    if (rb_obj_is_kind_of(y, rb_cTime) == Qtrue) {
	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	f  = FUNCALL_NOARGS(y, IDusec);
	ss = FUNCALL_NOARGS(y, IDsec);
	mm = FUNCALL_NOARGS(y, IDmin);
	hh = FUNCALL_NOARGS(y, IDhour);
	d  = FUNCALL_NOARGS(y, IDday);
	m  = FUNCALL_NOARGS(y, IDmonth);
	y  = FUNCALL_NOARGS(y, IDyear);
	f = INT2NUM(NUM2INT(f) * 1000);
    } else if (rb_obj_is_kind_of(y, rb_cDate) == Qtrue) {
	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	f  = INT2FIX(0);
	ss = INT2FIX(0);
	mm = INT2FIX(0);
	hh = INT2FIX(0);
	d  = FUNCALL_NOARGS(y, IDmday);
	m  = FUNCALL_NOARGS(y, IDmonth);
	y  = FUNCALL_NOARGS(y, IDyear);
    } else if ((argc == 1) && (rb_obj_is_kind_of(y, rb_cString) == Qtrue)) {
	if (timestamp_load1(self, y, 0) != Qnil) {
	    return self;
	}
    }
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    ts->year     = (y  == Qnil) ? 0 : NUM2INT(y);
    ts->month    = (m  == Qnil) ? 0 : NUM2INT(m);
    ts->day      = (d  == Qnil) ? 0 : NUM2INT(d);
    ts->hour     = (hh == Qnil) ? 0 : NUM2INT(hh);
    ts->minute   = (mm == Qnil) ? 0 : NUM2INT(mm);
    ts->second   = (ss == Qnil) ? 0 : NUM2INT(ss);
    ts->fraction = (f  == Qnil) ? 0 : NUM2INT(f);
    return self;
}

static VALUE
timestamp_clone(VALUE self)
{
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    VALUE obj = rb_obj_alloc(CLASS_OF(self));
    TIMESTAMP_STRUCT *ts1, *ts2;

    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts1);
    Data_Get_Struct(obj, TIMESTAMP_STRUCT, ts2);
    *ts2 = *ts1;
    return obj;
#else
    return timestamp_new(1, &self, CLASS_OF(self));
#endif
}

static VALUE
timestamp_to_s(VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    char buf[256];

    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d %u",
	    ts->year, ts->month, ts->day,
	    ts->hour, ts->minute, ts->second,
	    (unsigned int) ts->fraction);
    return rb_str_new2(buf);
}

static VALUE
timestamp_dump(VALUE self, VALUE depth)
{
    return timestamp_to_s(self);
}

static VALUE
timestamp_inspect(VALUE self)
{
    VALUE s = rb_str_new2("#<ODBC::TimeStamp: \"");

    s = rb_str_append(s, timestamp_to_s(self));
    return rb_str_append(s, rb_str_new2("\">"));
}

static VALUE
timestamp_year(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->year);
    }
    ts->year = NUM2INT(v);
    return self;
}

static VALUE
timestamp_month(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->month);
    }
    ts->month = NUM2INT(v);
    return self;
}

static VALUE
timestamp_day(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->day);
    }
    ts->day = NUM2INT(v);
    return self;
}

static VALUE
timestamp_hour(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->hour);
    }
    ts->hour = NUM2INT(v);
    return self;
}

static VALUE
timestamp_min(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->minute);
    }
    ts->minute = NUM2INT(v);
    return self;
}

static VALUE
timestamp_sec(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->second);
    }
    ts->second = NUM2INT(v);
    return self;
}

static VALUE
timestamp_fraction(int argc, VALUE *argv, VALUE self)
{
    TIMESTAMP_STRUCT *ts;
    VALUE v;

    rb_scan_args(argc, argv, "01", &v);
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts);
    if (v == Qnil) {
	return INT2NUM(ts->fraction);
    }
    ts->fraction = NUM2INT(v);
    return self;
}

static VALUE
timestamp_cmp(VALUE self, VALUE timestamp)
{
    TIMESTAMP_STRUCT *ts1, *ts2;

    if (rb_obj_is_kind_of(timestamp, Ctimestamp) != Qtrue) {
	rb_raise(rb_eTypeError, "need ODBC::TimeStamp as argument");
    }
    Data_Get_Struct(self, TIMESTAMP_STRUCT, ts1);
    Data_Get_Struct(timestamp, TIMESTAMP_STRUCT, ts2);
    if (ts1->year < ts2->year) {
	return INT2FIX(-1);
    }
    if (ts1->year == ts2->year) {
	if (ts1->month < ts2->month) {
	    return INT2FIX(-1);
	}
	if (ts1->month == ts2->month) {
	    if (ts1->day < ts2->day) {
		return INT2FIX(-1);
	    }
	    if (ts1->day == ts2->day) {
		if (ts1->hour < ts2->hour) {
		    return INT2FIX(-1);
		}
		if (ts1->hour == ts2->hour) {
		    if (ts1->minute < ts2->minute) {
			return INT2FIX(-1);
		    }
		    if (ts1->minute == ts2->minute) {
			if (ts1->second < ts2->second) {
			    return INT2FIX(-1);
			}
			if (ts1->second == ts2->second) {
			    if (ts1->fraction < ts2->fraction) {
				return INT2FIX(-1);
			    }
			    if (ts1->fraction == ts2->fraction) {
				return INT2FIX(0);
			    }
			}
		    }
		}
	    }
	}
    }
    return INT2FIX(1);
}

/*
 *----------------------------------------------------------------------
 *
 *	Statement methods.
 *
 *----------------------------------------------------------------------
 */

static VALUE
stmt_drop(VALUE self)
{
    STMT *q;

    Data_Get_Struct(self, STMT, q);
    if (q->hstmt != SQL_NULL_HSTMT) {
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		SQLFreeStmt(q->hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
	q->hstmt = SQL_NULL_HSTMT;
	unlink_stmt(q);
    }
    free_stmt_sub(q, 1);
    return self;
}

static VALUE
stmt_close(VALUE self)
{
    STMT *q;

    Data_Get_Struct(self, STMT, q);
    if (q->hstmt != SQL_NULL_HSTMT) {
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		SQLFreeStmt(q->hstmt, SQL_CLOSE), "SQLFreeStmt(SQL_CLOSE)");
    }
    free_stmt_sub(q, 1);
    return self;
}

static VALUE
stmt_cancel(VALUE self)
{
    STMT *q;
    char *msg;

    Data_Get_Struct(self, STMT, q);
    if (q->hstmt != SQL_NULL_HSTMT) {
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		       SQLCancel(q->hstmt), &msg, "SQLCancel")) {
	    rb_raise(Cerror, "%s", msg);
	}
    }
    return self;
}

static void
check_ncols(STMT *q)
{
    if ((q->hstmt != SQL_NULL_HSTMT) && (q->ncols <= 0) &&
	(q->coltypes == NULL)) {
	COLTYPE *coltypes = NULL;
	SQLSMALLINT cols = 0;

	if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		      SQLNumResultCols(q->hstmt, &cols), NULL,
		      "SQLNumResultCols")
	    && (cols > 0)) {
	    coltypes = make_coltypes(q->hstmt, cols, NULL);
	    if (coltypes != NULL) {
		q->ncols = cols;
		q->coltypes = coltypes;
	    }
	}
    }
}

static VALUE
stmt_ncols(VALUE self)
{
    STMT *q;

    Data_Get_Struct(self, STMT, q);
    check_ncols(q);
    return INT2FIX(q->ncols);
}

static VALUE
stmt_nrows(VALUE self)
{
    STMT *q;
    SQLLEN rows = -1;
    char *msg;

    Data_Get_Struct(self, STMT, q);
    if ((q->hstmt != SQL_NULL_HSTMT) &&
	(!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		    SQLRowCount(q->hstmt, &rows), &msg, "SQLRowCount"))) {
	rb_raise(Cerror, "%s", msg);
    }
    return INT2NUM(rows);
}

static VALUE
stmt_nparams(VALUE self)
{
    STMT *q;

    Data_Get_Struct(self, STMT, q);
    return INT2FIX(q->nump);
}

static int
param_num_check(STMT *q, VALUE pnum, int mkparaminfo, int needout)
{
    int vnum;

    Check_Type(pnum, T_FIXNUM);
    vnum = NUM2INT(pnum);
    if (mkparaminfo && (q->paraminfo == NULL)) {
	char *msg = NULL;
	SQLSMALLINT nump = 0;

	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		       SQLNumParams(q->hstmt, &nump), NULL, "SQLNumParams")) {
	    nump = 0;
	}
	if (nump > 0) {
	    PARAMINFO *paraminfo = make_paraminfo(q->hstmt, nump, &msg);

	    if (paraminfo == NULL) {
		rb_raise(Cerror, "%s", msg);
	    }
	    q->paraminfo = paraminfo;
	    if (q->paraminfo != NULL) {
		q->nump = nump;
	    }
	}
    }
    if ((q->paraminfo == NULL) || (vnum < 0) || (vnum >= q->nump)) {
	rb_raise(rb_eArgError, "parameter number out of bounds");
    }
    if (needout) {
	if ((q->paraminfo[vnum].iotype != SQL_PARAM_OUTPUT) &&
	    (q->paraminfo[vnum].iotype != SQL_PARAM_INPUT_OUTPUT)) {
	    rb_raise(Cerror, "not an output parameter");
	}
    }
    return vnum;
}

static VALUE
stmt_param_type(int argc, VALUE *argv, VALUE self)
{
    VALUE pnum, ptype, pcoldef, pscale;
    int vnum;
    STMT *q;

    rb_scan_args(argc, argv, "13", &pnum, &ptype, &pcoldef, &pscale);
    Data_Get_Struct(self, STMT, q);
    vnum = param_num_check(q, pnum, 1, 0);
    if (argc > 1) {
	int vtype, vcoldef, vscale;

	Check_Type(ptype, T_FIXNUM);
	vtype = NUM2INT(ptype);
	if (argc > 2) {
	    Check_Type(pcoldef, T_FIXNUM);
	    vcoldef = NUM2INT(pcoldef);
	    if (argc > 3) {
		Check_Type(pscale, T_FIXNUM);
		vscale = NUM2INT(pscale);
		q->paraminfo[vnum].scale = vscale;
	    }
	    q->paraminfo[vnum].coldef = vcoldef;
	}
	q->paraminfo[vnum].type = vtype;
	q->paraminfo[vnum].override = 1;
	return Qnil;
    }
    return INT2NUM(q->paraminfo[vnum].type);
}

static VALUE
stmt_param_iotype(int argc, VALUE *argv, VALUE self)
{
    VALUE pnum, piotype;
    int vnum, viotype;
    STMT *q;

    rb_scan_args(argc, argv, "11", &pnum, &piotype);
    Data_Get_Struct(self, STMT, q);
    vnum = param_num_check(q, pnum, 1, 0);
    if (argc > 1) {
	Check_Type(piotype, T_FIXNUM);
	viotype = NUM2INT(piotype);
	switch (viotype) {
	case SQL_PARAM_INPUT:
	case SQL_PARAM_INPUT_OUTPUT:
	case SQL_PARAM_OUTPUT:
	    q->paraminfo[vnum].iotype = viotype;
	    break;
	}
    }
    return INT2NUM(q->paraminfo[vnum].iotype);
}

static VALUE
stmt_param_output_value(int argc, VALUE *argv, VALUE self)
{
    VALUE pnum, v;
    int vnum;
    STMT *q;

    rb_scan_args(argc, argv, "10", &pnum);
    Data_Get_Struct(self, STMT, q);
    vnum = param_num_check(q, pnum, 0, 1);
    v = Qnil;
    if (q->paraminfo[vnum].rlen == SQL_NULL_DATA) {
	return v;
    }
    if (q->paraminfo[vnum].outbuf == NULL) {
	rb_raise(Cerror, "no output value available");
    }
    switch (q->paraminfo[vnum].ctype) {
    case SQL_C_LONG:
	v = INT2NUM(*((SQLINTEGER *) q->paraminfo[vnum].outbuf));
	break;
    case SQL_C_DOUBLE:
	v = rb_float_new(*((double *) q->paraminfo[vnum].outbuf));
	break;
    case SQL_C_DATE:
	{
	    DATE_STRUCT *date;

	    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
		const char *p;
		char buffer[128];
		VALUE d;

		date = (DATE_STRUCT *) q->paraminfo[vnum].outbuf;
		p = (q->dbcp->gmtime == Qtrue) ? "+00:00" : "";
		sprintf(buffer, "%d-%d-%dT00:00:00%s",
			date->year, date->month, date->day, p);
		d = rb_str_new2(buffer);
		v = rb_funcall(rb_cDate, IDparse, 1, d);
	    } else {
		v = Data_Make_Struct(Cdate, DATE_STRUCT, 0, xfree, date);
		*date = *((DATE_STRUCT *) q->paraminfo[vnum].outbuf);
	    }
	}
	break;
    case SQL_C_TIME:
	{
	    TIME_STRUCT *time;

	    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
		VALUE now, frac;

		time = (TIME_STRUCT *) q->paraminfo[vnum].outbuf;
		frac = rb_float_new(0.0);
		now = FUNCALL_NOARGS(rb_cTime, IDnow);
		v = rb_funcall(rb_cTime,
			       (q->dbcp->gmtime == Qtrue) ? IDutc : IDlocal,
			       7,
			       FUNCALL_NOARGS(now, IDyear),
			       FUNCALL_NOARGS(now, IDmonth),
			       FUNCALL_NOARGS(now, IDday),
			       INT2NUM(time->hour),
			       INT2NUM(time->minute),
			       INT2NUM(time->second),
			       frac);
	    } else {
		v = Data_Make_Struct(Ctime, TIME_STRUCT, 0, xfree, time);
		*time = *((TIME_STRUCT *) q->paraminfo[vnum].outbuf);
	    }
	}
	break;
    case SQL_C_TIMESTAMP:
	{
	    TIMESTAMP_STRUCT *ts;

	    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
		VALUE frac;

		ts = (TIMESTAMP_STRUCT *) q->paraminfo[vnum].outbuf;
		frac = rb_float_new((double) 1.0e-3 * ts->fraction);
		v = rb_funcall(rb_cTime,
			       (q->dbcp->gmtime == Qtrue) ? IDutc : IDlocal,
			       7,
			       INT2NUM(ts->year),
			       INT2NUM(ts->month),
			       INT2NUM(ts->day),
			       INT2NUM(ts->hour),
			       INT2NUM(ts->minute),
			       INT2NUM(ts->second),
			       frac);
	    } else {
		v = Data_Make_Struct(Ctimestamp, TIMESTAMP_STRUCT,
				     0, xfree, ts);
		*ts = *((TIMESTAMP_STRUCT *) q->paraminfo[vnum].outbuf);
	    }
	}
	break;
#ifdef UNICODE
    case SQL_C_WCHAR:
	v = uc_tainted_str_new((SQLWCHAR *) q->paraminfo[vnum].outbuf,
			       q->paraminfo[vnum].rlen / sizeof (SQLWCHAR));
	break;
#endif
    case SQL_C_CHAR:
	v = RB_TAINTED_STR_NEW(q->paraminfo[vnum].outbuf,
			       q->paraminfo[vnum].rlen);
	break;
    }
    return v;
}

static VALUE
stmt_param_output_size(int argc, VALUE *argv, VALUE self)
{
    VALUE pnum, psize;
    int vnum, vsize;
    STMT *q;

    rb_scan_args(argc, argv, "11", &pnum, &psize);
    Data_Get_Struct(self, STMT, q);
    vnum = param_num_check(q, pnum, 0, 1);
    if (argc > 1) {
	Check_Type(psize, T_FIXNUM);
	vsize = NUM2INT(psize);
	if ((vsize > 0) && (vsize < (int) (4 * sizeof (double)))) {
	    vsize = 4 * sizeof (double);
	}
	q->paraminfo[vnum].outsize = (vsize > 0) ? vsize : 0;
    }
    return INT2NUM(q->paraminfo[vnum].outsize);
}

static VALUE
stmt_param_output_type(int argc, VALUE *argv, VALUE self)
{
    VALUE pnum, ptype;
    int vnum, vtype;
    STMT *q;

    rb_scan_args(argc, argv, "11", &pnum, &ptype);
    Data_Get_Struct(self, STMT, q);
    vnum = param_num_check(q, pnum, 0, 1);
    if (argc > 1) {
	Check_Type(ptype, T_FIXNUM);
	vtype = NUM2INT(ptype);
	q->paraminfo[vnum].outtype = vtype;
    }
    return INT2NUM(q->paraminfo[vnum].outtype);
}

static VALUE
stmt_cursorname(int argc, VALUE *argv, VALUE self)
{
    VALUE cn = Qnil;
    STMT *q;
#ifdef UNICODE
    SQLWCHAR cname[SQL_MAX_MESSAGE_LENGTH];
    SQLWCHAR *cp;
#else
    SQLCHAR cname[SQL_MAX_MESSAGE_LENGTH];
    SQLCHAR *cp;
#endif
    char *msg;
    SQLSMALLINT cnLen = 0;

    rb_scan_args(argc, argv, "01", &cn);
    Data_Get_Struct(self, STMT, q);
    if (cn == Qnil) {
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		       SQLGetCursorName(q->hstmt, (SQLTCHAR *) cname,
					(SQLSMALLINT) sizeof (cname), &cnLen),
		       &msg, "SQLGetCursorName")) {
	    rb_raise(Cerror, "%s", msg);
	}
#ifdef UNICODE
	cnLen = (cnLen == 0) ? (SQLSMALLINT) uc_strlen(cname) :
	    (SQLSMALLINT) (cnLen / sizeof (SQLWCHAR));
	return uc_tainted_str_new(cname, cnLen);
#else
	cnLen = (cnLen == 0) ? (SQLSMALLINT) strlen((char *) cname) : cnLen;
	return RB_TAINTED_STR_NEW((char *) cname, cnLen);
#endif
    }
    if (TYPE(cn) != T_STRING) {
	cn = rb_any_to_s(cn);
    }
#ifdef UNICODE
#ifdef USE_RB_ENC
    cn = rb_funcall(cn, IDencode, 1, rb_encv);
#endif
    cp = uc_from_utf((unsigned char *) STR2CSTR(cn), -1);
    if (cp == NULL) {
	rb_raise(Cerror, "%s", set_err("Out of memory", 0));
    }
#else
    cp = (SQLCHAR *) STR2CSTR(cn);
#endif
    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		   SQLSetCursorName(q->hstmt, cp, SQL_NTS),
		   &msg, "SQLSetCursorName")) {
#ifdef UNICODE
	uc_free(cp);
#endif
	rb_raise(Cerror, "%s", msg);
    }
#ifdef UNICODE
    uc_free(cp);
#endif
    return cn;
}

static VALUE
stmt_column(int argc, VALUE *argv, VALUE self)
{
    STMT *q;
    VALUE col;
    int use_scn = 0;

    rb_scan_args(argc, argv, "1", &col);
    Check_Type(col, T_FIXNUM);
    Data_Get_Struct(self, STMT, q);
    check_ncols(q);
    if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) {
	use_scn = 1;
    }
    return make_column(q->hstmt, FIX2INT(col), q->upc, use_scn);
}

static VALUE
stmt_columns(int argc, VALUE *argv, VALUE self)
{
    STMT *q;
    int i, use_scn = 0;
    VALUE res, as_ary = Qfalse;

    rb_scan_args(argc, argv, "01", &as_ary);
    Data_Get_Struct(self, STMT, q);
    check_ncols(q);
    if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) {
	use_scn = 1;
    }
    if (rb_block_given_p()) {
	for (i = 0; i < q->ncols; i++) {
	    rb_yield(make_column(q->hstmt, i, q->upc, use_scn));
	}
	return self;
    }
    if (RTEST(as_ary)) {
	res = rb_ary_new2(q->ncols);
    } else {
	res = rb_hash_new();
    }
    for (i = 0; i < q->ncols; i++) {
	VALUE obj;

	obj = make_column(q->hstmt, i, q->upc, use_scn);
	if (RTEST(as_ary)) {
	    rb_ary_store(res, i, obj);
	} else {
	    VALUE name = rb_iv_get(obj, "@name");

	    if (rb_funcall(res, IDkeyp, 1, name) == Qtrue) {
		char buf[32];

		sprintf(buf, "#%d", i);
		name = rb_str_dup(name);
		name = RB_OBJ_TAINT(rb_str_cat2(name, buf));
	    }
	    rb_hash_aset(res, name, obj);
	}
    }
    return res;
}

static VALUE
stmt_param(int argc, VALUE *argv, VALUE self)
{
    STMT *q;
    VALUE par;
    int i;

    rb_scan_args(argc, argv, "1", &par);
    Check_Type(par, T_FIXNUM);
    Data_Get_Struct(self, STMT, q);
    i = FIX2INT(par);
    if ((i < 0) || (i >= q->nump)) {
	rb_raise(Cerror, "%s", set_err("Parameter out of bounds", 0));
    }
    return make_param(q, i);
}

static VALUE
stmt_params(VALUE self)
{
    STMT *q;
    int i;
    VALUE res;

    Data_Get_Struct(self, STMT, q);
    if (rb_block_given_p()) {
	for (i = 0; i < q->nump; i++) {
	    rb_yield(make_param(q, i));
	}
	return self;
    }
    res = rb_ary_new2(q->nump);
    for (i = 0; i < q->nump; i++) {
	VALUE obj;

	obj = make_param(q, i);
	rb_ary_store(res, i, obj);
    }
    return res;
}

static VALUE
do_fetch(STMT *q, int mode)
{
    int i, use_scn = 0, offc;
    char **bufs, *msg;
    VALUE res;

    if (q->ncols <= 0) {
	rb_raise(Cerror, "%s", set_err("No columns in result set", 0));
    }
    if (gc_threshold > 0 && ++q->fetchc >= gc_threshold) {
	q->fetchc = 0;
	start_gc();
    }
    bufs = q->dbufs;
    if (bufs == NULL) {
	int need = sizeof (char *) * q->ncols, needp;
	char *p;

	need = LEN_ALIGN(need);
	needp = need;
	for (i = 0; i < q->ncols; i++) {
	    if (q->coltypes[i].size != SQL_NO_TOTAL) {
		need += LEN_ALIGN(q->coltypes[i].size);
	    }
	}
	p = ALLOC_N(char, need);
	if (p == NULL) {
	    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	}
	q->dbufs = bufs = (char **) p;
	p += needp;
	for (i = 0; i < q->ncols; i++) {
	    int len = q->coltypes[i].size;

	    if (len == SQL_NO_TOTAL) {
		bufs[i] = NULL;
	    } else {
		bufs[i] = p;
		p += LEN_ALIGN(len);
	    }
	}
    }
    if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) {
	use_scn = 1;
    }
    switch (mode & DOFETCH_MODES) {
    case DOFETCH_HASH:
    case DOFETCH_HASH2:
    case DOFETCH_HASHK:
    case DOFETCH_HASHK2:
	if (q->colnames == NULL) {
	    int need = sizeof (char *) * 4 * q->ncols + sizeof (char *);
	    int max_len[2] = { 0, 0 };
	    char **na, *p;
#ifdef UNICODE
	    SQLWCHAR name[SQL_MAX_MESSAGE_LENGTH];
#else
	    char name[SQL_MAX_MESSAGE_LENGTH];
#endif
	    SQLSMALLINT name_len;

	    for (i = 0; i < q->ncols; i++) {
		int need_len;

		name[0] = 0;
		if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			       SQLColAttributes(q->hstmt,
						(SQLUSMALLINT) (i + 1),
						SQL_COLUMN_TABLE_NAME,
						name,
						sizeof (name),
						&name_len, NULL),
			       &msg,
			       "SQLColAttributes(SQL_COLUMN_TABLE_NAME)")) {
		    rb_raise(Cerror, "%s", msg);
		}
		if (name_len >= (SQLSMALLINT) sizeof (name)) {
		    name_len = sizeof (name) - 1;
		}
		if (name_len > 0) {
		    name[name_len / sizeof (name[0])] = 0;
		}
#ifdef UNICODE
		need_len = 6 * (uc_strlen(name) + 1);
#else
		need_len = 2 * (strlen(name) + 1);
#endif
		need += need_len;
		if (max_len[0] < need_len) {
		    max_len[0] = need_len;
		}
		name[0] = 0;
		if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			       SQLColAttributes(q->hstmt,
						(SQLUSMALLINT) (i + 1),
						use_scn ? SQL_COLUMN_NAME :
						SQL_COLUMN_LABEL, name,
						sizeof (name),
						&name_len, NULL),
			       &msg, use_scn ?
			       "SQLColAttributes(SQL_COLUMN_NAME)" :
			       "SQLColAttributes(SQL_COLUMN_LABEL)")) {
		    rb_raise(Cerror, "%s", msg);
		}
		if (name_len >= (SQLSMALLINT) sizeof (name)) {
		    name_len = sizeof (name) - 1;
		}
		if (name_len > 0) {
		    name[name_len / sizeof (name[0])] = 0;
		}
#ifdef UNICODE
		need_len = 6 * 2 * (uc_strlen(name) + 1);
#else
		need_len = 2 * (strlen(name) + 1);
#endif
		need += need_len;
		if (max_len[1] < need_len) {
		    max_len[1] = need_len;
		}
	    }
	    need += max_len[0] + max_len[1] + 32;
	    p = ALLOC_N(char, need);
	    if (p == NULL) {
		rb_raise(Cerror, "%s", set_err("Out of memory", 0));
	    }
	    na = (char **) p;
	    p += sizeof (char *) * 4 * q->ncols + sizeof (char *);
	    for (i = 0; i < q->ncols; i++) {
		char *p0;

		name[0] = 0;
		callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			SQLColAttributes(q->hstmt, (SQLUSMALLINT) (i + 1),
					 SQL_COLUMN_TABLE_NAME, name,
					 sizeof (name), &name_len, NULL),
			"SQLColAttributes(SQL_COLUMN_TABLE_NAME)");
		if (name_len >= (SQLSMALLINT) sizeof (name)) {
		    name_len = sizeof (name) - 1;
		}
		if (name_len > 0) {
		    name[name_len / sizeof (name[0])] = 0;
		}
		na[i + q->ncols] = p;
#ifdef UNICODE
		p += mkutf(p, name, uc_strlen(name));
#else
		strcpy(p, name);
#endif
		strcat(p, ".");
		p += strlen(p);
		p0 = p;
		name[0] = 0;
		callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			SQLColAttributes(q->hstmt, (SQLUSMALLINT) (i + 1),
					 use_scn ? SQL_COLUMN_NAME :
					 SQL_COLUMN_LABEL, name,
					 sizeof (name), &name_len, NULL),
			use_scn ? "SQLColAttributes(SQL_COLUMN_NAME)" :
			"SQLColAttributes(SQL_COLUMN_LABEL)");
		if (name_len >= (SQLSMALLINT) sizeof (name)) {
		    name_len = sizeof (name) - 1;
		}
		if (name_len > 0) {
		    name[name_len / sizeof (name[0])] = 0;
		}
		na[i] = p;
#ifdef UNICODE
		p += mkutf(p, name, uc_strlen(name)) + 1;
#else
		strcpy(p, name);
		p += strlen(p) + 1;
#endif
		na[i + 3 * q->ncols] = p;
		strcpy(p, na[i + q->ncols]);
		p += p0 - na[i + q->ncols];
		na[i + 2 * q->ncols] = upcase_if(p, 1);
		p += strlen(p) + 1;
	    }
	    /* reserved space for later adjustments */
	    na[4 * q->ncols] = p;
	    q->colnames = na;
	    if (q->colvals == NULL) {
		q->colvals = ALLOC_N(VALUE, 4 * q->ncols);
		if (q->colvals != NULL) {
		    VALUE cname;
		    VALUE colbuf[4];

		    for (i = 0; i < 4 * q->ncols; i++) {
			q->colvals[i] = Qnil;
		    }
		    for (i = 0; i < 4; i++) {
			colbuf[i] = rb_iv_get(q->self, colnamebuf[i]);
			if (colbuf[i] == Qnil) {
			    res = rb_hash_new();
			    rb_iv_set(q->self, colnamebuf[i], res);
			}
		    }
		    for (i = 0; i < 4 * q->ncols; i++) {
			res = colbuf[i / q->ncols];
			cname = RB_TAINTED_STR_NEW2(q->colnames[i]);
#ifdef USE_RB_ENC
			rb_enc_associate(cname, rb_enc);
#endif
			q->colvals[i] = cname;
			if (rb_funcall(res, IDkeyp, 1, cname) == Qtrue) {
			    char *p;

			    cname = RB_TAINTED_STR_NEW2(q->colnames[i]);
#ifdef USE_RB_ENC
			    rb_enc_associate(cname, rb_enc);
#endif
			    p = q->colnames[4 * q->ncols];
			    sprintf(p, "#%d", i);
			    cname = rb_str_cat2(cname, p);
			    q->colvals[i] = cname;
			}
			rb_obj_freeze(cname);
			rb_hash_aset(res, cname, Qtrue);
		    }
		}
	    }
	}
	/* FALL THRU */
    case DOFETCH_HASHN:
	if (mode & DOFETCH_BANG) {
	    res = rb_iv_get(q->self, "@_h");
	    if (res == Qnil) {
		res = rb_hash_new();
		rb_iv_set(q->self, "@_h", res);
	    }
	} else {
	    res = rb_hash_new();
	}
	break;
    default:
	if (mode & DOFETCH_BANG) {
	    res = rb_iv_get(q->self, "@_a");
	    if (res == Qnil) {
		res = rb_ary_new2(q->ncols);
		rb_iv_set(q->self, "@_a", res);
	    } else {
		rb_ary_clear(res);
	    }
	} else {
	    res = rb_ary_new2(q->ncols);
	}
    }
    offc = q->upc ? (2 * q->ncols) : 0;
    switch (mode & DOFETCH_MODES) {
    case DOFETCH_HASHK2:
    case DOFETCH_HASH2:
	offc += q->ncols;
	break;
    }
    for (i = 0; i < q->ncols; i++) {
	SQLLEN totlen;
	SQLLEN curlen = q->coltypes[i].size;
	SQLSMALLINT type = q->coltypes[i].type;
	VALUE v, name;
	char *valp, *freep = NULL;

	if (curlen == SQL_NO_TOTAL) {
	    SQLLEN chunksize = SEGSIZE;

	    totlen = 0;
#ifdef UNICODE
	    valp = ALLOC_N(char, chunksize + sizeof (SQLWCHAR));
#else
	    valp = ALLOC_N(char, chunksize + 1);
#endif
	    freep = valp;
	    while ((curlen == SQL_NO_TOTAL) || (curlen > chunksize)) {
		SQLRETURN rc;
		int ret;

		rc = SQLGetData(q->hstmt, (SQLUSMALLINT) (i + 1),
				type, (SQLPOINTER) (valp + totlen),
#ifdef UNICODE
				((type == SQL_C_CHAR) || (type == SQL_C_WCHAR)) ?
				(chunksize + (int) sizeof (SQLWCHAR)) :
				chunksize,
#else
				(type == SQL_C_CHAR) ?
				(chunksize + 1) : chunksize,
#endif
				&curlen);
		if (rc == SQL_NO_DATA) {
		    if (curlen == SQL_NO_TOTAL) {
			curlen = totlen;
		    }
		    break;
		}
		ret = succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
				rc, &msg, "SQLGetData");
		if (!ret) {
		    xfree(valp);
		    rb_raise(Cerror, "%s", msg);
		}
		if (curlen == SQL_NULL_DATA) {
		    break;
		}
		if (curlen == SQL_NO_TOTAL) {
		    totlen += chunksize;
		} else if (curlen > chunksize) {
		    totlen += chunksize;
		    chunksize = curlen - chunksize;
		} else {
		    totlen += curlen;
		    break;
		}
#ifdef UNICODE
		REALLOC_N(valp, char, totlen + chunksize + sizeof (SQLWCHAR));
#else
		REALLOC_N(valp, char, totlen + chunksize + 1);
#endif
		if (valp == NULL) {
		    if (freep != NULL) {
			xfree(freep);
		    }
		    rb_raise(Cerror, "%s", set_err("Out of memory", 0));
		}
		freep = valp;
	    }
	    if (totlen > 0) {
		curlen = totlen;
	    }
	} else {
	    totlen = curlen;
	    valp = bufs[i];
	    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			   SQLGetData(q->hstmt, (SQLUSMALLINT) (i + 1), type,
				      (SQLPOINTER) valp, totlen, &curlen),
			   &msg, "SQLGetData")) {
		rb_raise(Cerror, "%s", msg);
	    }
	}
	if (curlen == SQL_NULL_DATA) {
	    v = Qnil;
	} else {
	    switch (type) {
	    case SQL_C_LONG:
		v = INT2NUM(*((SQLINTEGER *) valp));
		break;
	    case SQL_C_DOUBLE:
		v = rb_float_new(*((double *) valp));
		break;
#ifdef SQL_C_SBIGINT
	    case SQL_C_SBIGINT:
#ifdef LL2NUM
		v = LL2NUM(*((SQLBIGINT *) valp));
#else
		v = INT2NUM(*((SQLBIGINT *) valp));
#endif
		break;
#endif
#ifdef SQL_C_UBIGINT
	    case SQL_C_UBIGINT:
#ifdef LL2NUM
		v = ULL2NUM(*((SQLBIGINT *) valp));
#else
		v = UINT2NUM(*((SQLBIGINT *) valp));
#endif
		break;
#endif
	    case SQL_C_DATE:
		{
		    DATE_STRUCT *date;

		    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
			const char *p;
			char buffer[128];
			VALUE d;

			date = (DATE_STRUCT *) valp;
			p = (q->dbcp->gmtime == Qtrue) ? "+00:00" : "";
			sprintf(buffer, "%d-%d-%dT00:00:00%s",
				date->year, date->month, date->day, p);
			d = rb_str_new2(buffer);
			v = rb_funcall(rb_cDate, IDparse, 1, d);
		    } else {
			v = Data_Make_Struct(Cdate, DATE_STRUCT, 0, xfree,
					     date);
			*date = *(DATE_STRUCT *) valp;
		    }
		}
		break;
	    case SQL_C_TIME:
		{
		    TIME_STRUCT *time;

		    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
			VALUE now, frac;

			time = (TIME_STRUCT *) valp;
			frac = rb_float_new(0.0);
			now = FUNCALL_NOARGS(rb_cTime, IDnow);
			v = rb_funcall(rb_cTime,
				       (q->dbcp->gmtime == Qtrue) ?
				       IDutc : IDlocal,
				       7,
				       FUNCALL_NOARGS(now, IDyear),
				       FUNCALL_NOARGS(now, IDmonth),
				       FUNCALL_NOARGS(now, IDday),
				       INT2NUM(time->hour),
				       INT2NUM(time->minute),
				       INT2NUM(time->second),
				       frac);
		    } else {
			v = Data_Make_Struct(Ctime, TIME_STRUCT, 0, xfree,
					     time);
			*time = *(TIME_STRUCT *) valp;
		    }
		}
		break;
	    case SQL_C_TIMESTAMP:
		{
		    TIMESTAMP_STRUCT *ts;

		    if (q->dbcp != NULL && q->dbcp->rbtime == Qtrue) {
			VALUE frac;

			ts = (TIMESTAMP_STRUCT *) valp;
			frac = rb_float_new((double) 1.0e-3 * ts->fraction);
			v = rb_funcall(rb_cTime,
				       (q->dbcp->gmtime == Qtrue) ?
				       IDutc : IDlocal,
				       7,
				       INT2NUM(ts->year),
				       INT2NUM(ts->month),
				       INT2NUM(ts->day),
				       INT2NUM(ts->hour),
				       INT2NUM(ts->minute),
				       INT2NUM(ts->second),
				       frac);
		    } else {
			v = Data_Make_Struct(Ctimestamp, TIMESTAMP_STRUCT,
					     0, xfree, ts);
			*ts = *(TIMESTAMP_STRUCT *) valp;
		    }
		}
		break;
#ifdef UNICODE
	    case SQL_C_WCHAR:
		v = uc_tainted_str_new((SQLWCHAR *) valp,
				       curlen / sizeof (SQLWCHAR));
		break;
#endif
	    default:
		v = RB_TAINTED_STR_NEW(valp, curlen);
		break;
	    }
	}
	if (freep != NULL) {
	    xfree(freep);
	}
	switch (mode & DOFETCH_MODES) {
	case DOFETCH_HASH:
	case DOFETCH_HASH2:
	    valp = q->colnames[i + offc];
	    name = (q->colvals == NULL) ? Qnil : q->colvals[i + offc];
	    if (name == Qnil) {
		name = RB_TAINTED_STR_NEW2(valp);
#ifdef USE_RB_ENC
		rb_enc_associate(name, rb_enc);
#endif
		if (rb_funcall(res, IDkeyp, 1, name) == Qtrue) {
		    char *p;

		    name = RB_TAINTED_STR_NEW2(valp);
#ifdef USE_RB_ENC
		    rb_enc_associate(name, rb_enc);
#endif
		    p = q->colnames[4 * q->ncols];
		    sprintf(p, "#%d", i);
		    name = rb_str_cat2(name, p);
		}
	    }
	    rb_hash_aset(res, name, v);
	    break;
	case DOFETCH_HASHK:
	case DOFETCH_HASHK2:
	    valp = q->colnames[i + offc];
#ifdef USE_RB_ENC
	    name = ID2SYM(rb_intern3(valp, strlen(valp), rb_enc));
#else
	    name = ID2SYM(rb_intern(valp));
#endif
	    if (rb_funcall(res, IDkeyp, 1, name) == Qtrue) {
		char *p;

		p = q->colnames[4 * q->ncols];
		sprintf(p, "%s#%d", valp, i);
#ifdef USE_RB_ENC
		name = ID2SYM(rb_intern3(p, strlen(p), rb_enc));
#else
		name = ID2SYM(rb_intern(p));
#endif
	    }
	    rb_hash_aset(res, name, v);
	    break;
	case DOFETCH_HASHN:
	    name = INT2NUM(i);
	    rb_hash_aset(res, name, v);
	    break;
	default:
	    rb_ary_push(res, v);
	}
    }
    return res;
}

static VALUE
stmt_fetch1(VALUE self, int bang)
{
    STMT *q;
    SQLRETURN ret;
    const char *msg;
    char *err;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    Data_Get_Struct(self, STMT, q);
    if (q->ncols <= 0) {
	return Qnil;
    }
    if (q->usef) {
	goto usef;
    }
#if (ODBCVER < 0x0300)
    msg = "SQLExtendedFetch(SQL_FETCH_NEXT)";
    ret = SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_NEXT, 0, &nRows, rowStat);
#else
    msg = "SQLFetchScroll(SQL_FETCH_NEXT)";
    ret = SQLFETCHSCROLL(q->hstmt, SQL_FETCH_NEXT, 0);
#endif
    if (ret == SQL_NO_DATA) {
	(void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	return Qnil;
    }
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &err, msg)) {
	return do_fetch(q, DOFETCH_ARY | (bang ? DOFETCH_BANG : 0));
    }
    if ((err != NULL) &&
	((strncmp(err, "IM001", 5) == 0) ||
	 (strncmp(err, "HYC00", 5) == 0))) {
usef:
	/* Fallback to SQLFetch() when others not implemented */
	msg = "SQLFetch";
	q->usef = 1;
	ret = SQLFETCH(q->hstmt);
	if (ret == SQL_NO_DATA) {
	    (void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	    return Qnil;
	}
	if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret,
		      &err, msg)) {
	    return do_fetch(q, DOFETCH_ARY | (bang ? DOFETCH_BANG : 0));
	}
    }
    rb_raise(Cerror, "%s", err);
    return Qnil;
}

static VALUE
stmt_fetch(VALUE self)
{
    if (rb_block_given_p()) {
	return stmt_each(self);
    }
    return stmt_fetch1(self, 0);
}

static VALUE
stmt_fetch_bang(VALUE self)
{
    if (rb_block_given_p()) {
	return stmt_each(self);
    }
    return stmt_fetch1(self, 1);
}

static VALUE
stmt_fetch_first1(VALUE self, int bang, int nopos)
{
    STMT *q;
    SQLRETURN ret;
    const char *msg;
    char *err;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    Data_Get_Struct(self, STMT, q);
    if (q->ncols <= 0) {
	return Qnil;
    }
    if (nopos) {
	goto dofetch;
    }
#if (ODBCVER < 0x0300)
    msg = "SQLExtendedFetch(SQL_FETCH_FIRST)";
    ret = SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_FIRST, 0, &nRows, rowStat);
#else
    msg = "SQLFetchScroll(SQL_FETCH_FIRST)";
    ret = SQLFETCHSCROLL(q->hstmt, SQL_FETCH_FIRST, 0);
#endif
    if (ret == SQL_NO_DATA) {
	(void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	return Qnil;
    }
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &err, msg)) {
dofetch:
	return do_fetch(q, DOFETCH_ARY | (bang ? DOFETCH_BANG : 0));
    }
    rb_raise(Cerror, "%s", err);
    return Qnil;
}

static VALUE
stmt_fetch_first(VALUE self)
{
    return stmt_fetch_first1(self, 0, 0);
}

static VALUE
stmt_fetch_first_bang(VALUE self)
{
    return stmt_fetch_first1(self, 1, 0);
}

static VALUE
stmt_fetch_scroll1(int argc, VALUE *argv, VALUE self, int bang)
{
    STMT *q;
    VALUE dir, offs;
    SQLRETURN ret;
    int idir, ioffs = 1;
    char msg[128], *err;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    rb_scan_args(argc, argv, "11", &dir, &offs);
    idir = NUM2INT(dir);
    if (offs != Qnil) {
	ioffs = NUM2INT(offs);
    }
    Data_Get_Struct(self, STMT, q);
    if (q->ncols <= 0) {
	return Qnil;
    }
#if (ODBCVER < 0x0300)
    sprintf(msg, "SQLExtendedFetch(%d)", idir);
    ret = SQLEXTENDEDFETCH(q->hstmt, (SQLSMALLINT) idir, (SQLROWOFFSET) ioffs,
			   &nRows, rowStat);
#else
    sprintf(msg, "SQLFetchScroll(%d)", idir);
    ret = SQLFETCHSCROLL(q->hstmt, (SQLSMALLINT) idir, (SQLROWOFFSET) ioffs);
#endif
    if (ret == SQL_NO_DATA) {
	(void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	return Qnil;
    }
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &err, msg)) {
	return do_fetch(q, DOFETCH_ARY | (bang ? DOFETCH_BANG : 0));
    }
    rb_raise(Cerror, "%s", err);
    return Qnil;
}

static VALUE
stmt_fetch_scroll(int argc, VALUE *argv, VALUE self)
{
    return stmt_fetch_scroll1(argc, argv, self, 0);
}

static VALUE
stmt_fetch_scroll_bang(int argc, VALUE *argv, VALUE self)
{
    return stmt_fetch_scroll1(argc, argv, self, 1);
}

static VALUE
stmt_fetch_many(VALUE self, VALUE arg)
{
    int i, max = 0, all = arg == Qnil;
    VALUE res;

    if (!all) {
	max = NUM2INT(arg);
    }
    res = rb_ary_new();
    for (i = 0; all || (i < max); i++) {
	VALUE v = stmt_fetch1(self, 0);

	if (v == Qnil) {
	    break;
	}
	rb_ary_push(res, v);
    }
    return (i == 0) ? Qnil : res;
}

static VALUE
stmt_fetch_all(VALUE self)
{
    return stmt_fetch_many(self, Qnil);
}

static int
stmt_hash_mode(int argc, VALUE *argv, VALUE self)
{
    VALUE withtab = Qnil, usesym = Qnil;
    int mode = DOFETCH_HASH;

    rb_scan_args(argc, argv, "02", &withtab, &usesym);
    if ((withtab != Qtrue) && (withtab != Qfalse) && (withtab != Modbc) &&
	(rb_obj_is_kind_of(withtab, rb_cHash) == Qtrue)) {
	VALUE v;

	v = rb_hash_aref(withtab, ID2SYM(IDkey));
	if (v == ID2SYM(IDSymbol)) {
	    mode = DOFETCH_HASHK;
	} else if (v == ID2SYM(IDString)) {
	    mode = DOFETCH_HASH;
	} else if (v == ID2SYM(IDFixnum)) {
	    mode = DOFETCH_HASHN;
	} else {
	    rb_raise(Cerror, "Unsupported key mode");
	}
	if (mode != DOFETCH_HASHN) {
	    v = rb_hash_aref(withtab, ID2SYM(IDtable_names));
	    if (RTEST(v)) {
		mode = (mode == DOFETCH_HASHK)
		     ? DOFETCH_HASHK2 : DOFETCH_HASH2;
	    }
	}
	return mode;
    }
    if (withtab == Modbc) {
	return DOFETCH_HASHN;
    }
    mode = RTEST(withtab) ? DOFETCH_HASH2 : DOFETCH_HASH;
    if (RTEST(usesym)) {
	mode = (mode == DOFETCH_HASH2) ? DOFETCH_HASHK2 : DOFETCH_HASHK;
    }
    return mode;
}

static VALUE
stmt_fetch_hash1(int argc, VALUE *argv, VALUE self, int bang)
{
    STMT *q;
    SQLRETURN ret;
    int mode = stmt_hash_mode(argc, argv, self);
    const char *msg;
    char *err;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    Data_Get_Struct(self, STMT, q);
    if (q->ncols <= 0) {
	return Qnil;
    }
    if (q->usef) {
	goto usef;
    }
#if (ODBCVER < 0x0300)
    msg = "SQLExtendedFetch(SQL_FETCH_NEXT)";
    ret = SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_NEXT, 0, &nRows, rowStat);
#else
    msg = "SQLFetchScroll(SQL_FETCH_NEXT)";
    ret = SQLFETCHSCROLL(q->hstmt, SQL_FETCH_NEXT, 0);
#endif
    if (ret == SQL_NO_DATA) {
	(void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	return Qnil;
    }
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &err, msg)) {
	return do_fetch(q, mode | (bang ? DOFETCH_BANG : 0));
    }
    if ((err != NULL) &&
	((strncmp(err, "IM001", 5) == 0) ||
	 (strncmp(err, "HYC00", 5) == 0))) {
usef:
	/* Fallback to SQLFetch() when others not implemented */
	msg = "SQLFetch";
	q->usef = 1;
	ret = SQLFETCH(q->hstmt);
	if (ret == SQL_NO_DATA) {
	    (void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	    return Qnil;
	}
	if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret,
		      &err, msg)) {
	    return do_fetch(q, mode | (bang ? DOFETCH_BANG : 0));
	}
    }
    rb_raise(Cerror, "%s", err);
    return Qnil;
}

static VALUE
stmt_fetch_hash(int argc, VALUE *argv, VALUE self)
{
    if (rb_block_given_p()) {
	return stmt_each_hash(argc, argv, self);
    }
    return stmt_fetch_hash1(argc, argv, self, 0);
}

static VALUE
stmt_fetch_hash_bang(int argc, VALUE *argv, VALUE self)
{
    if (rb_block_given_p()) {
	return stmt_each_hash(argc, argv, self);
    }
    return stmt_fetch_hash1(argc, argv, self, 1);
}

static VALUE
stmt_fetch_first_hash1(int argc, VALUE *argv, VALUE self, int bang, int nopos)
{
    STMT *q;
    SQLRETURN ret;
    int mode = stmt_hash_mode(argc, argv, self);
    const char *msg;
    char *err;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    Data_Get_Struct(self, STMT, q);
    if (q->ncols <= 0) {
	return Qnil;
    }
    if (nopos) {
	goto dofetch;
    }
#if (ODBCVER < 0x0300)
    msg = "SQLExtendedFetch(SQL_FETCH_FIRST)";
    ret = SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_FIRST, 0, &nRows, rowStat);
#else
    msg = "SQLFetchScroll(SQL_FETCH_FIRST)";
    ret = SQLFETCHSCROLL(q->hstmt, SQL_FETCH_FIRST, 0);
#endif
    if (ret == SQL_NO_DATA) {
	(void) tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, msg);
	return Qnil;
    }
    if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &err, msg)) {
dofetch:
	return do_fetch(q, mode | (bang ? DOFETCH_BANG : 0));
    }
    rb_raise(Cerror, "%s", err);
    return Qnil;
}

static VALUE
stmt_fetch_first_hash(int argc, VALUE *argv, VALUE self)
{
    return stmt_fetch_first_hash1(argc, argv, self, 0, 0);
}

static VALUE
stmt_each(VALUE self)
{
    VALUE row, res = Qnil;
    STMT *q;
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    Data_Get_Struct(self, STMT, q);
#if (ODBCVER < 0x0300)
    switch (callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		    SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_FIRST, 0, &nRows,
				     rowStat),
		    "SQLExtendedFetch(SQL_FETCH_FIRST)"))
#else
    switch (callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		    SQLFETCHSCROLL(q->hstmt, SQL_FETCH_FIRST, 0),
		    "SQLFetchScroll(SQL_FETCH_FIRST)"))
#endif
    {
    case SQL_NO_DATA:
	row = Qnil;
	break;
    case SQL_SUCCESS:
    case SQL_SUCCESS_WITH_INFO:
	row = stmt_fetch_first1(self, 0, 1);
	break;
    default:
	row = stmt_fetch1(self, 0);
    }
    if (rb_block_given_p()) {
	while (row != Qnil) {
	    rb_yield(row);
	    row = stmt_fetch1(self, 0);
	}
	return self;
    }
    if (row != Qnil) {
	res = rb_ary_new();
	while (row != Qnil) {
	    rb_ary_push(res, row);
	    row = stmt_fetch1(self, 0);
	}
    }
    return res;
}

static VALUE
stmt_each_hash(int argc, VALUE *argv, VALUE self)
{
    VALUE row, res = Qnil, withtab[2];
    STMT *q;
    int mode = stmt_hash_mode(argc, argv, self);
#if (ODBCVER < 0x0300)
    SQLROWSETSIZE nRows;
    SQLUSMALLINT rowStat[1];
#endif

    if (mode == DOFETCH_HASHN) {
	withtab[0] = Modbc;
	withtab[1] = Qfalse;
    } else {
	withtab[0] = ((mode == DOFETCH_HASH2) || (mode == DOFETCH_HASHK2))
		   ? Qtrue : Qfalse;
	withtab[1] = ((mode == DOFETCH_HASHK) || (mode == DOFETCH_HASHK2))
		   ? Qtrue : Qfalse;
    }
    Data_Get_Struct(self, STMT, q);
#if (ODBCVER < 0x0300)
    switch (callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		    SQLEXTENDEDFETCH(q->hstmt, SQL_FETCH_FIRST, 0, &nRows,
				     rowStat),
		    "SQLExtendedFetch(SQL_FETCH_FIRST)"))
#else
    switch (callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		    SQLFETCHSCROLL(q->hstmt, SQL_FETCH_FIRST, 0),
		    "SQLFetchScroll(SQL_FETCH_FIRST)"))
#endif
    {
    case SQL_NO_DATA:
	row = Qnil;
	break;
    case SQL_SUCCESS:
    case SQL_SUCCESS_WITH_INFO:
	row = stmt_fetch_first_hash1(2, withtab, self, 0, 1);
	break;
    default:
	row = stmt_fetch_hash1(2, withtab, self, 0);
    }
    if (rb_block_given_p()) {
	while (row != Qnil) {
	    rb_yield(row);
	    row = stmt_fetch_hash1(2, withtab, self, 0);
	}
	return self;
    }
    if (row != Qnil) {
	res = rb_ary_new();
	while (row != Qnil) {
	    rb_ary_push(res, row);
	    row = stmt_fetch_hash1(2, withtab, self, 0);
	}
    }
    return res;
}

static VALUE
stmt_more_results(VALUE self)
{
    STMT *q;

    if (rb_block_given_p()) {
	rb_raise(rb_eArgError, "block not allowed");
    }
    Data_Get_Struct(self, STMT, q);
    if (q->hstmt == SQL_NULL_HSTMT) {
	return Qfalse;
    }
    switch (tracesql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		     SQLMoreResults(q->hstmt), "SQLMoreResults")) {
    case SQL_NO_DATA:
	return Qfalse;
    case SQL_SUCCESS:
    case SQL_SUCCESS_WITH_INFO:
	free_stmt_sub(q, 0);
	make_result(q->dbc, q->hstmt, self, 0);
	break;
    default:
	rb_raise(Cerror, "%s",
		 get_err(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt));
    }
    return Qtrue;
}

static VALUE
stmt_prep_int(int argc, VALUE *argv, VALUE self, int mode)
{
    DBC *p = get_dbc(self);
    STMT *q = NULL;
    VALUE sql, dbc, stmt;
    SQLHSTMT hstmt;
    SQLRETURN ret;
#ifdef UNICODE
    SQLWCHAR *ssql = NULL;
#else
    SQLCHAR *ssql = NULL;
#endif
    char *csql = NULL, *msg = NULL;

    if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) {
	Data_Get_Struct(self, STMT, q);
	free_stmt_sub(q, 0);
	if (q->hstmt == SQL_NULL_HSTMT) {
	    if (!succeeded(SQL_NULL_HENV, p->hdbc, q->hstmt,
			   SQLAllocStmt(p->hdbc, &q->hstmt),
			   &msg, "SQLAllocStmt")) {
		rb_raise(Cerror, "%s", msg);
	    }
	} else if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
			      SQLFreeStmt(q->hstmt, SQL_CLOSE),
			      &msg, "SQLFreeStmt(SQL_CLOSE)")) {
	    rb_raise(Cerror, "%s", msg);
	}
	hstmt = q->hstmt;
	stmt = self;
	dbc = q->dbc;
    } else {
	if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		       SQLAllocStmt(p->hdbc, &hstmt),
		       &msg, "SQLAllocStmt")) {
	    rb_raise(Cerror, "%s", msg);
	}
	stmt = Qnil;
	dbc = self;
    }
    rb_scan_args(argc, argv, "1", &sql);
    Check_Type(sql, T_STRING);
#ifdef UNICODE
#ifdef USE_RB_ENC
    sql = rb_funcall(sql, IDencode, 1, rb_encv);
#endif
    csql = STR2CSTR(sql);
    ssql = uc_from_utf((unsigned char *) csql, -1);
    if (ssql == NULL) {
	rb_raise(Cerror, "%s", set_err("Out of memory", 0));
    }
#else
    csql = STR2CSTR(sql);
    ssql = (SQLCHAR *) csql;
#endif
    if ((mode & MAKERES_EXECD)) {
	ret = SQLEXECDIRECT(hstmt, ssql, SQL_NTS);
	if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, ret,
			      &msg, "SQLExecDirect('%s')", csql)) {
	    goto sqlerr;
	}
	if (ret == SQL_NO_DATA) {
	    callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		    SQLFreeStmt(hstmt, SQL_CLOSE), "SQLFreeStmt(SQL_DROP)");
	    if (q != NULL) {
		q->hstmt = SQL_NULL_HSTMT;
		unlink_stmt(q);
	    }
	    hstmt = SQL_NULL_HSTMT;
	}
    } else {
	ret = SQLPREPARE(hstmt, ssql, SQL_NTS);
	if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, ret,
			  &msg, "SQLPrepare('%s')", csql)) {
sqlerr:
#ifdef UNICODE
	    uc_free(ssql);
#endif
	    callsql(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt,
		    SQLFreeStmt(hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
	    if (q != NULL) {
		q->hstmt = SQL_NULL_HSTMT;
		unlink_stmt(q);
		}
	    rb_raise(Cerror, "%s", msg);
	} else {
	    mode |= MAKERES_PREPARE;
	}
    }
#ifdef UNICODE
    uc_free(ssql);
#endif
    return make_result(dbc, hstmt, stmt, mode);
}

static VALUE
stmt_prep(int argc, VALUE *argv, VALUE self)
{
    return stmt_prep_int(argc, argv, self, MAKERES_BLOCK);
}

static int
bind_one_param(int pnum, VALUE arg, STMT *q, char **msgp, int *outpp)
{
    SQLPOINTER valp = (SQLPOINTER) &q->paraminfo[pnum].buffer;
    SQLSMALLINT ctype, stype;
    SQLINTEGER vlen, rlen;
    SQLUINTEGER coldef;
#ifdef NO_RB_STR2CSTR
    VALUE val;
#endif
    long llen;
    int retry = 1;
#ifdef UNICODE
    SQLWCHAR *up;

    q->paraminfo[pnum].tofree = NULL;
#endif
    switch (TYPE(arg)) {
    case T_STRING:
#ifdef UNICODE
	ctype = SQL_C_WCHAR;
#ifdef USE_RB_ENC
	arg = rb_funcall(arg, IDencode, 1, rb_encv);
#endif
#ifndef NO_RB_STR2CSTR
	up = (SQLWCHAR *) rb_str2cstr(arg, &llen);
	if (llen != (long) strlen((char *) up)) {
	    ctype = SQL_C_BINARY;
	    valp = (SQLPOINTER) up;
	    rlen = llen;
	    vlen = rlen + 1;
	    break;
	}
#else
	val = rb_string_value(&arg);
	up = (SQLWCHAR *) RSTRING_PTR(val);
	llen = RSTRING_LEN(val);
	if (up == NULL) {
	    goto oom;
	}
	if (memchr((char *) up, 0, llen)) {
	    ctype = SQL_C_BINARY;
	    valp = (SQLPOINTER) up;
	    rlen = llen;
	    vlen = rlen + 1;
	    break;
	}
	up = (SQLWCHAR *) rb_string_value_cstr(&arg);
#endif
	up = uc_from_utf((unsigned char *) up, llen);
	if (up == NULL) {
	    goto oom;
	}
	*(SQLWCHAR **) valp = up;
	rlen = uc_strlen(up) * sizeof (SQLWCHAR);
	vlen = rlen + sizeof (SQLWCHAR);
	q->paraminfo[pnum].tofree = up;
#else
	ctype = SQL_C_CHAR;
#ifndef NO_RB_STR2CSTR
	valp = (SQLPOINTER) rb_str2cstr(arg, &llen);
	rlen = llen;
	if (rlen != (SQLINTEGER) strlen((char *) valp)) {
	    ctype = SQL_C_BINARY;
	}
	vlen = rlen + 1;
#else
	val = rb_string_value(&arg);
	valp = (SQLPOINTER) RSTRING_PTR(val);
	llen = RSTRING_LEN(val);
	if (valp == NULL) {
	    goto oom;
	}
	rlen = llen;
	vlen = rlen + 1;
	if (memchr((char *) valp, 0, llen)) {
	    ctype = SQL_C_BINARY;
	    break;
	}
	valp = (SQLPOINTER) rb_string_value_cstr(&arg);
#endif
#endif
	break;
    case T_FIXNUM:
	ctype = SQL_C_LONG;
	*(SQLINTEGER *) valp = FIX2INT(arg);
	rlen = 1;
	vlen = sizeof (SQLINTEGER);
	break;
    case T_FLOAT:
	ctype = SQL_C_DOUBLE;
	*(double *) valp = NUM2DBL(arg);
	rlen = 1;
	vlen = sizeof (double);
	break;
    case T_NIL:
	ctype = SQL_C_CHAR;
	valp = NULL;
	rlen = SQL_NULL_DATA;
	vlen = 0;
	break;
    case T_SYMBOL:
	ctype = SQL_C_CHAR;
	valp = NULL;
	vlen = 0;
	if (arg == ID2SYM(IDNULL)) {
	    rlen = SQL_NULL_DATA;
	} else if (arg == ID2SYM(IDdefault)) {
	    rlen = SQL_DEFAULT_PARAM;
	}
	/* fall through */
    default:
	if (rb_obj_is_kind_of(arg, Cdate) == Qtrue) {
	    DATE_STRUCT *date;

	    ctype = SQL_C_DATE;
	    Data_Get_Struct(arg, DATE_STRUCT, date);
	    valp = (SQLPOINTER) date;
	    rlen = 1;
	    vlen = sizeof (DATE_STRUCT);
	    break;
	}
	if (rb_obj_is_kind_of(arg, Ctime) == Qtrue) {
	    TIME_STRUCT *time;

	    ctype = SQL_C_TIME;
	    Data_Get_Struct(arg, TIME_STRUCT, time);
	    valp = (SQLPOINTER) time;
	    rlen = 1;
	    vlen = sizeof (TIME_STRUCT);
	    break;
	}
	if (rb_obj_is_kind_of(arg, Ctimestamp) == Qtrue) {
	    TIMESTAMP_STRUCT *ts;

	    ctype = SQL_C_TIMESTAMP;
	    Data_Get_Struct(arg, TIMESTAMP_STRUCT, ts);
	    valp = (SQLPOINTER) ts;
	    rlen = 1;
	    vlen = sizeof (TIMESTAMP_STRUCT);
	    break;
	}
	if (rb_obj_is_kind_of(arg, rb_cTime) == Qtrue) {
	    if (q->paraminfo[pnum].type == SQL_TIME) {
		TIME_STRUCT *time;

		ctype = SQL_C_TIME;
		time = (TIME_STRUCT *) valp;
		memset(time, 0, sizeof (TIME_STRUCT));
		time->hour   = FUNCALL_NOARGS(arg, IDhour);
		time->minute = FUNCALL_NOARGS(arg, IDmin);
		time->second = FUNCALL_NOARGS(arg, IDsec);
		rlen = 1;
		vlen = sizeof (TIME_STRUCT);
	    } else if (q->paraminfo[pnum].type == SQL_DATE) {
		DATE_STRUCT *date;

		ctype = SQL_C_DATE;
		date = (DATE_STRUCT *) valp;
		memset(date, 0, sizeof (DATE_STRUCT));
		date->year  = FUNCALL_NOARGS(arg, IDyear);
		date->month = FUNCALL_NOARGS(arg, IDmonth);
		date->day   = FUNCALL_NOARGS(arg, IDday);
		rlen = 1;
		vlen = sizeof (TIMESTAMP_STRUCT);
	    } else {
		TIMESTAMP_STRUCT *ts;

		ctype = SQL_C_TIMESTAMP;
		ts = (TIMESTAMP_STRUCT *) valp;
		memset(ts, 0, sizeof (TIMESTAMP_STRUCT));
		ts->year     = FUNCALL_NOARGS(arg, IDyear);
		ts->month    = FUNCALL_NOARGS(arg, IDmonth);
		ts->day      = FUNCALL_NOARGS(arg, IDday);
		ts->hour     = FUNCALL_NOARGS(arg, IDhour);
		ts->minute   = FUNCALL_NOARGS(arg, IDmin);
		ts->second   = FUNCALL_NOARGS(arg, IDsec);
#ifdef TIME_USE_USEC
		ts->fraction = FUNCALL_NOARGS(arg, IDusec) * 1000;
#else
		ts->fraction = FUNCALL_NOARGS(arg, IDnsec);
#endif
		rlen = 1;
		vlen = sizeof (TIMESTAMP_STRUCT);
	    }
	    break;
	}
	if (rb_obj_is_kind_of(arg, rb_cDate) == Qtrue) {
	    DATE_STRUCT *date;

	    ctype = SQL_C_DATE;
	    date = (DATE_STRUCT *) valp;
	    memset(date, 0, sizeof (DATE_STRUCT));
	    date->year  = FUNCALL_NOARGS(arg, IDyear);
	    date->month = FUNCALL_NOARGS(arg, IDmonth);
	    date->day   = FUNCALL_NOARGS(arg, IDmday);
	    rlen = 1;
	    vlen = sizeof (DATE_STRUCT);
	    break;
	}
	ctype = SQL_C_CHAR;
#ifndef NO_RB_STR2CSTR
	valp = (SQLPOINTER *) rb_str2cstr(rb_str_to_str(arg), &llen);
	rlen = llen;
	if (rlen != (SQLINTEGER) strlen((char *) valp)) {
	    ctype = SQL_C_BINARY;
	}
	vlen = rlen + 1;
#else
	val = rb_string_value(&arg);
	valp = (SQLPOINTER) RSTRING_PTR(val);
	llen = RSTRING_LEN(val);
	if (valp == NULL) {
	    goto oom;
	}
	rlen = llen;
	vlen = rlen + 1;
	if (memchr((char *) valp, 0, llen)) {
	    ctype = SQL_C_BINARY;
	    break;
	}
	valp = (SQLPOINTER) rb_string_value_cstr(&arg);
#endif
	break;
    }
    stype = q->paraminfo[pnum].type;
    coldef = q->paraminfo[pnum].coldef;
    q->paraminfo[pnum].rlen = rlen;
    q->paraminfo[pnum].ctype = ctype;
    if (coldef == 0) {
	switch (ctype) {
	case SQL_C_LONG:
	    coldef = 10;
	    break;
	case SQL_C_DOUBLE:
	    coldef = 15;
	    if (stype == SQL_VARCHAR) {
		stype = SQL_DOUBLE;
	    }
	    break;
	case SQL_C_DATE:
	    coldef = 10;
	    break;
	case SQL_C_TIME:
	    coldef = 8;
	    break;
	case SQL_C_TIMESTAMP:
	    coldef = 19;
	    break;
	default:
	    /*
	     * Patch adopted from the Perl DBD::ODBC module ...
	     * per patch from Paul G. Weiss, who was experiencing re-preparing
	     * of queries when the size of the bound string's were increasing
	     * for example select * from tabtest where name = ?
	     * then executing with 'paul' and then 'thomas' would cause
	     * SQLServer to prepare the query twice, but if we ran 'thomas'
	     * then 'paul', it would not re-prepare the query.  The key seems
	     * to be allocating enough space for the largest parameter.
	     * TBD: the default for this should be a tunable parameter.
	     */
	    if ((stype == SQL_VARCHAR) &&
		(q->paraminfo[pnum].iotype != SQL_PARAM_INPUT_OUTPUT) &&
		(q->paraminfo[pnum].iotype != SQL_PARAM_OUTPUT)) {
		if (q->paraminfo[pnum].coldef_max == 0) {
		    q->paraminfo[pnum].coldef_max = (vlen > 128) ? vlen : 128;
		} else {
		    /* bump up max, if needed */
		    if (vlen > (SQLINTEGER) q->paraminfo[pnum].coldef_max) {
			q->paraminfo[pnum].coldef_max = vlen;
		    }
		}
		coldef = q->paraminfo[pnum].coldef_max;
	    } else {
		coldef = vlen;
	    }
	    break;
	}
    }
    if ((q->paraminfo[pnum].iotype == SQL_PARAM_INPUT_OUTPUT) ||
	(q->paraminfo[pnum].iotype == SQL_PARAM_OUTPUT)) {
	if (valp == NULL) {
	    if (q->paraminfo[pnum].outsize > 0) {
		if (q->paraminfo[pnum].outbuf != NULL) {
		    xfree(q->paraminfo[pnum].outbuf);
		}
		q->paraminfo[pnum].outbuf = xmalloc(q->paraminfo[pnum].outsize);
		if (q->paraminfo[pnum].outbuf == NULL) {
		    goto oom;
		}
		ctype = q->paraminfo[pnum].ctype = q->paraminfo[pnum].outtype;
		outpp[0]++;
		valp = q->paraminfo[pnum].outbuf;
		vlen = q->paraminfo[pnum].outsize;
	    }
	} else {
	    if (q->paraminfo[pnum].outbuf != NULL) {
		xfree(q->paraminfo[pnum].outbuf);
	    }
	    q->paraminfo[pnum].outbuf = xmalloc(vlen);
	    if (q->paraminfo[pnum].outbuf == NULL) {
oom:
#ifdef UNICODE
		if (q->paraminfo[pnum].tofree != NULL) {
		    uc_free(q->paraminfo[pnum].tofree);
		    q->paraminfo[pnum].tofree = NULL;
		}
#endif
		*msgp = set_err("Out of memory", 0);
		return -1;
	    }
#ifdef UNICODE
	    if (ctype == SQL_C_WCHAR) {
		memcpy(q->paraminfo[pnum].outbuf, *(SQLWCHAR **) valp, vlen);
	    } else
#endif
	    memcpy(q->paraminfo[pnum].outbuf, valp, vlen);
#ifdef UNICODE
	    if (ctype == SQL_C_WCHAR) {
		*(SQLWCHAR **) valp = (SQLWCHAR *) q->paraminfo[pnum].outbuf;
	    } else
#endif
	    valp = q->paraminfo[pnum].outbuf;
	    outpp[0]++;
	}
    }
retry:
#ifdef UNICODE
    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		   SQLBindParameter(q->hstmt, (SQLUSMALLINT) (pnum + 1),
				    q->paraminfo[pnum].iotype,
				    ctype, stype, coldef,
				    q->paraminfo[pnum].scale,
				    (ctype == SQL_C_WCHAR) ?
				    *(SQLWCHAR **) valp : valp,
				    vlen, &q->paraminfo[pnum].rlen),
		   msgp, "SQLBindParameter(%d)", pnum + 1))
#else
    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		   SQLBindParameter(q->hstmt, (SQLUSMALLINT) (pnum + 1),
				    q->paraminfo[pnum].iotype,
				    ctype, stype, coldef,
				    q->paraminfo[pnum].scale,
				    valp, vlen, &q->paraminfo[pnum].rlen),
		   msgp, "SQLBindParameter(%d)", pnum + 1))
#endif
    {
	if (retry) {
	    retry = 0;
	    if (stype == SQL_VARCHAR) {
		/* maybe MS Jet memo field */
		stype = SQL_LONGVARCHAR;
		goto retry;
	    }
#ifdef UNICODE
	    if (stype == SQL_WVARCHAR) {
		stype = SQL_WLONGVARCHAR;
		goto retry;
	    }
#endif
	}
	return -1;
    }
    return 0;
}

static VALUE
stmt_exec_int(int argc, VALUE *argv, VALUE self, int mode)
{
    STMT *q;
    int i, argnum, has_out_parms = 0;
    char *msg = NULL;
    SQLRETURN ret;

    Data_Get_Struct(self, STMT, q);
    if (argc > q->nump - ((EXEC_PARMXOUT(mode) < 0) ? 0 : 1)) {
	rb_raise(Cerror, "%s", set_err("Too much parameters", 0));
    }
    if (q->hstmt == SQL_NULL_HSTMT) {
	rb_raise(Cerror, "%s", set_err("Stale ODBC::Statement", 0));
    }
    if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		   SQLFreeStmt(q->hstmt, SQL_CLOSE),
		   &msg, "SQLFreeStmt(SQL_CLOSE)")) {
	goto error;
    }
    callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
	    SQLFreeStmt(q->hstmt, SQL_RESET_PARAMS),
	    "SQLFreeStmt(SQL_RESET_PARMS)");
    for (i = argnum = 0; i < q->nump; i++) {
	VALUE arg;

	if (i == EXEC_PARMXOUT(mode)) {
	    if (bind_one_param(i, Qnil, q, &msg, &has_out_parms) < 0) {
		goto error;
	    }
	    continue;
	}
	arg = (argnum < argc) ? argv[argnum++] : Qnil;
	if (bind_one_param(i, arg, q, &msg, &has_out_parms) < 0) {
	    goto error;
	}
    }
    ret = SQLEXECUTE(q->hstmt);
    if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret,
			  &msg, "SQLExecute")) {
error:
#ifdef UNICODE
	for (i = 0; i < q->nump; i++) {
	    if (q->paraminfo[i].tofree != NULL) {
		uc_free(q->paraminfo[i].tofree);
		q->paraminfo[i].tofree = NULL;
	    }
	}
#endif
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		SQLFreeStmt(q->hstmt, SQL_DROP), "SQLFreeStmt(SQL_DROP)");
	q->hstmt = SQL_NULL_HSTMT;
	unlink_stmt(q);
	rb_raise(Cerror, "%s", msg);
    }
#ifdef UNICODE
    for (i = 0; i < q->nump; i++) {
	if (q->paraminfo[i].tofree != NULL) {
	    uc_free(q->paraminfo[i].tofree);
	    q->paraminfo[i].tofree = NULL;
	}
    }
#endif
    if (!has_out_parms) {
	callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt,
		SQLFreeStmt(q->hstmt, SQL_RESET_PARAMS),
		"SQLFreeStmt(SQL_RESET_PARAMS)");
    }
    if (ret == SQL_NO_DATA) {
	return Qnil;
    }
    return make_result(q->dbc, q->hstmt, self, mode);
}

static VALUE
stmt_exec(int argc, VALUE *argv, VALUE self)
{
    return stmt_exec_int(argc, argv, self, MAKERES_BLOCK);
}

static VALUE
stmt_run(int argc, VALUE *argv, VALUE self)
{
    if (argc < 1) {
	rb_raise(rb_eArgError, "wrong # of arguments");
    }
    if (argc == 1) {
	return stmt_prep_int(1, argv, self,
			     MAKERES_EXECD | MAKERES_BLOCK);
    }
    return stmt_exec(argc - 1, argv + 1, stmt_prep_int(1, argv, self, 0));
}

static VALUE
stmt_do(int argc, VALUE *argv, VALUE self)
{
    VALUE stmt;

    if (argc < 1) {
	rb_raise(rb_eArgError, "wrong # of arguments");
    }
    if (argc == 1) {
	stmt = stmt_prep_int(1, argv, self,
			     MAKERES_EXECD | MAKERES_BLOCK | MAKERES_NOCLOSE);
    } else {
	stmt = stmt_prep_int(1, argv, self, 0);
	stmt_exec_int(argc - 1, argv + 1, stmt,
		      MAKERES_BLOCK | MAKERES_NOCLOSE);
    }
    return rb_ensure(stmt_nrows, stmt, stmt_drop, stmt);
}

static VALUE
stmt_ignorecase(int argc, VALUE *argv, VALUE self)
{
    VALUE onoff = Qnil;
    int *flag = NULL;

    rb_scan_args(argc, argv, "01", &onoff);
    if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) {
	STMT *q;

	Data_Get_Struct(self, STMT, q);
	flag = &q->upc;
    } else if (rb_obj_is_kind_of(self, Cdbc) == Qtrue) {
	DBC *p;

	Data_Get_Struct(self, DBC, p);
	flag = &p->upc;
    } else {
	rb_raise(rb_eTypeError, "ODBC::Statement or ODBC::Database expected");
	return Qnil;
    }
    if (argc > 0) {
	*flag = RTEST(onoff);
    }
    return *flag ? Qtrue : Qfalse;
}

/*
 *----------------------------------------------------------------------
 *
 *	Create statement without implicit SQL prepare or execute.
 *
 *----------------------------------------------------------------------
 */
static VALUE
stmt_new(VALUE self)
{
    DBC *p;
    SQLHSTMT hstmt;
    char *msg = NULL;

    Data_Get_Struct(self, DBC, p);
    if (!succeeded(SQL_NULL_HENV, p->hdbc, SQL_NULL_HSTMT,
		   SQLAllocStmt(p->hdbc, &hstmt),
		   &msg, "SQLAllocStmt")) {
	rb_raise(Cerror, "%s", msg);
    }
    return wrap_stmt(self, p, hstmt, NULL);
}

/*
 *----------------------------------------------------------------------
 *
 *	Procedures with statements.
 *
 *----------------------------------------------------------------------
 */

static VALUE
stmt_proc_init(int argc, VALUE *argv, VALUE self)
{
    VALUE stmt = (argc > 0) ? argv[0] : Qnil;

    if (rb_obj_is_kind_of(stmt, Cstmt) == Qtrue) {
	rb_iv_set(self, "@statement", stmt);
	rb_iv_set(self, "@return_output_param", (argc > 1) ? argv[1] : Qnil);
	return self;
    }
    rb_raise(rb_eTypeError, "need ODBC::Statement as argument");
    return Qnil;
}

static VALUE
stmt_proc_call(int argc, VALUE *argv, VALUE self)
{
    VALUE stmt, val;

    stmt = rb_iv_get(self, "@statement");
    val = rb_iv_get(self, "@return_output_param");
    if (RTEST(val)) {
	int parnum = NUM2INT(val);

	stmt_exec_int(argc, argv, stmt, EXEC_PARMXNULL(parnum));
	rb_call_super(1, &stmt);
	return stmt_param_output_value(1, &val, stmt);
    }
    stmt_exec_int(argc, argv, stmt, 0);
    return rb_call_super(1, &stmt);
}

static VALUE
stmt_proc(int argc, VALUE *argv, VALUE self)
{
    VALUE sql, ptype, psize, pnum = Qnil, stmt, args[2];
    int parnum = 0;
#if (RUBY_API_VERSION_CODE >= 30000)
    VALUE block, proc_new_args[2];
#endif

#if (RUBY_API_VERSION_CODE >= 30000)
    rb_scan_args(argc, argv, "13&", &sql, &ptype, &psize, &pnum, &block);
#else
    rb_scan_args(argc, argv, "13", &sql, &ptype, &psize, &pnum);
#endif
    if (!rb_block_given_p()) {
	rb_raise(rb_eArgError, "block required");
    }
    stmt = stmt_prep_int(1, &sql, self, 0);
#if (RUBY_API_VERSION_CODE >= 30000)
    proc_new_args[0] = stmt;
#endif
    if (argc == 1) {
#if (RUBY_API_VERSION_CODE >= 30000)
	return rb_funcall_with_block(Cproc, IDnew, 1, proc_new_args, block);
#else
	return rb_funcall(Cproc, IDnew, 1, stmt);
#endif
    }
    if ((argc < 4) || (pnum == Qnil)) {
	pnum = INT2NUM(parnum);
    } else {
	parnum = NUM2INT(pnum);
    }
    args[0] = pnum;
    args[1] = INT2NUM(SQL_PARAM_OUTPUT);
    stmt_param_iotype(2, args, stmt);
    args[1] = ptype;
    stmt_param_output_type(2, args, stmt);
    if (argc > 2) {
	args[1] = psize;
    } else {
	args[1] = INT2NUM(256);
    }
    stmt_param_output_size(2, args, stmt);
#if (RUBY_API_VERSION_CODE >= 30000)
    proc_new_args[1] = pnum;
    return rb_funcall_with_block(Cproc, IDnew, 2, proc_new_args, block);
#else
    return rb_funcall(Cproc, IDnew, 2, stmt, pnum);
#endif
}

static VALUE
stmt_procwrap(int argc, VALUE *argv, VALUE self)
{
    VALUE arg0 = Qnil, arg1 = Qnil;

    rb_scan_args(argc, argv, "02", &arg0, &arg1);
    if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) {
	if (arg1 != Qnil) {
	    rb_raise(rb_eArgError, "wrong # arguments");
	}
	arg1 = arg0;
	arg0 = self;
    } else if (rb_obj_is_kind_of(arg0, Cstmt) != Qtrue) {
	rb_raise(rb_eTypeError, "need ODBC::Statement as 1st argument");
    }
    return rb_funcall(Cproc, IDnew, 2, arg0, arg1);
}

/*
 *----------------------------------------------------------------------
 *
 *	Module functions.
 *
 *----------------------------------------------------------------------
 */

static VALUE
mod_dbcdisc(VALUE dbc)
{
    return dbc_disconnect(0, NULL, dbc);
}

static VALUE
mod_connect(int argc, VALUE *argv, VALUE self)
{
    VALUE dbc = dbc_new(argc, argv, self);

    if (rb_block_given_p()) {
	return rb_ensure(rb_yield, dbc, mod_dbcdisc, dbc);
    }
    return dbc;
}

static VALUE
mod_2time(int argc, VALUE *argv, VALUE self)
{
    VALUE a1, a2;
    VALUE y, m, d, hh, mm, ss, us;
    int once = 0;

    rb_scan_args(argc, argv, "11", &a1, &a2);
again:
    if (rb_obj_is_kind_of(a1, Ctimestamp) == Qtrue) {
	TIMESTAMP_STRUCT *ts;

	if (argc > 1) {
	    rb_raise(rb_eArgError, "wrong # arguments(2 for 1)");
	}
	Data_Get_Struct(a1, TIMESTAMP_STRUCT, ts);
	y = INT2NUM(ts->year);
	m = INT2NUM(ts->month);
	d = INT2NUM(ts->day);
	hh = INT2NUM(ts->hour);
	mm = INT2NUM(ts->minute);
	ss = INT2NUM(ts->second);
	us = INT2NUM(ts->fraction / 1000);
	goto mktime;
    }
    if (rb_obj_is_kind_of(a1, Cdate) == Qtrue) {
	DATE_STRUCT *date;

	if (a2 != Qnil) {
	    if (rb_obj_is_kind_of(a2, Ctime) == Qtrue) {
		TIME_STRUCT *time;

		Data_Get_Struct(a2, TIME_STRUCT, time);
		hh = INT2NUM(time->hour);
		mm = INT2NUM(time->minute);
		ss = INT2NUM(time->second);
	    } else {
		rb_raise(rb_eTypeError, "expecting ODBC::Time");
	    }
	} else {
	    hh = INT2FIX(0);
	    mm = INT2FIX(0);
	    ss = INT2FIX(0);
	}
	Data_Get_Struct(a1, DATE_STRUCT, date);
	y = INT2NUM(date->year);
	m = INT2NUM(date->month);
	d = INT2NUM(date->day);
	us = INT2FIX(0);
    }
    if (rb_obj_is_kind_of(a1, Ctime) == Qtrue) {
	TIME_STRUCT *time;

	if (a2 != Qnil) {
	    if (rb_obj_is_kind_of(a2, Cdate) == Qtrue) {
		DATE_STRUCT *date;

		Data_Get_Struct(a2, DATE_STRUCT, date);
		y = INT2NUM(date->year);
		m = INT2NUM(date->month);
		d = INT2NUM(date->day);
	    } else {
		rb_raise(rb_eTypeError, "expecting ODBC::Date");
	    }
	} else {
	    VALUE now = FUNCALL_NOARGS(rb_cTime, IDnow);

	    y = rb_funcall(rb_cTime, IDyear, 1, now);
	    m = rb_funcall(rb_cTime, IDmonth, 1, now);
	    d = rb_funcall(rb_cTime, IDday, 1, now);
	}
	Data_Get_Struct(a1, TIME_STRUCT, time);
	hh = INT2NUM(time->hour);
	mm = INT2NUM(time->minute);
	ss = INT2NUM(time->second);
	us = INT2FIX(0);
mktime:
	return rb_funcall(rb_cTime, IDlocal, 7, y, m, d, hh, mm, ss, us);
    }
    if ((!once) && ((m = timestamp_load1(Ctimestamp, a1, -1)) != Qnil)) {
	a1 = m;
	once++;
	goto again;
    }
    if ((!once) && ((m = date_load1(Cdate, a1, -1)) != Qnil)) {
	a1 = m;
	if ((argc > 1) && ((m = time_load1(Ctime, a2, -1)) != Qnil)) {
	    a2 = m;
	}
	once++;
	goto again;
    }
    if ((!once) && ((m = time_load1(Ctime, a1, -1)) != Qnil)) {
	a1 = m;
	if ((argc > 1) && ((m = date_load1(Cdate, a2, -1)) != Qnil)) {
	    a2 = m;
	}
	once++;
	goto again;
    }
    rb_raise(rb_eTypeError,
	     "expecting ODBC::TimeStamp or ODBC::Date/Time or String");
    return Qnil;
}

static VALUE
mod_2date(VALUE self, VALUE arg)
{
    VALUE y, m, d;
    int once = 0;

again:
    if (rb_obj_is_kind_of(arg, Cdate) == Qtrue) {
	DATE_STRUCT *date;

	Data_Get_Struct(arg, DATE_STRUCT, date);
	y = INT2NUM(date->year);
	m = INT2NUM(date->month);
	d = INT2NUM(date->day);
	goto mkdate;
    }
    if (rb_obj_is_kind_of(arg, Ctimestamp) == Qtrue){
	TIMESTAMP_STRUCT *ts;

	Data_Get_Struct(arg, TIMESTAMP_STRUCT, ts);
	y = INT2NUM(ts->year);
	m = INT2NUM(ts->month);
	d = INT2NUM(ts->day);
mkdate:
	return rb_funcall(rb_cDate, IDnew, 3, y, m, d);
    }
    if ((!once) &&
	(((m = date_load1(Cdate, arg, -1)) != Qnil) ||
	 ((m = timestamp_load1(Ctimestamp, arg, -1)) != Qnil))) {
	arg = m;
	once++;
	goto again;
    }
    rb_raise(rb_eTypeError, "expecting ODBC::Date/Timestamp or String");
    return Qnil;
}

static VALUE
mod_trace(int argc, VALUE *argv, VALUE self)
{
    VALUE v = Qnil;

    rb_scan_args(argc, argv, "01", &v);
#ifdef TRACING
    if (argc > 0) {
	tracing = NUM2INT(v);
    }
    return INT2NUM(tracing);
#else
    return INT2NUM(0);
#endif
}

static VALUE
mod_gc_threshold(int argc, VALUE *argv, VALUE self)
{
    VALUE v = Qnil;

    rb_scan_args(argc, argv, "01", &v);
    if (argc > 0) {
	gc_threshold = NUM2INT(v);
    }
    return INT2NUM(gc_threshold);
}

/*
 *----------------------------------------------------------------------
 *
 *	Table of constants and intern'ed string mappings.
 *
 *----------------------------------------------------------------------
 */

#define O_CONST(x)    { #x, x }
#define O_CONSTU(x)   { #x, SQL_UNKNOWN_TYPE }
#define O_CONST2(x,y) { #x, y }
#define O_CONST_END   { NULL, -1 }

static struct {
    const char *name;
    int value;
} o_const[] = {
    O_CONST(SQL_CURSOR_FORWARD_ONLY),
    O_CONST(SQL_CURSOR_KEYSET_DRIVEN),
    O_CONST(SQL_CURSOR_DYNAMIC),
    O_CONST(SQL_CURSOR_STATIC),
    O_CONST(SQL_CONCUR_READ_ONLY),
    O_CONST(SQL_CONCUR_LOCK),
    O_CONST(SQL_CONCUR_ROWVER),
    O_CONST(SQL_CONCUR_VALUES),
    O_CONST(SQL_FETCH_NEXT),
    O_CONST(SQL_FETCH_FIRST),
    O_CONST(SQL_FETCH_LAST),
    O_CONST(SQL_FETCH_PRIOR),
    O_CONST(SQL_FETCH_ABSOLUTE),
    O_CONST(SQL_FETCH_RELATIVE),
    O_CONST(SQL_UNKNOWN_TYPE),
    O_CONST(SQL_CHAR),
    O_CONST(SQL_NUMERIC),
    O_CONST(SQL_DECIMAL),
    O_CONST(SQL_INTEGER),
    O_CONST(SQL_SMALLINT),
    O_CONST(SQL_FLOAT),
    O_CONST(SQL_REAL),
    O_CONST(SQL_DOUBLE),
    O_CONST(SQL_VARCHAR),
#ifdef SQL_DATETIME
    O_CONST(SQL_DATETIME),
#else
    O_CONSTU(SQL_DATETIME),
#endif
#ifdef SQL_DATE
    O_CONST(SQL_DATE),
#else
    O_CONSTU(SQL_DATE),
#endif
#ifdef SQL_TYPE_DATE
    O_CONST(SQL_TYPE_DATE),
#else
    O_CONSTU(SQL_TYPE_DATE),
#endif
#ifdef SQL_TIME
    O_CONST(SQL_TIME),
#else
    O_CONSTU(SQL_TIME),
#endif
#ifdef SQL_TYPE_TIME
    O_CONST(SQL_TYPE_TIME),
#else
    O_CONSTU(SQL_TYPE_TIME),
#endif
#ifdef SQL_TIMESTAMP
    O_CONST(SQL_TIMESTAMP),
#else
    O_CONSTU(SQL_TIMESTAMP),
#endif
#ifdef SQL_TYPE_TIMESTAMP
    O_CONST(SQL_TYPE_TIMESTAMP),
#else
    O_CONSTU(SQL_TYPE_TIMESTAMP),
#endif
#ifdef SQL_LONGVARCHAR
    O_CONST(SQL_LONGVARCHAR),
#else
    O_CONSTU(SQL_LONGVARCHAR),
#endif
#ifdef SQL_BINARY
    O_CONST(SQL_BINARY),
#else
    O_CONSTU(SQL_BINARY),
#endif
#ifdef SQL_VARBINARY
    O_CONST(SQL_VARBINARY),
#else
    O_CONSTU(SQL_VARBINARY),
#endif
#ifdef SQL_LONGVARBINARY
    O_CONST(SQL_LONGVARBINARY),
#else
    O_CONSTU(SQL_LONGVARBINARY),
#endif
#ifdef SQL_BIGINT
    O_CONST(SQL_BIGINT),
#else
    O_CONSTU(SQL_BIGINT),
#endif
#ifdef SQL_TINYINT
    O_CONST(SQL_TINYINT),
#else
    O_CONSTU(SQL_TINYINT),
#endif
#ifdef SQL_BIT
    O_CONST(SQL_BIT),
#else
    O_CONSTU(SQL_BIT),
#endif
#ifdef SQL_GUID
    O_CONST(SQL_GUID),
#else
    O_CONSTU(SQL_GUID),
#endif
#ifdef SQL_WCHAR
    O_CONST(SQL_WCHAR),
#else
    O_CONSTU(SQL_WCHAR),
#endif
#ifdef SQL_WVARCHAR
    O_CONST(SQL_WVARCHAR),
#else
    O_CONSTU(SQL_WVARCHAR),
#endif
#ifdef SQL_WLONGVARCHAR
    O_CONST(SQL_WLONGVARCHAR),
#else
    O_CONSTU(SQL_WLONGVARCHAR),
#endif
#ifdef SQL_ATTR_ODBC_VERSION
    O_CONST(SQL_OV_ODBC2),
    O_CONST(SQL_OV_ODBC3),
#else
    O_CONST2(SQL_OV_ODBC2, 2),
    O_CONST2(SQL_OV_ODBC3, 3),
#endif
#ifdef SQL_ATTR_CONNECTION_POOLING
#ifdef SQL_CP_OFF
    O_CONST(SQL_CP_OFF),
#else
    O_CONST2(SQL_CP_OFF, 0),
#endif
#ifdef SQL_CP_ONE_PER_DRIVER
    O_CONST(SQL_CP_ONE_PER_DRIVER),
#else
    O_CONST2(SQL_CP_ONE_PER_DRIVER, 1),
#endif
#ifdef SQL_CP_ONE_PER_HENV
    O_CONST(SQL_CP_ONE_PER_HENV),
#else
    O_CONST2(SQL_CP_ONE_PER_HENV, 2),
#endif
#ifdef SQL_CP_DEFAULT
    O_CONST(SQL_CP_DEFAULT),
#else
    O_CONST2(SQL_CP_DEFAULT, 0),
#endif
#else
    O_CONST2(SQL_CP_OFF, 0),
    O_CONST2(SQL_CP_ONE_PER_DRIVER, 0),
    O_CONST2(SQL_CP_ONE_PER_HENV, 0),
    O_CONST2(SQL_CP_DEFAULT, 0),
#endif
#ifdef SQL_ATTR_CP_MATCH
    O_CONST(SQL_CP_STRICT_MATCH),
    O_CONST(SQL_CP_RELAXED_MATCH),
    O_CONST(SQL_CP_MATCH_DEFAULT),
#else
    O_CONST2(SQL_CP_STRICT_MATCH, 0),
    O_CONST2(SQL_CP_RELAXED_MATCH, 0),
    O_CONST2(SQL_CP_MATCH_DEFAULT, 0),
#endif
#ifdef SQL_SCOPE_CURROW
    O_CONST(SQL_SCOPE_CURROW),
#else
    O_CONST2(SQL_SCOPE_CURROW, 0),
#endif
#ifdef SQL_SCOPE_TRANSACTION
    O_CONST(SQL_SCOPE_TRANSACTION),
#else
    O_CONST2(SQL_SCOPE_TRANSACTION, 0),
#endif
#ifdef SQL_SCOPE_SESSION
    O_CONST(SQL_SCOPE_SESSION),
#else
    O_CONST2(SQL_SCOPE_SESSION, 0),
#endif
#ifdef SQL_BEST_ROWID
    O_CONST(SQL_BEST_ROWID),
#else
    O_CONST2(SQL_BEST_ROWID, 0),
#endif
#ifdef SQL_ROWVER
    O_CONST(SQL_ROWVER),
#else
    O_CONST2(SQL_ROWVER, 0),
#endif
    O_CONST(SQL_PARAM_TYPE_UNKNOWN),
    O_CONST(SQL_PARAM_INPUT),
    O_CONST(SQL_PARAM_OUTPUT),
    O_CONST(SQL_PARAM_INPUT_OUTPUT),
    O_CONST(SQL_DEFAULT_PARAM),
    O_CONST(SQL_RETURN_VALUE),
    O_CONST(SQL_RESULT_COL),
    O_CONST(SQL_PT_UNKNOWN),
    O_CONST(SQL_PT_PROCEDURE),
    O_CONST(SQL_PT_FUNCTION),

    /* end of table */
    O_CONST_END
};

static struct {
    ID *idp;
    const char *str;
} ids[] = {
    { &IDstart, "start" },
    { &IDatatinfo, "@@info" },
    { &IDataterror, "@@error" },
    { &IDkeys, "keys" },
    { &IDatattrs, "@attrs" },
    { &IDday, "day" },
    { &IDmonth, "month" },
    { &IDyear, "year" },
    { &IDmday, "mday" },
    { &IDnsec, "nsec" },
    { &IDusec, "usec" },
    { &IDsec, "sec" },
    { &IDmin, "min" },
    { &IDhour, "hour" },
    { &IDusec, "usec" },
    { &IDkeyp, "key?" },
    { &IDkey, "key" },
    { &IDSymbol, "Symbol" },
    { &IDString, "String" },
    { &IDFixnum, "Fixnum" },
    { &IDtable_names, "table_names" },
    { &IDnew, "new" },
    { &IDnow, "now" },
    { &IDlocal, "local" },
    { &IDname, "name" },
    { &IDtable, "table" },
    { &IDtype, "type" },
    { &IDlength, "length" },
    { &IDnullable, "nullable" },
    { &IDscale, "scale" },
    { &IDprecision, "precision" },
    { &IDsearchable, "searchable" },
    { &IDunsigned, "unsigned" },
    { &IDiotype, "iotype" },
    { &IDoutput_size, "output_size" },
    { &IDoutput_type, "output_type" },
    { &IDdescr, "descr" },
    { &IDstatement, "statement" },
    { &IDreturn_output_param, "return_output_param" },
    { &IDattrs, "attrs" },
    { &IDNULL, "NULL" },
    { &IDdefault, "default" },
#ifdef USE_RB_ENC
    { &IDencode, "encode" },
#endif
    { &IDparse, "parse" },
    { &IDutc, "utc" },
    { &IDlocal, "local" },
    { &IDto_s, "to_s" }
};

/*
 *----------------------------------------------------------------------
 *
 *	Module initializer.
 *
 *----------------------------------------------------------------------
 */

void
#ifdef UNICODE
Init_odbc_utf8()
#else
Init_odbc()
#endif
{
    int i;
    const char *modname = "ODBC";
    ID modid = rb_intern(modname);
    VALUE v = Qnil;

    rb_require("date");
    rb_cDate = rb_eval_string("Date");

    if (rb_const_defined(rb_cObject, modid)) {
	v = rb_const_get(rb_cObject, modid);
	if (TYPE(v) != T_MODULE) {
	    rb_raise(rb_eTypeError, "%s already defined", modname);
	}
    }
    if (v != Qnil) {
#ifdef UNICODE
	modname = "ODBC_UTF8";
#else
	modname = "ODBC_NONE";
#endif
    }

    for (i = 0; i < (int) (sizeof (ids) / sizeof (ids[0])); i++) {
	*(ids[i].idp) = rb_intern(ids[i].str);
    }

    Modbc = rb_define_module(modname);
    Cobj = rb_define_class_under(Modbc, "Object", rb_cObject);
    rb_define_class_variable(Cobj, "@@error", Qnil);
    rb_define_class_variable(Cobj, "@@info", Qnil);

    Cenv = rb_define_class_under(Modbc, "Environment", Cobj);
    Cdbc = rb_define_class_under(Modbc, "Database", Cenv);
    Cstmt = rb_define_class_under(Modbc, "Statement", Cdbc);
    rb_include_module(Cstmt, rb_mEnumerable);

    Ccolumn = rb_define_class_under(Modbc, "Column", Cobj);
    rb_attr(Ccolumn, IDname, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDtable, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDtype, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDlength, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDnullable, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDscale, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDprecision, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDsearchable, 1, 0, Qfalse);
    rb_attr(Ccolumn, IDunsigned, 1, 0, Qfalse);

    Cparam = rb_define_class_under(Modbc, "Parameter", Cobj);
    rb_attr(Cparam, IDtype, 1, 0, Qfalse);
    rb_attr(Cparam, IDprecision, 1, 0, Qfalse);
    rb_attr(Cparam, IDscale, 1, 0, Qfalse);
    rb_attr(Cparam, IDnullable, 1, 0, Qfalse);
    rb_attr(Cparam, IDiotype, 1, 0, Qfalse);
    rb_attr(Cparam, IDoutput_size, 1, 0, Qfalse);
    rb_attr(Cparam, IDoutput_type, 1, 0, Qfalse);

    Cdsn = rb_define_class_under(Modbc, "DSN", Cobj);
    rb_attr(Cdsn, IDname, 1, 1, Qfalse);
    rb_attr(Cdsn, IDdescr, 1, 1, Qfalse);

    Cdrv = rb_define_class_under(Modbc, "Driver", Cobj);
    rb_attr(Cdrv, IDname, 1, 1, Qfalse);
    rb_attr(Cdrv, IDattrs, 1, 1, Qfalse);

    Cerror = rb_define_class_under(Modbc, "Error", rb_eStandardError);

    Cproc = rb_define_class("ODBCProc", rb_cProc);

    Cdate = rb_define_class_under(Modbc, "Date", Cobj);
    rb_include_module(Cdate, rb_mComparable);
    Ctime = rb_define_class_under(Modbc, "Time", Cobj);
    rb_include_module(Ctime, rb_mComparable);
    Ctimestamp = rb_define_class_under(Modbc, "TimeStamp", Cobj);
    rb_include_module(Ctimestamp, rb_mComparable);

    /* module functions */
    rb_define_module_function(Modbc, "trace", mod_trace, -1);
    rb_define_module_function(Modbc, "trace=", mod_trace, -1);
    rb_define_module_function(Modbc, "connect", mod_connect, -1);
    rb_define_module_function(Modbc, "datasources", dbc_dsns, 0);
    rb_define_module_function(Modbc, "drivers", dbc_drivers, 0);
    rb_define_module_function(Modbc, "error", dbc_error, 0);
    rb_define_module_function(Modbc, "info", dbc_warn, 0);
    rb_define_module_function(Modbc, "clear_error", dbc_clrerror, 0);
    rb_define_module_function(Modbc, "newenv", env_new, 0);
    rb_define_module_function(Modbc, "to_time", mod_2time, -1);
    rb_define_module_function(Modbc, "to_date", mod_2date, 1);
    rb_define_module_function(Modbc, "connection_pooling", env_cpooling, -1);
    rb_define_module_function(Modbc, "connection_pooling=", env_cpooling, -1);
    rb_define_module_function(Modbc, "raise", dbc_raise, 1);
    rb_define_module_function(Modbc, "gc_threshold", mod_gc_threshold, -1);
    rb_define_module_function(Modbc, "gc_threshold=", mod_gc_threshold, -1);

    /* singleton methods and constructors */
    rb_define_singleton_method(Cobj, "error", dbc_error, 0);
    rb_define_singleton_method(Cobj, "info", dbc_warn, 0);
    rb_define_singleton_method(Cobj, "clear_error", dbc_clrerror, 0);
    rb_define_singleton_method(Cobj, "raise", dbc_raise, 1);
    rb_define_alloc_func(Cenv, env_new);
    rb_define_singleton_method(Cenv, "connect", dbc_new, -1);
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(Cdbc, dbc_alloc);
#else
    rb_define_alloc_func(Cdbc, dbc_new);
    rb_define_alloc_func(Cdsn, dsn_new);
    rb_define_alloc_func(Cdrv, drv_new);
#endif
    rb_define_method(Cdsn, "initialize", dsn_init, 0);
    rb_define_method(Cdrv, "initialize", drv_init, 0);
    rb_define_method(Cdbc, "newstmt", stmt_new, 0);

    /* common (Cobj) methods */
    rb_define_method(Cobj, "error", dbc_error, 0);
    rb_define_method(Cobj, "info", dbc_warn, 0);
    rb_define_method(Cobj, "clear_error", dbc_clrerror, 0);
    rb_define_method(Cobj, "raise", dbc_raise, 1);

    /* common (Cenv) methods */
    rb_define_method(Cenv, "connect", dbc_new, -1);
    rb_define_method(Cenv, "environment", env_of, 0);
    rb_define_method(Cenv, "transaction", dbc_transaction, 0);
    rb_define_method(Cenv, "commit", dbc_commit, 0);
    rb_define_method(Cenv, "rollback", dbc_rollback, 0);
    rb_define_method(Cenv, "connection_pooling", env_cpooling, -1);
    rb_define_method(Cenv, "connection_pooling=", env_cpooling, -1);
    rb_define_method(Cenv, "cp_match", env_cpmatch, -1);
    rb_define_method(Cenv, "cp_match=", env_cpmatch, -1);
    rb_define_method(Cenv, "odbc_version", env_odbcver, -1);
    rb_define_method(Cenv, "odbc_version=", env_odbcver, -1);

    /* management things (odbcinst.h) */
    rb_define_module_function(Modbc, "add_dsn", dbc_adddsn, -1);
    rb_define_module_function(Modbc, "config_dsn", dbc_confdsn, -1);
    rb_define_module_function(Modbc, "del_dsn", dbc_deldsn, -1);
    rb_define_module_function(Modbc, "write_file_dsn", dbc_wfdsn, -1);
    rb_define_module_function(Modbc, "read_file_dsn", dbc_rfdsn, -1);

    /* connection (database) methods */
    rb_define_method(Cdbc, "initialize", dbc_connect, -1);
    rb_define_method(Cdbc, "connect", dbc_connect, -1);
    rb_define_method(Cdbc, "connected?", dbc_connected, 0);
    rb_define_method(Cdbc, "drvconnect", dbc_drvconnect, 1);
    rb_define_method(Cdbc, "drop_all", dbc_dropall, 0);
    rb_define_method(Cdbc, "disconnect", dbc_disconnect, -1);
    rb_define_method(Cdbc, "tables", dbc_tables, -1);
    rb_define_method(Cdbc, "columns", dbc_columns, -1);
    rb_define_method(Cdbc, "primary_keys", dbc_primkeys, -1);
    rb_define_method(Cdbc, "indexes", dbc_indexes, -1);
    rb_define_method(Cdbc, "types", dbc_types, -1);
    rb_define_method(Cdbc, "foreign_keys", dbc_forkeys, -1);
    rb_define_method(Cdbc, "table_privileges", dbc_tpriv, -1);
    rb_define_method(Cdbc, "procedures", dbc_procs, -1);
    rb_define_method(Cdbc, "procedure_columns", dbc_proccols, -1);
    rb_define_method(Cdbc, "special_columns", dbc_speccols, -1);
    rb_define_method(Cdbc, "get_info", dbc_getinfo, -1);
    rb_define_method(Cdbc, "prepare", stmt_prep, -1);
    rb_define_method(Cdbc, "run", stmt_run, -1);
    rb_define_method(Cdbc, "do", stmt_do, -1);
    rb_define_method(Cdbc, "proc", stmt_proc, -1);
    rb_define_method(Cdbc, "use_time", dbc_timefmt, -1);
    rb_define_method(Cdbc, "use_time=", dbc_timefmt, -1);
    rb_define_method(Cdbc, "use_utc", dbc_timeutc, -1);
    rb_define_method(Cdbc, "use_utc=", dbc_timeutc, -1);
    rb_define_method(Cdbc, "use_sql_column_name", dbc_use_scn, -1);
    rb_define_method(Cdbc, "use_sql_column_name=", dbc_use_scn, -1);

    /* connection options */
    rb_define_method(Cdbc, "get_option", dbc_getsetoption, -1);
    rb_define_method(Cdbc, "set_option", dbc_getsetoption, -1);
    rb_define_method(Cdbc, "autocommit", dbc_autocommit, -1);
    rb_define_method(Cdbc, "autocommit=", dbc_autocommit, -1);
    rb_define_method(Cdbc, "concurrency", dbc_concurrency, -1);
    rb_define_method(Cdbc, "concurrency=", dbc_concurrency, -1);
    rb_define_method(Cdbc, "maxrows", dbc_maxrows, -1);
    rb_define_method(Cdbc, "maxrows=", dbc_maxrows, -1);
    rb_define_method(Cdbc, "timeout", dbc_timeout, -1);
    rb_define_method(Cdbc, "timeout=", dbc_timeout, -1);
    rb_define_method(Cdbc, "login_timeout", dbc_login_timeout, -1);
    rb_define_method(Cdbc, "login_timeout=", dbc_login_timeout, -1);
    rb_define_method(Cdbc, "maxlength", dbc_maxlength, -1);
    rb_define_method(Cdbc, "maxlength=", dbc_maxlength, -1);
    rb_define_method(Cdbc, "rowsetsize", dbc_rowsetsize, -1);
    rb_define_method(Cdbc, "cursortype", dbc_cursortype, -1);
    rb_define_method(Cdbc, "cursortype=", dbc_cursortype, -1);
    rb_define_method(Cdbc, "noscan", dbc_noscan, -1);
    rb_define_method(Cdbc, "noscan=", dbc_noscan, -1);
    rb_define_method(Cdbc, "ignorecase", stmt_ignorecase, -1);
    rb_define_method(Cdbc, "ignorecase=", stmt_ignorecase, -1);

    /* statement methods */
    rb_define_method(Cstmt, "drop", stmt_drop, 0);
    rb_define_method(Cstmt, "close", stmt_close, 0);
    rb_define_method(Cstmt, "cancel", stmt_cancel, 0);
    rb_define_method(Cstmt, "column", stmt_column, -1);
    rb_define_method(Cstmt, "columns", stmt_columns, -1);
    rb_define_method(Cstmt, "parameter", stmt_param, -1);
    rb_define_method(Cstmt, "parameters", stmt_params, 0);
    rb_define_method(Cstmt, "param_type", stmt_param_type, -1);
    rb_define_method(Cstmt, "param_iotype", stmt_param_iotype, -1);
    rb_define_method(Cstmt, "param_output_size", stmt_param_output_size, -1);
    rb_define_method(Cstmt, "param_output_type", stmt_param_output_type, -1);
    rb_define_method(Cstmt, "param_output_value", stmt_param_output_value, -1);
    rb_define_method(Cstmt, "ncols", stmt_ncols, 0);
    rb_define_method(Cstmt, "nrows", stmt_nrows, 0);
    rb_define_method(Cstmt, "nparams", stmt_nparams, 0);
    rb_define_method(Cstmt, "cursorname", stmt_cursorname, -1);
    rb_define_method(Cstmt, "cursorname=", stmt_cursorname, -1);
    rb_define_method(Cstmt, "fetch", stmt_fetch, 0);
    rb_define_method(Cstmt, "fetch!", stmt_fetch_bang, 0);
    rb_define_method(Cstmt, "fetch_first", stmt_fetch_first, 0);
    rb_define_method(Cstmt, "fetch_first!", stmt_fetch_first_bang, 0);
    rb_define_method(Cstmt, "fetch_scroll", stmt_fetch_scroll, -1);
    rb_define_method(Cstmt, "fetch_scroll!", stmt_fetch_scroll_bang, -1);
    rb_define_method(Cstmt, "fetch_hash", stmt_fetch_hash, -1);
    rb_define_method(Cstmt, "fetch_hash!", stmt_fetch_hash_bang, -1);
    rb_define_method(Cstmt, "fetch_first_hash", stmt_fetch_first_hash, -1);
    rb_define_method(Cstmt, "fetch_many", stmt_fetch_many, 1);
    rb_define_method(Cstmt, "fetch_all", stmt_fetch_all, 0);
    rb_define_method(Cstmt, "each", stmt_each, 0);
    rb_define_method(Cstmt, "each_hash", stmt_each_hash, -1);
    rb_define_method(Cstmt, "execute", stmt_exec, -1);
    rb_define_method(Cstmt, "make_proc", stmt_procwrap, -1);
    rb_define_method(Cstmt, "more_results", stmt_more_results, 0);
    rb_define_method(Cstmt, "prepare", stmt_prep, -1);
    rb_define_method(Cstmt, "run", stmt_run, -1);
    rb_define_singleton_method(Cstmt, "make_proc", stmt_procwrap, -1);

    /* statement options */
    rb_define_method(Cstmt, "get_option", stmt_getsetoption, -1);
    rb_define_method(Cstmt, "set_option", stmt_getsetoption, -1);
    rb_define_method(Cstmt, "concurrency", stmt_concurrency, -1);
    rb_define_method(Cstmt, "concurrency=", stmt_concurrency, -1);
    rb_define_method(Cstmt, "maxrows", stmt_maxrows, -1);
    rb_define_method(Cstmt, "maxrows=", stmt_maxrows, -1);
    rb_define_method(Cstmt, "timeout", stmt_timeout, -1);
    rb_define_method(Cstmt, "timeout=", stmt_timeout, -1);
    rb_define_method(Cstmt, "maxlength", stmt_maxlength, -1);
    rb_define_method(Cstmt, "maxlength=", stmt_maxlength, -1);
    rb_define_method(Cstmt, "cursortype", stmt_cursortype, -1);
    rb_define_method(Cstmt, "cursortype=", stmt_cursortype, -1);
    rb_define_method(Cstmt, "noscan", stmt_noscan, -1);
    rb_define_method(Cstmt, "noscan=", stmt_noscan, -1);
    rb_define_method(Cstmt, "rowsetsize", stmt_rowsetsize, -1);

    /* data type methods */
#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(Cdate, date_alloc);
#else
    rb_define_singleton_method(Cdate, "new", date_new, -1);
#endif
    rb_define_singleton_method(Cdate, "_load", date_load, 1);
    rb_define_method(Cdate, "initialize", date_init, -1);
    rb_define_method(Cdate, "clone", date_clone, 0);
    rb_define_method(Cdate, "to_s", date_to_s, 0);
    rb_define_method(Cdate, "_dump", date_dump, 1);
    rb_define_method(Cdate, "inspect", date_inspect, 0);
    rb_define_method(Cdate, "year", date_year, -1);
    rb_define_method(Cdate, "month", date_month, -1);
    rb_define_method(Cdate, "day", date_day, -1);
    rb_define_method(Cdate, "year=", date_year, -1);
    rb_define_method(Cdate, "month=", date_month, -1);
    rb_define_method(Cdate, "day=", date_day, -1);
    rb_define_method(Cdate, "<=>", date_cmp, 1);

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(Ctime, time_alloc);
#else
    rb_define_singleton_method(Ctime, "new", time_new, -1);
#endif
    rb_define_singleton_method(Ctime, "_load", time_load, 1);
    rb_define_method(Ctime, "initialize", time_init, -1);
    rb_define_method(Ctime, "clone", time_clone, 0);
    rb_define_method(Ctime, "to_s", time_to_s, 0);
    rb_define_method(Ctime, "_dump", time_dump, 1);
    rb_define_method(Ctime, "inspect", time_inspect, 0);
    rb_define_method(Ctime, "hour", time_hour, -1);
    rb_define_method(Ctime, "minute", time_min, -1);
    rb_define_method(Ctime, "second", time_sec, -1);
    rb_define_method(Ctime, "hour=", time_hour, -1);
    rb_define_method(Ctime, "minute=", time_min, -1);
    rb_define_method(Ctime, "second=", time_sec, -1);
    rb_define_method(Ctime, "<=>", time_cmp, 1);

#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(Ctimestamp, timestamp_alloc);
#else
    rb_define_singleton_method(Ctimestamp, "new", timestamp_new, -1);
#endif
    rb_define_singleton_method(Ctimestamp, "_load", timestamp_load, 1);
    rb_define_method(Ctimestamp, "initialize", timestamp_init, -1);
    rb_define_method(Ctimestamp, "clone", timestamp_clone, 0);
    rb_define_method(Ctimestamp, "to_s", timestamp_to_s, 0);
    rb_define_method(Ctimestamp, "_dump", timestamp_dump, 1);
    rb_define_method(Ctimestamp, "inspect", timestamp_inspect, 0);
    rb_define_method(Ctimestamp, "year", timestamp_year, -1);
    rb_define_method(Ctimestamp, "month", timestamp_month, -1);
    rb_define_method(Ctimestamp, "day", timestamp_day, -1);
    rb_define_method(Ctimestamp, "hour", timestamp_hour, -1);
    rb_define_method(Ctimestamp, "minute", timestamp_min, -1);
    rb_define_method(Ctimestamp, "second", timestamp_sec, -1);
    rb_define_method(Ctimestamp, "fraction", timestamp_fraction, -1);
    rb_define_method(Ctimestamp, "year=", timestamp_year, -1);
    rb_define_method(Ctimestamp, "month=", timestamp_month, -1);
    rb_define_method(Ctimestamp, "day=", timestamp_day, -1);
    rb_define_method(Ctimestamp, "hour=", timestamp_hour, -1);
    rb_define_method(Ctimestamp, "minute=", timestamp_min, -1);
    rb_define_method(Ctimestamp, "second=", timestamp_sec, -1);
    rb_define_method(Ctimestamp, "fraction=", timestamp_fraction, -1);
    rb_define_method(Ctimestamp, "<=>", timestamp_cmp, 1);

    /* procedure methods */
    rb_define_method(Cproc, "initialize", stmt_proc_init, -1);
    rb_define_method(Cproc, "call", stmt_proc_call, -1);
    rb_define_method(Cproc, "[]", stmt_proc_call, -1);
    rb_attr(Cproc, IDstatement, 1, 0, Qfalse);
    rb_attr(Cproc, IDreturn_output_param, 1, 0, Qfalse);
#ifndef HAVE_RB_DEFINE_ALLOC_FUNC
    rb_enable_super(Cproc, "call");
    rb_enable_super(Cproc, "[]");
#endif

    /* constants */
    for (i = 0; o_const[i].name != NULL; i++) {
	rb_define_const(Modbc, o_const[i].name,
			INT2NUM(o_const[i].value));
    }
    for (i = 0; get_info_map[i].name != NULL; i++) {
	rb_define_const(Modbc, get_info_map[i].name,
			INT2NUM(get_info_map[i].info));
    }
    for (i = 0; get_info_bitmap[i].name != NULL; i++) {
	rb_define_const(Modbc, get_info_bitmap[i].name,
			INT2NUM(get_info_bitmap[i].bits));
    }
    for (i = 0; option_map[i].name != NULL; i++) {
	rb_define_const(Modbc, option_map[i].name,
			INT2NUM(option_map[i].option));
    }

#ifdef UNICODE
    rb_define_const(Modbc, "UTF8", Qtrue);
#ifdef USE_RB_ENC
    rb_enc = rb_utf8_encoding();
    rb_encv = rb_enc_from_encoding(rb_enc);
#endif
#else
    rb_define_const(Modbc, "UTF8", Qfalse);
#endif

#ifdef TRACING
    if (ruby_verbose) {
	tracing = -1;
    }
#endif
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * tab-width: 8
 * End:
 */
